Подход № 1: Pandas + NumPy (некоторые - нет)
Мы попытаемся сохранить его в pandas / NumPy, чтобы мы могли использовать методы dataframe или массива и ufuncs, в то время как векторизация на своем уровне. Это облегчает расширение функциональных возможностей, когда нужно решать сложные проблемы или генерировать статистику, как это, кажется, имеет место здесь.
Теперь, чтобы решить проблему, сохраняя ее близкой к pandas , будет генерировать промежуточные идентификаторы или теги, которые напоминают комбинированное отслеживание A
и B
на заданных бинах bins_A
и bins_B
соответственно. Для этого одним из способов было бы использовать searchsorted
для этих двух данных отдельно -
tagsA = np.searchsorted(bins_A,df.A)
tagsB = np.searchsorted(bins_B,df.B)
Теперь нас интересуют только случаи внутри границ, поэтому необходимо маскирование -
vm = (tagsB>0) & (tagsB<len(bins_B)) & (tagsA>0) & (tagsA<len(bins_A))
Давайте применим эту маску к исходному фрейму данных -
dfm = df.iloc[vm]
Добавьте теги для допустимых, которые будут представлять A_mins
и B_min
эквивалентов и, следовательно, будут отображаться в конечном выводе -
dfm['TA'] = bins_A[(tagsA-1)[vm]]
dfm['TB'] = bins_B[(tagsB-1)[vm]]
Итак, наш тегированный фрейм данных готов, что может составить describe-d
для получения общей статистики после группировки по этим двум тегам -
df_out = dfm.groupby(['TA','TB'])['x'].describe()
Пример прогона, чтобы прояснить ситуацию при сравнении с опубликованным решением -
In [46]: np.random.seed(0)
...: n = 100
...: df = pd.DataFrame(
...: {
...: "x": np.random.randn(n),
...: "A": np.random.randn(n)+5,
...: "B": np.random.randn(n)+10
...: }
...: )
In [47]: binned
Out[47]:
A_min A_max B_min B_max x_mean x_std x_count
0 3 4 8 9 0.400199 0.719007 5
1 3 4 9 10 -0.268252 0.914784 6
2 3 4 10 11 0.458746 1.499419 5
3 3 4 11 12 0.939782 0.055092 2
4 4 5 8 9 0.238318 1.173704 5
5 4 5 9 10 -0.263020 0.815974 8
6 4 5 10 11 -0.449831 0.682148 12
7 4 5 11 12 -0.273111 1.385483 2
8 5 6 8 9 -0.438074 NaN 1
9 5 6 9 10 -0.009721 1.401260 16
10 5 6 10 11 0.467934 1.221720 11
11 5 6 11 12 0.729922 0.789260 3
12 6 7 8 9 -0.977278 NaN 1
13 6 7 9 10 0.211842 0.825401 7
14 6 7 10 11 -0.097307 0.427639 5
15 6 7 11 12 0.915971 0.195841 2
In [48]: df_out
Out[48]:
count mean std ... 50% 75% max
TA TB ...
3 8 5.0 0.400199 0.719007 ... 0.302472 0.976639 1.178780
9 6.0 -0.268252 0.914784 ... -0.001510 0.401796 0.653619
10 5.0 0.458746 1.499419 ... 0.462782 1.867558 1.895889
11 2.0 0.939782 0.055092 ... 0.939782 0.959260 0.978738
4 8 5.0 0.238318 1.173704 ... -0.212740 0.154947 2.269755
9 8.0 -0.263020 0.815974 ... -0.365103 0.449313 0.950088
10 12.0 -0.449831 0.682148 ... -0.436773 -0.009697 0.761038
11 2.0 -0.273111 1.385483 ... -0.273111 0.216731 0.706573
5 8 1.0 -0.438074 NaN ... -0.438074 -0.438074 -0.438074
9 16.0 -0.009721 1.401260 ... 0.345020 1.284173 1.950775
10 11.0 0.467934 1.221720 ... 0.156349 1.471263 2.240893
11 3.0 0.729922 0.789260 ... 1.139401 1.184846 1.230291
6 8 1.0 -0.977278 NaN ... -0.977278 -0.977278 -0.977278
9 7.0 0.211842 0.825401 ... 0.121675 0.398750 1.764052
10 5.0 -0.097307 0.427639 ... -0.103219 0.144044 0.401989
11 2.0 0.915971 0.195841 ... 0.915971 0.985211 1.054452
Итак, как уже упоминалось ранее, у нас есть A_min
и B_min
в TA
и TB
, а соответствующие статистические данные фиксируются в других заголовках. Обратите внимание, что это будет мультииндексный фрейм данных. Если нам нужно захватить эквивалентные данные массива, просто выполните: df_out.loc[:,['count','mean','std']].values
для статистики, а np.vstack(df_out.loc[:,['count','mean','std']].index)
для интервала запуска бинов.
В качестве альтернативы, для захвата эквивалентных данных статистики без describe
, но с использованием методов dataframe, мы можем сделать что-то вроде этого -
dfmg = dfm.groupby(['TA','TB'])['x']
dfmg.size().unstack().values
dfmg.std().unstack().values
dfmg.mean().unstack().values
Альтернатива # 1: Использование pd.cut
Мы также можем использовать pd.cut
, как было предложено в вопросе, для замены searchsorted
на более компактный, так как вне границ обрабатываются автоматически, сохраняя базовые значения * 1079. * идея такая же -
df['TA'] = pd.cut(df['A'],bins=bins_A, labels=range(len(bins_A)-1))
df['TB'] = pd.cut(df['B'],bins=bins_B, labels=range(len(bins_B)-1))
df_out = df.groupby(['TA','TB'])['x'].describe()
Итак, это дает нам статистику. Для A_min
и B_min
эквивалентов просто используйте уровни индекса -
A_min = bins_A[df_out.index.get_level_values(0)]
B_min = bins_B[df_out.index.get_level_values(1)]
или используйте какой-нибудь метод сетки -
mA,mB = np.meshgrid(bins_A[:-1],bins_B[:-1])
A_min,B_min = mA.ravel('F'),mB.ravel('F')
Подход № 2: С bincount
Мы можем использовать np.bincount
, чтобы получить все эти три значения статистики metri c, включая стандартное отклонение, снова в векторизации -
lA,lB = len(bins_A),len(bins_B)
n = lA+1
x,A,B = df.x.values,df.A.values,df.B.values
tagsA = np.searchsorted(bins_A,A)
tagsB = np.searchsorted(bins_B,B)
t = tagsB*n + tagsA
L = n*lB
countT = np.bincount(t, minlength=L)
countT_x = np.bincount(t,x, minlength=L)
avg_all = countT_x/countT
count = countT.reshape(-1,n)[1:,1:-1].ravel('F')
avg = avg_all.reshape(-1,n)[1:,1:-1].ravel('F')
# Using numpy std definition for ddof case
ddof = 1.0 # default one for pandas std
grp_diffs = (x-avg_all[t])**2
std_all = np.sqrt(np.bincount(t,grp_diffs, minlength=L)/(countT-ddof))
stds = std_all.reshape(-1,n)[1:,1:-1].ravel('F')
Подход № 3: С sorting
для плечо reduceat
методов -
x,A,B = df.x.values,df.A.values,df.B.values
vm = (A>bins_A[0]) & (A<bins_A[-1]) & (B>bins_B[0]) & (B<bins_B[-1])
xm = x[vm]
tagsA = np.searchsorted(bins_A,A)
tagsB = np.searchsorted(bins_B,B)
tagsAB = tagsB*(tagsA.max()+1) + tagsA
tagsABm = tagsAB[vm]
sidx = tagsABm.argsort()
tagsAB_s = tagsABm[sidx]
xms = xm[sidx]
cut_idx = np.flatnonzero(np.r_[True,tagsAB_s[:-1]!=tagsAB_s[1:],True])
N = (len(bins_A)-1)*(len(bins_B)-1)
count = np.diff(cut_idx)
avg = np.add.reduceat(xms,cut_idx[:-1])/count
stds = np.empty(N)
for ii,(s0,s1) in enumerate(zip(cut_idx[:-1],cut_idx[1:])):
stds[ii] = np.std(xms[s0:s1], ddof=1)
Чтобы получить тот же или аналогичный формат, что и вывод в стиле pandas в виде информационного кадра, нам нужно изменить форму. Следовательно, это будет avg.reshape(-1,len(bins_A)-1).T
и т. Д.