Как рассчитать статистику Колмогорова-Смирнова между двумя взвешенными выборками
Допустим, у нас есть два образца data1
а также data2
с их соответствующими весами weight1
а также weight2
и что мы хотим вычислить статистику Колмогорова-Смирнова между двумя взвешенными выборками.
То, как мы это делаем в Python, выглядит следующим образом:
def ks_w(data1,data2,wei1,wei2):
ix1=np.argsort(data1)
ix2=np.argsort(data2)
wei1=wei1[ix1]
wei2=wei2[ix2]
data1=data1[ix1]
data2=data2[ix2]
d=0.
fn1=0.
fn2=0.
j1=0
j2=0
j1w=0.
j2w=0.
while(j1<len(data1))&(j2<len(data2)):
d1=data1[j1]
d2=data2[j2]
w1=wei1[j1]
w2=wei2[j2]
if d1<=d2:
j1+=1
j1w+=w1
fn1=(j1w)/sum(wei1)
if d2<=d1:
j2+=1
j2w+=w2
fn2=(j2w)/sum(wei2)
if abs(fn2-fn1)>d:
d=abs(fn2-fn1)
return d
где мы просто модифицируем для нашей цели классическую статистику KS с двумя выборками, как это реализовано в изданиях Press, Flannery, Teukolsky, Vetterling - Numeric Recipes in C - Cambridge University Press - 1992 - pag.626.
Наши вопросы:
- Кто-нибудь знает какой-либо другой способ сделать это?
- есть ли библиотека в Python/R/*, которая выполняет это?
- как насчет теста? Существует ли она или мы должны использовать процедуру перестановки для оценки статистики?
3 ответа
Изучая scipy.stats.ks_2samp
В коде нам удалось найти более эффективное решение Python. Мы делимся ниже, если кому-то интересно:
from __future__ import division # (for python 2/3 support)
import numpy as np
def ks_w2(data1, data2, wei1, wei2):
ix1 = np.argsort(data1)
ix2 = np.argsort(data2)
data1 = data1[ix1]
data2 = data2[ix2]
wei1 = wei1[ix1]
wei2 = wei2[ix2]
data = np.concatenate([data1, data2])
cwei1 = np.hstack([0, np.cumsum(wei1)/sum(wei1)])
cwei2 = np.hstack([0, np.cumsum(wei2)/sum(wei2)])
cdf1we = cwei1[[np.searchsorted(data1, data, side='right')]]
cdf2we = cwei2[[np.searchsorted(data2, data, side='right')]]
return np.max(np.abs(cdf1we - cdf2we))
Для оценки производительности мы провели следующий тест:
ds1 = random.rand(10000)
ds2 = random.randn(40000) + .2
we1 = random.rand(10000) + 1.
we2 = random.rand(40000) + 1.
ks_w2(ds1, ds2, we1, we2)
потребовалось 11,7мс на нашей машине, а ks_w(ds1, ds2, we1, we2)
заняло 1,43 с
Чтобы добавить к ответу Луки Джокулла, если вы хотите также вернуть p-значение (аналогично невзвешенному
scipy.stats.ks_2samp
функция), предлагаемый
ks_w2()
функция может быть изменена следующим образом:
from scipy.stats import distributions
def ks_weighted(data1, data2, wei1, wei2, alternative='two-sided'):
ix1 = np.argsort(data1)
ix2 = np.argsort(data2)
data1 = data1[ix1]
data2 = data2[ix2]
wei1 = wei1[ix1]
wei2 = wei2[ix2]
data = np.concatenate([data1, data2])
cwei1 = np.hstack([0, np.cumsum(wei1)/sum(wei1)])
cwei2 = np.hstack([0, np.cumsum(wei2)/sum(wei2)])
cdf1we = cwei1[np.searchsorted(data1, data, side='right')]
cdf2we = cwei2[np.searchsorted(data2, data, side='right')]
d = np.max(np.abs(cdf1we - cdf2we))
# calculate p-value
n1 = data1.shape[0]
n2 = data2.shape[0]
m, n = sorted([float(n1), float(n2)], reverse=True)
en = m * n / (m + n)
if alternative == 'two-sided':
prob = distributions.kstwo.sf(d, np.round(en))
else:
z = np.sqrt(en) * d
# Use Hodges' suggested approximation Eqn 5.3
# Requires m to be the larger of (n1, n2)
expt = -2 * z**2 - 2 * z * (m + 2*n)/np.sqrt(m*n*(m+n))/3.0
prob = np.exp(expt)
return d, prob
Это асимптотический метод, который использует исходная невзвешенная функция scipy .
Это R-версия взвешенной статистики KS с двумя хвостами, предложенная Моноханом в "Численных методах статистики", стр. 334 в 1Е и стр. 358 в 2E.
ks_weighted <- function(vector_1,vector_2,weights_1,weights_2){
F_vec_1 <- ewcdf(vector_1, weights = weights_1, normalise=FALSE)
F_vec_2 <- ewcdf(vector_2, weights = weights_2, normalise=FALSE)
xw <- c(vector_1,vector_2)
d <- max(abs(F_vec_1(xw) - F_vec_2(xw)))
## P-VALUE with NORMAL SAMPLE
# n_vector_1 <- length(vector_1)
# n_vector_2<- length(vector_2)
# n <- n_vector_1 * n_vector_2/(n_vector_1 + n_vector_2)
# P-VALUE EFFECTIVE SAMPLE SIZE as suggested by Monahan
n_vector_1 <- sum(weights_1)^2/sum(weights_1^2)
n_vector_2 <- sum(weights_2)^2/sum(weights_2^2)
n <- n_vector_1 * n_vector_2/(n_vector_1 + n_vector_2)
pkstwo <- function(x, tol = 1e-06) {
if (is.numeric(x))
x <- as.double(x)
else stop("argument 'x' must be numeric")
p <- rep(0, length(x))
p[is.na(x)] <- NA
IND <- which(!is.na(x) & (x > 0))
if (length(IND))
p[IND] <- .Call(stats:::C_pKS2, p = x[IND], tol)
p
}
pval <- 1 - pkstwo(sqrt(n) * d)
out <- c(KS_Stat=d, P_value=pval)
return(out)
}