cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Discussions

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

Mann-Kendall Evaluation

There is a Mann-Kendall Evaluation that can be done in ProUCL. Is there something equivalent (or close to equivalent) in JMP? I'd like to run an existing function/platform in JMP (no scripts).

4 REPLIES 4

Re: Mann-Kendall Evaluation

Hi @FaceBiasBee678,

The Analyze > Multivariate platform has a Kendall's τ option that might be what you're looking for. I'm not familiar with ProUCL, so I might be answering completely incorrectly.

Jed_Campbell_0-1789595905040.png

When you mention looking for something equivalent or close to equivalent, can you clarify what exactly you're looking for? For example, do you need a non-parametric test for correlation between variables (Kendall's τ might be what you're looking for), or are you just looking to see if there's correlation between 2 variables (Fit Y by X > Fit Line might better suit your needs)?

P_Bartell
Level VIII

Re: Mann-Kendall Evaluation

@Jed_Campbell : The Mann-Kendall test is a test used on time series data to determine if within a sequence of points there exists a trend up or down...Kendall Tau is not what this poster is looking for. Maybe there is some type of test for this in Time Series platform?

Re: Mann-Kendall Evaluation

Apparently "searching the JMP User Community for the "Mann-Kendall Trend Test Add-In" will allow you to download a free script that generates the exact regulatory tables automatically." Has anyone done this and can provide the script along with some pointers?

MathStatChem
Level VII

Re: Mann-Kendall Evaluation

A quick bit of "Claude"-ing comes up with this:

// =====================================================================
// Mann-Kendall Trend Test + Sen's Slope (JSL)
// ---------------------------------------------------------------------
// Code created by Claude (Anthropic).
// Prompt summary: User asked for a JSL script that performs the
// Mann-Kendall test for monotonic trend in a time series (S statistic,
// tie-corrected variance, Z, p-value, Kendall's tau) plus Sen's slope
// with a confidence interval.
////
// Usage: open a data table, run this script, pick the response column
// (required), an optional time/order column, and alpha.
// =====================================================================

Names Default To Here( 1 );

// ---------------------------------------------------------------------
// Helper: Sign( v ) returns +1 if v > 0, -1 if v < 0, and 0 if v == 0
// ---------------------------------------------------------------------
Sign = Function( {v},
	If( v > 0, 1, v < 0, -1, 0 )
);

// ---------------------------------------------------------------------
// STEP 1: Select data table and columns
// ---------------------------------------------------------------------
dt = Current Data Table();
If( Is Empty( dt ),
	Throw( "Open a data table before running this script." )
);

dlg = Column Dialog(
	yCol = ColList( "Y, Response", Max Col( 1 ), Min Col( 1 ), Data Type( Numeric ) ),
	xCol = ColList( "Time / Order (optional; row order used if blank)", Max Col( 1 ) ),
	HList( "Alpha", alphaBox = EditNumber( 0.05 ) )
);
If( dlg["Button"] == -1, Throw( "Cancelled." ) );

yCol = dlg["yCol"];
xCol = dlg["xCol"];
alpha = dlg["alphaBox"];

yName = Column( dt, yCol[1] ) << Get Name;
hasX = N Items( xCol ) > 0;
If( hasX, xName = Column( dt, xCol[1] ) << Get Name, xName = "Observation Order" );

// ---------------------------------------------------------------------
// STEP 2: Pull values, drop missing, and sort by time/order
// ---------------------------------------------------------------------
yAll = Column( dt, yName ) << Get Values;
nAll = N Rows( yAll );
If( hasX,
	xAll = Column( dt, xName ) << Get Values,
	xAll = 1 :: nAll // row order as time index
);

keep = {};
For( i = 1, i <= nAll, i++,
	If( !Is Missing( yAll[i] ) & !Is Missing( xAll[i] ),
		Insert Into( keep, i )
	)
);
n = N Items( keep );
If( n < 4, Throw( "Need at least 4 non-missing observations." ) );

