cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
  • Instantly extract effect sizes, F-ratios, and FDR-adjusted p-values from your models with the Calculate Effects Sizes extension, available now in the JMP Marketplace!
  • New to JMP? Join us Sept. 23-24 for the Early User Edition of Discovery Summit, tailor-made for new users. Register now for free!
  • Your voice matters! Tell us how you prefer to receive JMP updates, so we can tailor our communication to your needs. Take short survey.

Discussions

Solve problems, and share tips and tricks with other JMP users.
Choose Language Hide Translation Bar
jj_jmp
Level II

How do I construct a column A, whose entries are equal to the mean/median/mode of the last 10 non-null entries of another column B?

Hello, 

I have a column A (around 15,000 rows) which has numbers and many null values (maybe 70-80% null).

I would like to construct a column B which at row i, is equal to the the mean of:   the *last 10 non-null values from A* 

--> this means take  A[row i-1] , A[row i-2], ... and go back until you have 10 non-null values (or if you reach row 1 then  take whatever number <= 10 of  non-null values you get)

 

I would also like to do in addition to mean, a median and also mode, but a smoothed version of the mode where:

I first do a Gaussian smoothing of the 10 non-null values ...  and take the argmax (mode) of that distributions. 

 

Thanks for any thoughts or suggestions!

 

***

Solution ideas: One thought I had was because I want to do several different calculations with the last 10 non-null values, I should first construct a column C whose entries are arrays equal to those last 10 non-null values ... then I can do 3 formulas which operate on column C to produce B1 (mean), B2 (medial), B3 (smoothed mode).

 

Here are some code fragments moving towards constructing this column C of arrays:

//step 1 grab the last say 50 entries (some fixed amount that will likely have 10 non-nulls)

For Each Row( dt_join, 

	to = Row();

	If( to - 50 < 0,

	from = 1,

	from = to - 50); // Quantile( 0.5, 

		values = :away_1g_pull_seconds[Index( From, To )] <<Get Values //this is an array 

		Show(values);

	If(Row() > 50, Break());

); 

//step 2 convert array to list, drop nulls, convert list of first 10 entries back to array ... 
//I don't know how to assign this to a row of C 

num_lag = 10; 

values = dt_join:A[Index( From, To )] ;

 MyList = Remove(as list(values)[1], as list(Loc(as list(values)[1], .)) );   //convert array to list and remove nulls

 Mat = Number Col Box("", MyList[1:10]) << get as matrix; //hack convert to array

 

 

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
Craige_Hales
Super User

Re: How do I construct a column A, whose entries are equal to the mean/median/mode of the last 10 non-null entries of another column B?

I'd guess it is still N^2 for the helper=AsConstant. That will be copying a 100K array 400K times...

 

edit: guess confirmed. But even lifting the helper out of the formula still leaves my code about half as fast as Brady's.

 

But...Jim's code can be tweaked to be faster...

 

dt = Current Data Table();
startTime = Tick Seconds();
theMatrix = {., ., ., ., ., ., ., ., ., .};

z = .;
dt << New Column( "B" );
dt << begindataupdate;
For Each Row(
    v = :A;
    If( v == v,
        Insert Into( theMatrix, v );
        Remove From( theMatrix, 1 );
        z = Mean( theMatrix );
    );
    :B = z;
);
stopTime = Tick Seconds();
Show( stopTime - startTime );
dt << enddataupdate;
Craige

View solution in original post

8 REPLIES 8
txnelson
Super User

Re: How do I construct a column A, whose entries are equal to the mean/median/mode of the last 10 non-null entries of another column B?

Here is a beginning for your first request.  It will create a mean for the last 10 values for column A and put the mean into column B

I don't have time right now to work more on this.  Maybe later this weekend.

Names Default To Here( 1 );
dt = Current Data Table();
theMatrix = [];
dt << New Column( "B" );
For Each Row(
	If( Is Missing( :A ) == 0,
		theMatrix = Matrix( :a ) || theMatrix
	);
	If( Length( theMatrix ) > 10,
		theMatrix[1, 11] = []
	);
	:B = Mean( theMatrix );
);
Jim
Craige_Hales
Super User

Re: How do I construct a column A, whose entries are equal to the mean/median/mode of the last 10 non-null entries of another column B?

@DonMcCormack this would make a good challenge problem.

I think this will work and be reasonably efficient.

nr = 15000;
dt = New Table( "Untitled",
    Add Rows( nr ),
    New Column( "a", Numeric, "Continuous", Format( "Best", 12 ), Set Values( J( nr, 1, If( Random Uniform() < .3, Random Integer( 0, 1 ), . ) ) ) )
);

lookback = 10;

