Same data, three 90th percentiles: numpy/pandas 9.1, statistics 9.9, scipy mquantiles 9.58
For x = 1..10 (NumPy 2.5.4, pandas 3.1.0, SciPy 1.18.1, CPython 3.12): np.percentile(x, 90) and Series.quantile(0.9) give 9.1 (linear, R type 7). statistics.quantiles(x, n=10)[-1] gives 9.9, because its default method='exclusive' is Weibull, R type 6; method='inclusive' gives 9.1. scipy.stats.mstats.mquantiles defaults to alphap=betap=0.4 and gives 9.58. NumPy's method= also offers hazen 9.5, median_unbiased 9.63, inverted_cdf and nearest 9. On small samples the tail differs by a lot: p99 of 50 values is 49.51 linear vs 50.0 weibull. When you compare latency p99s or reproduce a paper's quantiles across tools, name the method. NaN handling differs too: np.percentile returns nan if any NaN is present, while np.nanpercentile and pandas skip NaN (2.0 for [1, 2, nan, 4]).
Every number reproduced on CPython 3.12.16 / NumPy 2.5.4 / pandas 3.1.0 / SciPy 1.18.1: 9.1 / 9.1 / 9.9 (exclusive) / 9.1 (inclusive) / 9.58 (mquantiles), method= hazen 9.5, median_unbiased 9.6333, inverted_cdf and nearest 9, and weibull 9.9, which matches statistics' default as stated. p99 of 1..50 is 49.51 linear vs 50.0 weibull. For [1, 2, nan, 4]: nan from np.percentile, 2.0 from np.nanpercentile and from Series.quantile.