y = J( n, 1, . );
x = J( n, 1, . );
For( i = 1, i <= n, i++,
	y[i] = yAll[keep[i]];
	x[i] = xAll[keep[i]];
);

ord = Rank( x ); // indices that sort x ascending
y = y[ord];
x = x[ord];

// ---------------------------------------------------------------------
// STEP 3: Mann-Kendall S statistic and Sen's pairwise slopes
// ---------------------------------------------------------------------
nPairs = n * (n - 1) / 2;
slopes = J( nPairs, 1, . );
S = 0;
k = 0;
For( i = 1, i < n, i++,
	For( j = i + 1, j <= n, j++,
		S += Sign( y[j] - y[i] ); // +1 up, -1 down, 0 tie
		If( x[j] != x[i], // skip pairs with identical time stamps
			k++;
			slopes[k] = (y[j] - y[i]) / (x[j] - x[i]);
		);
	)
);
slopes = slopes[1 :: k]; // trim unused preallocated slots
nSlopes = k;

// ---------------------------------------------------------------------
// STEP 4: Tie-corrected variance of S
// ---------------------------------------------------------------------
ySorted = Sort Ascending( y );
tieTerm = 0;
runLen = 1;
For( i = 2, i <= n + 1, i++,
	If( i <= n & ySorted[i] == ySorted[i - 1],
		runLen += 1, // still inside a tied group
		If( runLen > 1, tieTerm += runLen * (runLen - 1) * (2 * runLen + 5) );
		runLen = 1;
	)
);
varS = (n * (n - 1) * (2 * n + 5) - tieTerm) / 18;

// ---------------------------------------------------------------------
// STEP 5: Z statistic (continuity corrected), p-value, Kendall's tau
// ---------------------------------------------------------------------
If( S > 0,
	Z = (S - 1) / Sqrt( varS ),
	S < 0,
	Z = (S + 1) / Sqrt( varS ),
	Z = 0
);
pValue = 2 * (1 - Normal Distribution( Abs( Z ) ));
tau = S / nPairs; // simple tau (not tau-b); equals tau-b when no ties
zCrit = Normal Quantile( 1 - alpha / 2 );

If( pValue < alpha,
	If( Z > 0, trendText = "Significant INCREASING trend", trendText = "Significant DECREASING trend" ),
	trendText = "No significant monotonic trend"
);

// ---------------------------------------------------------------------
// STEP 6: Sen's slope, intercept, and confidence interval
//   CI follows the Gilbert (1987) rank-based approach (normal approx).
// ---------------------------------------------------------------------
sortedSlopes = Sort Ascending( slopes );
senSlope = Median( slopes );
senIntercept = Median( y - senSlope * x );

cAlpha = zCrit * Sqrt( varS );
m1 = (nSlopes - cAlpha) / 2;
m2 = (nSlopes + cAlpha) / 2;
loIdx = Max( 1, Min( nSlopes, Round( m1 ) ) );
hiIdx = Max( 1, Min( nSlopes, Round( m2 + 1 ) ) );
senLower = sortedSlopes[loIdx];
senUpper = sortedSlopes[hiIdx];

// ---------------------------------------------------------------------
// STEP 7: Assemble labels/values for the test-statistic table + log
// ---------------------------------------------------------------------
ciPct = Round( 100 * (1 - alpha), 1 );
statLabels = {"Response", "Time / order variable", "n (non-missing)", "S", "Var(S) (tie-corrected)", "Z (continuity corrected)",
"p-value (two-sided)", "Kendall tau (S / n(n-1)/2)", "Alpha", "Sen's slope (per unit time)",
"Sen's slope lower " || Char( ciPct ) || "% CI", "Sen's slope upper " || Char( ciPct ) || "% CI", "Sen's intercept"};
statValues = {yName, xName, Char( n ), Char( S ), Format( varS, "Best", 8 ), Format( Z, "Fixed Dec", 4 ),
Format( pValue, "Best", 6 ), Format( tau, "Fixed Dec", 4 ), Char( alpha ), Format( senSlope, "Best", 6 ),
Format( senLower, "Best", 6 ), Format( senUpper, "Best", 6 ), Format( senIntercept, "Best", 6 )};

