Pandas Dataframe - 在多列上分箱并在另一列上获取统计信息
Pandas Dataframe - Bin on multiple columns & get statistics on another column
问题
我有一个目标变量 x
和一些附加变量 A
和 B
。当满足 A
和 B
的某些条件时,我想计算 x
的平均值(和其他统计数据)。一个真实世界的例子是当太阳辐射 (A
) 和风速 (B
) 落入特定预置时,通过一系列测量计算平均气温 (x
)定义的 bin 范围。
可能的解决方案
我已经能够通过循环完成此操作(参见下面的示例),但我了解到我应该避免在数据帧上循环。从我对这个网站的研究来看,我觉得可能有一个使用 pd.cut
或 np.select
的更优雅/矢量化的解决方案,但坦率地说,我不知道该怎么做。
例子
生成示例数据
import pandas as pd
import numpy as np
n = 100
df = pd.DataFrame(
{
"x": np.random.randn(n),
"A": np.random.randn(n)+5,
"B": np.random.randn(n)+10
}
)
df.head()
输出:
x A B
0 -0.585313 6.038620 9.909762
1 0.412323 3.991826 8.836848
2 0.211713 5.019520 9.667349
3 0.710699 5.353677 9.757903
4 0.681418 4.452754 10.647738
计算 bin 平均值
# define bin ranges
bins_A = np.arange(3, 8)
bins_B = np.arange(8, 13)
# prepare output lists
A_mins= []
A_maxs= []
B_mins= []
B_maxs= []
x_means= []
x_stds= []
x_counts= []
# loop over bins
for i_A in range(0, len(bins_A)-1):
A_min = bins_A[i_A]
A_max = bins_A[i_A+1]
for i_B in range(0, len(bins_B)-1):
B_min = bins_B[i_B]
B_max = bins_B[i_B+1]
# binning conditions for current step
conditions = np.logical_and.reduce(
[
df["A"] > A_min,
df["A"] < A_max,
df["B"] > B_min,
df["B"] < B_max,
]
)
# calculate statistics for x and store values in lists
x_values = df.loc[conditions, "x"]
x_means.append(x_values.mean())
x_stds.append(x_values.std())
x_counts.append(x_values.count())
A_mins.append(A_min)
A_maxs.append(A_max)
B_mins.append(B_min)
B_maxs.append(B_max)
将结果存储在新的数据框中
binned = pd.DataFrame(
data={
"A_min": A_mins,
"A_max": A_maxs,
"B_min": B_mins,
"B_max": B_maxs,
"x_mean": x_means,
"x_std": x_stds,
"x_count": x_counts
}
)
binned.head()
输出:
A_min A_max B_min B_max x_mean x_std x_count
0 3 4 8 9 0.971624 0.790972 2
1 3 4 9 10 0.302795 0.380102 3
2 3 4 10 11 0.447398 1.787659 5
3 3 4 11 12 0.462149 1.195844 2
4 4 5 8 9 0.379431 0.983965 4
如果您关心的是性能,如果您使用numba
,您可以对for循环稍作改动
这里有一个函数可以进行计算。关键是 calculate
使用 numba 所以它真的很快。其余仅用于创建 pandas 数据框:
from numba import njit
def calc_numba(df, bins_A, bins_B):
""" wrapper for the timeit. It only creates a dataframe """
@njit
def calculate(A, B, x, bins_A, bins_B):
size = (len(bins_A) - 1)*(len(bins_B) - 1)
out = np.empty((size, 7))
index = 0
for i_A, A_min in enumerate(bins_A[:-1]):
A_max = bins_A[i_A + 1]
for i_B, B_min in enumerate(bins_B[:-1]):
B_max = bins_B[i_B + 1]
mfilter = (A_min < A)*(A < A_max)*(B_min < B)*(B < B_max)
x_values = x[mfilter]
out[index, :] = [
A_min,
A_max,
B_min,
B_max,
x_values.mean(),
x_values.std(),
len(x_values)
]
index += 1
return out
columns = ["A_min", "A_max", "B_min", "B_max", "mean", "std", "count"]
out = calculate(df["A"].values, df["B"].values, df["x"].values, bins_A, bins_B)
return pd.DataFrame(out, columns=columns)
性能测试
使用 n = 1_000_000
和相同的 bins_A
和 bins_B
我们得到:
%timeit code_question(df, bins_A, bins_B)
15.7 s ± 428 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit calc_numba(df, bins_A, bins_B)
507 ms ± 12.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
It is around 30 faster than the code from the question
由于 pandas
内置方法使用类似的增强功能,因此很难超越 numba 的性能。
方法 #1:Pandas + NumPy(部分 none)
我们将尽量保持 pandas/NumPy 以便我们可以利用数据框方法或数组方法和 ufunc,同时在它们的级别对其进行矢量化。这使得在要解决复杂问题或要生成统计数据时更容易扩展功能,就像这里的情况一样。
现在,要解决问题同时保持接近 pandas,将生成类似于 A
和 B
的组合跟踪的中间 ID 或标签bins bins_A
和 bins_B
分别。为此,一种方法是分别对这两个数据使用 searchsorted
-
tagsA = np.searchsorted(bins_A,df.A)
tagsB = np.searchsorted(bins_B,df.B)
现在,我们只对 within-the-bounds 个案例感兴趣,因此需要屏蔽 -
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
因此,如前所述,我们在 TA
和 TB
中有我们的 A_min
和 B_min
,而其他 headers 中捕获了相关统计信息].请注意,这将是一个 multi-index 数据框。如果我们需要捕获等效的数组数据,只需对统计数据执行 df_out.loc[:,['count','mean','std']].values
,而对 bin interval-starts 执行 np.vstack(df_out.loc[:,['count','mean','std']].index)
。
或者,要在没有 describe
的情况下捕获等效的统计数据,但使用数据框方法,我们可以做这样的事情 -
dfmg = dfm.groupby(['TA','TB'])['x']
dfmg.size().unstack().values
dfmg.std().unstack().values
dfmg.mean().unstack().values
备选方案 #1:使用 pd.cut
我们也可以使用问题中建议的 pd.cut
来替换 searchsorted
以获得更紧凑的 out-of-bounds ,因为 out-of-bounds 是自动处理的,保持基本思想相同 -
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
获取所有这三个统计指标值,包括 standard-deviation,同样以矢量化方式 -
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
等等。
这是一个仅使用 Numpy 和 pandas 的简短解决方案。这当然不是最有效的方式,但我想是最简单易懂的方式。
import pandas as pd
import numpy as np
n = 20
df = pd.DataFrame(
{
"x": np.random.randn(n),
"A": np.random.randn(n)+5,
"B": np.random.randn(n)+10
}
)
# define bin ranges
bins_A = np.arange(3, 8)
bins_B = np.arange(8, 13)
直到这里我使用你的例子。
然后我使用 numpy
引入较低和较高的 Bin 边缘
A_mins=bins_A[:-1]
A_maxs=bins_A[1:]
B_mins=bins_B[:-1]
B_maxs=bins_B[1:]
以某种方式将其组合在一起,实际上您正在使用那些嵌套循环,我将自己限制在 numpy 中,在那里我仍然可以准确地保持结构,您可以使用嵌套循环获得该结构。
A_mins_list=np.repeat(A_mins,len(B_mins))
A_maxs_list=np.repeat(A_maxs,len(B_mins))
B_mins_list=np.tile(B_mins,len(A_mins))
B_maxs_list=np.tile(B_maxs,len(A_mins))
新数据帧已使用 bin 信息进行初始化。
newdf=pd.DataFrame(np.array([A_mins_list,A_maxs_list,B_mins_list,B_maxs_list]).T,columns=['Amin','Amax','Bmin','Bmax'])
xvalues 列是这里最邪恶的列,因为我必须将它变成一个 numpy 数组以适应数据帧。这个子数组是一个 numpy 数组,并且必须进一步被视为一个数组。请记住这一点,因为某些 pandas 功能可能无法正常工作;在某些情况下它必须是一个 numpy 函数。
newdf['xvalues']=newdf.apply(lambda row:np.array(df.x[(row.Amin<df.A) & (row.Amax>df.A) & (row.Bmin<df.B) & (row.Bmax>df.B)]),axis=1)
此外,您可以使用 lambda 函数做任何您想做的事情。正如我所说,这可能不是最有效的方法,但代码有点清晰,只要您不需要数百万条目的数据帧所需的最高性能,此代码很容易扩展
newdf['xmeans']=newdf.apply(lambda row: row.xvalues.mean(),axis=1)
newdf['stds']=newdf.apply(lambda row: row.xvalues.std(),axis=1)
newdf['xcounts']=newdf.apply(lambda row: row.xvalues.size,axis=1)
或您可能喜欢的任何内容。
使用 cython,通过避免 lambda-way 可以显着提高性能,但我不习惯 cython,所以我宁愿把它留给专家......
另外请注意,如果您试图对一个空数组或只有一个值的标准差求平均值,可能会出现一些警告。如果需要,可以使用警告包抑制这些。
问题
我有一个目标变量 x
和一些附加变量 A
和 B
。当满足 A
和 B
的某些条件时,我想计算 x
的平均值(和其他统计数据)。一个真实世界的例子是当太阳辐射 (A
) 和风速 (B
) 落入特定预置时,通过一系列测量计算平均气温 (x
)定义的 bin 范围。
可能的解决方案
我已经能够通过循环完成此操作(参见下面的示例),但我了解到我应该避免在数据帧上循环。从我对这个网站的研究来看,我觉得可能有一个使用 pd.cut
或 np.select
的更优雅/矢量化的解决方案,但坦率地说,我不知道该怎么做。
例子
生成示例数据
import pandas as pd
import numpy as np
n = 100
df = pd.DataFrame(
{
"x": np.random.randn(n),
"A": np.random.randn(n)+5,
"B": np.random.randn(n)+10
}
)
df.head()
输出:
x A B
0 -0.585313 6.038620 9.909762
1 0.412323 3.991826 8.836848
2 0.211713 5.019520 9.667349
3 0.710699 5.353677 9.757903
4 0.681418 4.452754 10.647738
计算 bin 平均值
# define bin ranges
bins_A = np.arange(3, 8)
bins_B = np.arange(8, 13)
# prepare output lists
A_mins= []
A_maxs= []
B_mins= []
B_maxs= []
x_means= []
x_stds= []
x_counts= []
# loop over bins
for i_A in range(0, len(bins_A)-1):
A_min = bins_A[i_A]
A_max = bins_A[i_A+1]
for i_B in range(0, len(bins_B)-1):
B_min = bins_B[i_B]
B_max = bins_B[i_B+1]
# binning conditions for current step
conditions = np.logical_and.reduce(
[
df["A"] > A_min,
df["A"] < A_max,
df["B"] > B_min,
df["B"] < B_max,
]
)
# calculate statistics for x and store values in lists
x_values = df.loc[conditions, "x"]
x_means.append(x_values.mean())
x_stds.append(x_values.std())
x_counts.append(x_values.count())
A_mins.append(A_min)
A_maxs.append(A_max)
B_mins.append(B_min)
B_maxs.append(B_max)
将结果存储在新的数据框中
binned = pd.DataFrame(
data={
"A_min": A_mins,
"A_max": A_maxs,
"B_min": B_mins,
"B_max": B_maxs,
"x_mean": x_means,
"x_std": x_stds,
"x_count": x_counts
}
)
binned.head()
输出:
A_min A_max B_min B_max x_mean x_std x_count
0 3 4 8 9 0.971624 0.790972 2
1 3 4 9 10 0.302795 0.380102 3
2 3 4 10 11 0.447398 1.787659 5
3 3 4 11 12 0.462149 1.195844 2
4 4 5 8 9 0.379431 0.983965 4
如果您关心的是性能,如果您使用numba
,您可以对for循环稍作改动这里有一个函数可以进行计算。关键是 calculate
使用 numba 所以它真的很快。其余仅用于创建 pandas 数据框:
from numba import njit
def calc_numba(df, bins_A, bins_B):
""" wrapper for the timeit. It only creates a dataframe """
@njit
def calculate(A, B, x, bins_A, bins_B):
size = (len(bins_A) - 1)*(len(bins_B) - 1)
out = np.empty((size, 7))
index = 0
for i_A, A_min in enumerate(bins_A[:-1]):
A_max = bins_A[i_A + 1]
for i_B, B_min in enumerate(bins_B[:-1]):
B_max = bins_B[i_B + 1]
mfilter = (A_min < A)*(A < A_max)*(B_min < B)*(B < B_max)
x_values = x[mfilter]
out[index, :] = [
A_min,
A_max,
B_min,
B_max,
x_values.mean(),
x_values.std(),
len(x_values)
]
index += 1
return out
columns = ["A_min", "A_max", "B_min", "B_max", "mean", "std", "count"]
out = calculate(df["A"].values, df["B"].values, df["x"].values, bins_A, bins_B)
return pd.DataFrame(out, columns=columns)
性能测试
使用 n = 1_000_000
和相同的 bins_A
和 bins_B
我们得到:
%timeit code_question(df, bins_A, bins_B)
15.7 s ± 428 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit calc_numba(df, bins_A, bins_B)
507 ms ± 12.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
It is around 30 faster than the code from the question
由于 pandas
内置方法使用类似的增强功能,因此很难超越 numba 的性能。
方法 #1:Pandas + NumPy(部分 none)
我们将尽量保持 pandas/NumPy 以便我们可以利用数据框方法或数组方法和 ufunc,同时在它们的级别对其进行矢量化。这使得在要解决复杂问题或要生成统计数据时更容易扩展功能,就像这里的情况一样。
现在,要解决问题同时保持接近 pandas,将生成类似于 A
和 B
的组合跟踪的中间 ID 或标签bins bins_A
和 bins_B
分别。为此,一种方法是分别对这两个数据使用 searchsorted
-
tagsA = np.searchsorted(bins_A,df.A)
tagsB = np.searchsorted(bins_B,df.B)
现在,我们只对 within-the-bounds 个案例感兴趣,因此需要屏蔽 -
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
因此,如前所述,我们在 TA
和 TB
中有我们的 A_min
和 B_min
,而其他 headers 中捕获了相关统计信息].请注意,这将是一个 multi-index 数据框。如果我们需要捕获等效的数组数据,只需对统计数据执行 df_out.loc[:,['count','mean','std']].values
,而对 bin interval-starts 执行 np.vstack(df_out.loc[:,['count','mean','std']].index)
。
或者,要在没有 describe
的情况下捕获等效的统计数据,但使用数据框方法,我们可以做这样的事情 -
dfmg = dfm.groupby(['TA','TB'])['x']
dfmg.size().unstack().values
dfmg.std().unstack().values
dfmg.mean().unstack().values
备选方案 #1:使用 pd.cut
我们也可以使用问题中建议的 pd.cut
来替换 searchsorted
以获得更紧凑的 out-of-bounds ,因为 out-of-bounds 是自动处理的,保持基本思想相同 -
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
获取所有这三个统计指标值,包括 standard-deviation,同样以矢量化方式 -
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
等等。
这是一个仅使用 Numpy 和 pandas 的简短解决方案。这当然不是最有效的方式,但我想是最简单易懂的方式。
import pandas as pd
import numpy as np
n = 20
df = pd.DataFrame(
{
"x": np.random.randn(n),
"A": np.random.randn(n)+5,
"B": np.random.randn(n)+10
}
)
# define bin ranges
bins_A = np.arange(3, 8)
bins_B = np.arange(8, 13)
直到这里我使用你的例子。 然后我使用 numpy
引入较低和较高的 Bin 边缘A_mins=bins_A[:-1]
A_maxs=bins_A[1:]
B_mins=bins_B[:-1]
B_maxs=bins_B[1:]
以某种方式将其组合在一起,实际上您正在使用那些嵌套循环,我将自己限制在 numpy 中,在那里我仍然可以准确地保持结构,您可以使用嵌套循环获得该结构。
A_mins_list=np.repeat(A_mins,len(B_mins))
A_maxs_list=np.repeat(A_maxs,len(B_mins))
B_mins_list=np.tile(B_mins,len(A_mins))
B_maxs_list=np.tile(B_maxs,len(A_mins))
新数据帧已使用 bin 信息进行初始化。
newdf=pd.DataFrame(np.array([A_mins_list,A_maxs_list,B_mins_list,B_maxs_list]).T,columns=['Amin','Amax','Bmin','Bmax'])
xvalues 列是这里最邪恶的列,因为我必须将它变成一个 numpy 数组以适应数据帧。这个子数组是一个 numpy 数组,并且必须进一步被视为一个数组。请记住这一点,因为某些 pandas 功能可能无法正常工作;在某些情况下它必须是一个 numpy 函数。
newdf['xvalues']=newdf.apply(lambda row:np.array(df.x[(row.Amin<df.A) & (row.Amax>df.A) & (row.Bmin<df.B) & (row.Bmax>df.B)]),axis=1)
此外,您可以使用 lambda 函数做任何您想做的事情。正如我所说,这可能不是最有效的方法,但代码有点清晰,只要您不需要数百万条目的数据帧所需的最高性能,此代码很容易扩展
newdf['xmeans']=newdf.apply(lambda row: row.xvalues.mean(),axis=1)
newdf['stds']=newdf.apply(lambda row: row.xvalues.std(),axis=1)
newdf['xcounts']=newdf.apply(lambda row: row.xvalues.size,axis=1)
或您可能喜欢的任何内容。
使用 cython,通过避免 lambda-way 可以显着提高性能,但我不习惯 cython,所以我宁愿把它留给专家......
另外请注意,如果您试图对一个空数组或只有一个值的标准差求平均值,可能会出现一些警告。如果需要,可以使用警告包抑制这些。