dt << New Column( "b" || Char( lookback ),
    formula(
        // make a helper to help look up the non-missing rows
        helper = As Constant( // AsConstant is for efficiency
            temp = dt << getrowswhere( !Is Missing( a ) );
            // temp is the non-missing row numbers. Make sure row 1
            // is included, even if missing, because LocSorted will
            // return [1] when a smaller value would have been better.
            If( temp[1] != 1,
                Insert Into( temp, 1, 1 )
            );
            temp; // this is assigned to 'helper'
        );
        currentrow = Row(); // this is the target for a new mean, etc
        end = Loc Sorted( helper, currentrow )[1]; // end is the last non missing row before or at the target
        start = Max( 1, end - lookback + 1 ); // start is the row 'lookback' rows earlier
        rowset = helper[start :: end]; // rowset is the set of nonmissing rows, possibly including a missing value at row 1.
        // it is OK if a missing value is present; mean/median/mode ignore it.
        // grab the rowset from column a and report the statistic...
        Mean( dt[rowset, {a}] );// median, mode
    )
);

 

Craige

Re: How do I construct a column A, whose entries are equal to the mean/median/mode of the last 10 non-null entries of another column B?

Hello,

 

Here is another way to do this, using an intermediate table with no missings, which allows a simpler formula.

Names Default To Here( 1 );

dt = Current Data Table();

//get nonmissing rows of table and put them in a new table
m = dt << get rows where( !Is Missing( :a ) );
dt2 = New Table( "xxx", invisible, <<add rows( N Row( m ) ) );
dt2[0, 1] = dt[m, 1];

//run formula on the new table. it has no missings, so direct indexing can be used
dt2 << New Column( "Xx",
	formula(
		tab = :column1 << get data table;
		Mean( tab[Index( Max( Row() - 9, 1 ), Row() ), 1] );
	)
);

//place these answers in their corresponding rows in the orignal table, in a new column.
dt << New Column( "ans" );
dt[m, N Col( dt )] = dt2[0, 2];

//span empty data to fill
dt << New Column( "Mean10",
	formula( If( Row() == 1, :ans, !Is Missing( :ans ), :ans, Lag( :Mean10 ) ) )
);

//cleanup
dt:Mean10 << Delete Formula;
dt << delete columns( :ans );
Close( dt2, nosave );


 

When the original table is small (like yours... about 15K rows), the two approaches perform similarly--the first is a touch faster:

 

brady_brady_1-1665844715162.png

 

As the original table grows, the "separate table" approach seems to scale appreciably better. Here is a 150K row example:

 

brady_brady_0-1665844547704.png

 

A possible deal-breaker (??) is that this approach does not produce a formula in the source table, so you would need to re-run this whenever new data is appended.

 

I've attached the 150K row file in case you want to experiment.

 

Cheers,

Brady

Re: How do I construct a column A, whose entries are equal to the mean/median/mode of the last 10 non-null entries of another column B?

Here are some more results. The "separate" method seems to be scaling linearly. The "same table" method seems to hit a wall somewhere north of 450,000 rows. I'm not sure why.

 

brady_brady_2-1665849827059.png

brady_brady_1-1665849460162.png

Craige_Hales
Super User

Re: How do I construct a column A, whose entries are equal to the mean/median/mode of the last 10 non-null entries of another column B?

I'd guess it is still N^2 for the helper=AsConstant. That will be copying a 100K array 400K times...

 

edit: guess confirmed. But even lifting the helper out of the formula still leaves my code about half as fast as Brady's.

 

But...Jim's code can be tweaked to be faster...

 

dt = Current Data Table();
startTime = Tick Seconds();
theMatrix = {., ., ., ., ., ., ., ., ., .};

z = .;
dt << New Column( "B" );
dt << begindataupdate;
For Each Row(
    v = :A;
    If( v == v,
        Insert Into( theMatrix, v );
        Remove From( theMatrix, 1 );
        z = Mean( theMatrix );
    );
    :B = z;
);
stopTime = Tick Seconds();
Show( stopTime - startTime );
dt << enddataupdate;
Craige

Re: How do I construct a column A, whose entries are equal to the mean/median/mode of the last 10 non-null entries of another column B?

That is really nice--much more elegant than my approach.

txnelson
Super User

Re: How do I construct a column A, whose entries are equal to the mean/median/mode of the last 10 non-null entries of another column B?

Wow......did this exercise teach me a bunch.  My original code worked, but was sooooo much slower than the alternate methods, that I took the time to study them.  Very fast and clever code.  Then @Craige_Hales provided his latest code, which is down right speedy, but takes an approach very close to what my original code in approach.  @Craige_Hales code is much faster.  And most of the difference is in my lack of using

Begin Data Update;
.....
End Data Update;

So I have now gained a large appreciation for the holding up of the Update Messages until the end of the updating.

Thanks for the fun!

Jim
jj_jmp
Level II

Re: How do I construct a column A, whose entries are equal to the mean/median/mode of the last 10 non-null entries of another column B?

Thank so much for all your responses - I am blown away! 

I have not had chance to read them in detail but I will respond more when I do. 

 

Best,

 

Joe

Recommended Articles