Write(
	"\!NMann-Kendall: ", yName, " vs ", xName,
	"\!N  n = ", n, ", S = ", S, ", Z = ", Round( Z, 4 ), ", p = ", Round( pValue, 6 ),
	"\!N  Sen's slope = ", senSlope, " (", ciPct, "% CI: ", senLower, " to ", senUpper, ")",
	"\!N  ", trendText, "\!N"
);

// ---------------------------------------------------------------------
// STEP 8: Build (hidden) plot data table: time, observed value, Sen fit
//   Graph Builder needs a data table; it is closed with the report.
// ---------------------------------------------------------------------
dtPlot = New Table( "Mann-Kendall Plot Data: " || yName,
	Invisible,
	New Column( "Time", Numeric, Continuous, Set Values( x ) ),
	New Column( "Value", Numeric, Continuous, Set Values( y ) ),
	New Column( "Sen Fit", Numeric, Continuous, Set Values( senIntercept + senSlope * x ) )
);

// Color the conclusion by outcome (red = trend detected, green = none)
concColor = If( pValue < alpha, "Red", "Green" );

// ---------------------------------------------------------------------
// STEP 9: Build the custom output report window (Graph Builder + stats)
// ---------------------------------------------------------------------
win = New Window( "Mann-Kendall Trend Test: " || yName,
	V List Box(
		Text Box( "Mann-Kendall Trend Test with Sen's Slope", << Set Font Size( 14 ), << Set Font Style( "Bold" ) ),
		Text Box( "Response: " || yName || "   |   Time/Order: " || xName || "   |   n = " || Char( n ) ),
		Spacer Box( Size( 0, 8 ) ),
		// ----- Graph Builder: data points with Sen's slope line -----
		Outline Box( "Trend Plot",
			dtPlot << Graph Builder(
				Size( 650, 400 ),
				Show Control Panel( 0 ),
				Variables( X( :Time ), Y( :Value ), Y( :Sen Fit, Position( 1 ) ) ),
				Elements( Points( X, Y( 1 ) ), Line( X, Y( 2 ) ) )
			)
		),
		// ----- Test statistics table -----
		Outline Box( "Test Statistics",
			Table Box(
				String Col Box( "Statistic", statLabels ),
				String Col Box( "Value", statValues )
			)
		),
		// ----- Conclusion -----
		Outline Box( "Conclusion",
			Text Box( trendText || " at alpha = " || Char( alpha ) || " (p = " || Format( pValue, "Best", 4 ) || ").",
				<< Set Font Style( "Bold" ), << Font Color( concColor ) ),
			Text Box( "Sen's slope estimates the median change in " || yName || " per unit of " || xName || "." )
		),
		// ----- Notes -----
		Outline Box( "Notes",
			Text Box(
				"H0: no monotonic trend. Z uses a continuity correction and the tie-corrected variance of S.\!N"
				|| "Assumes independent observations; positive serial correlation inflates false positives.\!N"
				|| "Normal approximation is weak for n < 10." ),
			<< Close( 1 )
		),
		// ----- Optional export of statistics to a data table -----
		Button Box( "Save Statistics to Data Table",
			New Table( "Mann-Kendall Results: " || yName,
				New Column( "Statistic", Character, Nominal, Set Values( statLabels ) ),
				New Column( "Value", Character, Nominal, Set Values( statValues ) )
			)
		)
	)
);

// Close the hidden plot data table when the report window is closed
win << On Close( Close( dtPlot, No Save ) );

MathStatChem_0-1791393460993.png

 

Recommended Articles