@@ -50,16 +50,16 @@ public static class DataFrameExtensionsStatistics
5050 /// </summary>
5151 /// <typeparam name="T">Numeric type</typeparam>
5252 /// <param name="column">Column to calculate median for</param>
53- /// <returns>Median value, or null if column is empty or all values are null</returns>
54- public static T ? Median < T > ( this PrimitiveDataFrameColumn < T > column )
53+ /// <returns>Median value as double , or null if column is empty or all values are null</returns>
54+ public static double ? Median < T > ( this PrimitiveDataFrameColumn < T > column )
5555 where T : unmanaged, INumber < T >
5656 {
5757 if ( column == null || column . Length == 0 )
5858 {
5959 return null ;
6060 }
6161
62- var values = column . Where ( v => v . HasValue ) . Select ( v => v ! . Value ) . OrderBy ( v => v ) . ToList ( ) ;
62+ var values = column . Where ( v => v . HasValue ) . Select ( v => Convert . ToDouble ( v ! . Value ) ) . OrderBy ( v => v ) . ToList ( ) ;
6363
6464 if ( values . Count == 0 )
6565 {
@@ -71,7 +71,7 @@ public static class DataFrameExtensionsStatistics
7171 if ( values . Count % 2 == 0 )
7272 {
7373 // Even number of elements - average the two middle values
74- return ( values [ middleIndex - 1 ] + values [ middleIndex ] ) / T . CreateChecked ( 2 ) ;
74+ return ( values [ middleIndex - 1 ] + values [ middleIndex ] ) / 2.0 ;
7575 }
7676 else
7777 {
@@ -95,7 +95,7 @@ public static class DataFrameExtensionsStatistics
9595 }
9696
9797 /// <summary>
98- /// Calculates the variance of a column
98+ /// Calculates the variance of a column using Welford's online algorithm for numerical stability
9999 /// </summary>
100100 /// <typeparam name="T">Numeric type</typeparam>
101101 /// <param name="column">Column to calculate variance for</param>
@@ -109,18 +109,32 @@ public static class DataFrameExtensionsStatistics
109109 return null ;
110110 }
111111
112- var values = column . Where ( v => v . HasValue ) . Select ( v => Convert . ToDouble ( v ! . Value ) ) . ToList ( ) ;
112+ // Single-pass variance calculation using Welford's algorithm
113+ var count = 0 ;
114+ var mean = 0.0 ;
115+ var m2 = 0.0 ;
113116
114- if ( values . Count < ( sample ? 2 : 1 ) )
117+ for ( var i = 0 ; i < column . Length ; i ++ )
115118 {
116- return null ;
119+ var value = column [ i ] ;
120+ if ( value . HasValue )
121+ {
122+ count ++ ;
123+ var doubleValue = Convert . ToDouble ( value . Value ) ;
124+ var delta = doubleValue - mean ;
125+ mean += delta / count ;
126+ var delta2 = doubleValue - mean ;
127+ m2 += delta * delta2 ;
128+ }
117129 }
118130
119- var mean = values . Average ( ) ;
120- var sumOfSquaredDifferences = values . Sum ( v => Math . Pow ( v - mean , 2 ) ) ;
121- var divisor = sample ? values . Count - 1 : values . Count ;
131+ if ( count < ( sample ? 2 : 1 ) )
132+ {
133+ return null ;
134+ }
122135
123- return sumOfSquaredDifferences / divisor ;
136+ var divisor = sample ? count - 1 : count ;
137+ return m2 / divisor ;
124138 }
125139
126140 /// <summary>
@@ -212,7 +226,7 @@ public static long Count<T>(this PrimitiveDataFrameColumn<T> column)
212226 /// <typeparam name="T">Numeric type</typeparam>
213227 /// <param name="column">Column to calculate statistics for</param>
214228 /// <returns>Tuple containing (count, mean, stddev, min, 25th percentile, median, 75th percentile, max)</returns>
215- public static ( long Count , T ? Mean , double ? StdDev , T ? Min , T ? Q25 , T ? Median , T ? Q75 , T ? Max ) Describe < T > ( this PrimitiveDataFrameColumn < T > column )
229+ public static ( long Count , T ? Mean , double ? StdDev , T ? Min , double ? Q25 , double ? Median , double ? Q75 , T ? Max ) Describe < T > ( this PrimitiveDataFrameColumn < T > column )
216230 where T : unmanaged, INumber < T >
217231 {
218232 var count = column . Count ( ) ;
@@ -233,16 +247,16 @@ public static (long Count, T? Mean, double? StdDev, T? Min, T? Q25, T? Median, T
233247 /// <typeparam name="T">Numeric type</typeparam>
234248 /// <param name="column">Column to calculate quantile for</param>
235249 /// <param name="quantile">Quantile to calculate (0.0 to 1.0, e.g., 0.25 for 25th percentile)</param>
236- /// <returns>Quantile value, or null if column is empty</returns>
237- public static T ? Quantile < T > ( this PrimitiveDataFrameColumn < T > column , double quantile )
250+ /// <returns>Quantile value as double , or null if column is empty</returns>
251+ public static double ? Quantile < T > ( this PrimitiveDataFrameColumn < T > column , double quantile )
238252 where T : unmanaged, INumber < T >
239253 {
240254 if ( column == null || column . Length == 0 || quantile < 0 || quantile > 1 )
241255 {
242256 return null ;
243257 }
244258
245- var values = column . Where ( v => v . HasValue ) . Select ( v => v ! . Value ) . OrderBy ( v => v ) . ToList ( ) ;
259+ var values = column . Where ( v => v . HasValue ) . Select ( v => Convert . ToDouble ( v ! . Value ) ) . OrderBy ( v => v ) . ToList ( ) ;
246260
247261 if ( values . Count == 0 )
248262 {
@@ -258,7 +272,7 @@ public static (long Count, T? Mean, double? StdDev, T? Min, T? Q25, T? Median, T
258272 return values [ lowerIndex ] ;
259273 }
260274
261- var weight = T . CreateChecked ( index - lowerIndex ) ;
275+ var weight = index - lowerIndex ;
262276 return values [ lowerIndex ] + weight * ( values [ upperIndex ] - values [ lowerIndex ] ) ;
263277 }
264278}
0 commit comments