@@ -342,13 +342,22 @@ def _get_stats_internal(self, column: str) -> ColumnStats:
342342 "nulls" : col .null_count (),
343343 }
344344
345- # As of Sep 2025, pyarrow and ibis do not support quantiles
345+ # As of Oct 2025, pyarrow and ibis do not support quantiles
346346 # through narwhals
347- supports_quantiles = (
347+ supports_numeric_quantiles = (
348+ not frame .implementation .is_pyarrow ()
349+ and not frame .implementation .is_ibis ()
350+ )
351+ supports_temporal_quantiles = (
348352 not frame .implementation .is_pyarrow ()
349353 and not frame .implementation .is_ibis ()
350354 )
351355
356+ quantile_interpolation = "nearest"
357+ if frame .implementation .is_duckdb ():
358+ # As of Oct 2025, DuckDB does not support "nearest" interpolation
359+ quantile_interpolation = "linear"
360+
352361 if is_narwhals_string_type (dtype ):
353362 exprs ["unique" ] = col .n_unique ()
354363 elif dtype == nw .Boolean :
@@ -401,15 +410,25 @@ def _get_stats_internal(self, column: str) -> ColumnStats:
401410 "max" : col .max (),
402411 }
403412 )
404- if supports_quantiles :
413+ if supports_temporal_quantiles :
405414 exprs .update (
406415 {
407416 "mean" : col .mean (),
408- "median" : col .quantile (0.5 , interpolation = "nearest" ),
409- "p5" : col .quantile (0.05 , interpolation = "nearest" ),
410- "p25" : col .quantile (0.25 , interpolation = "nearest" ),
411- "p75" : col .quantile (0.75 , interpolation = "nearest" ),
412- "p95" : col .quantile (0.95 , interpolation = "nearest" ),
417+ "median" : col .quantile (
418+ 0.5 , interpolation = quantile_interpolation
419+ ),
420+ "p5" : col .quantile (
421+ 0.05 , interpolation = quantile_interpolation
422+ ),
423+ "p25" : col .quantile (
424+ 0.25 , interpolation = quantile_interpolation
425+ ),
426+ "p75" : col .quantile (
427+ 0.75 , interpolation = quantile_interpolation
428+ ),
429+ "p95" : col .quantile (
430+ 0.95 , interpolation = quantile_interpolation
431+ ),
413432 }
414433 )
415434 elif is_narwhals_integer_type (dtype ):
@@ -423,13 +442,21 @@ def _get_stats_internal(self, column: str) -> ColumnStats:
423442 "median" : col .median (),
424443 }
425444 )
426- if supports_quantiles :
445+ if supports_numeric_quantiles :
427446 exprs .update (
428447 {
429- "p5" : col .quantile (0.05 , interpolation = "nearest" ),
430- "p25" : col .quantile (0.25 , interpolation = "nearest" ),
431- "p75" : col .quantile (0.75 , interpolation = "nearest" ),
432- "p95" : col .quantile (0.95 , interpolation = "nearest" ),
448+ "p5" : col .quantile (
449+ 0.05 , interpolation = quantile_interpolation
450+ ),
451+ "p25" : col .quantile (
452+ 0.25 , interpolation = quantile_interpolation
453+ ),
454+ "p75" : col .quantile (
455+ 0.75 , interpolation = quantile_interpolation
456+ ),
457+ "p95" : col .quantile (
458+ 0.95 , interpolation = quantile_interpolation
459+ ),
433460 }
434461 )
435462 elif dtype .is_numeric ():
@@ -443,13 +470,21 @@ def _get_stats_internal(self, column: str) -> ColumnStats:
443470 "median" : col .median (),
444471 }
445472 )
446- if supports_quantiles :
473+ if supports_numeric_quantiles :
447474 exprs .update (
448475 {
449- "p5" : col .quantile (0.05 , interpolation = "nearest" ),
450- "p25" : col .quantile (0.25 , interpolation = "nearest" ),
451- "p75" : col .quantile (0.75 , interpolation = "nearest" ),
452- "p95" : col .quantile (0.95 , interpolation = "nearest" ),
476+ "p5" : col .quantile (
477+ 0.05 , interpolation = quantile_interpolation
478+ ),
479+ "p25" : col .quantile (
480+ 0.25 , interpolation = quantile_interpolation
481+ ),
482+ "p75" : col .quantile (
483+ 0.75 , interpolation = quantile_interpolation
484+ ),
485+ "p95" : col .quantile (
486+ 0.95 , interpolation = quantile_interpolation
487+ ),
453488 }
454489 )
455490
@@ -461,6 +496,10 @@ def _get_stats_internal(self, column: str) -> ColumnStats:
461496 if key in units :
462497 stats_dict [key ] = f"{ value } { units [key ]} "
463498
499+ # Maybe coerce null count to int
500+ if stats_dict ["nulls" ] is not None :
501+ stats_dict ["nulls" ] = int (stats_dict ["nulls" ])
502+
464503 return ColumnStats (** stats_dict )
465504
466505 def get_bin_values (self , column : str , num_bins : int ) -> list [BinValue ]:
0 commit comments