设计在 pandas df 中的分层样本
Stratified sample with design in pandas df
我有一个 df,其中包含表示层 (strat) 的列。我想遍历这些层并将行拉出到新的 df,df_sample
。如果案例很少,我想拉出一个层中的所有行。
我已经尝试了下面的方法,并且有效。但是我想知道是否有更好的解决方案来解决这个问题。也许 pd.concat
很慢,例如,当我以后使用真正大得多的数据时。
df=pd.DataFrame({'ID': range(0,120),
'strat': ['A', 'B', 'B', 'A', 'B', 'A', 'D', 'A', 'B', 'C',
'A', 'D', 'A', 'A', 'A', 'D', 'F', 'D', 'F', 'C',
'B', 'A', 'A', 'C', 'A', 'A', 'B', 'D', 'B', 'C',
'C', 'A', 'C', 'A', 'C', 'A', 'D', 'C', 'C', 'A',
'B', 'F', 'F', 'C', 'B', 'D', 'A', 'A', 'B', 'B',
'A', 'C', 'A', 'A', 'F', 'A', 'A', 'B', 'A', 'D',
'C', 'B', 'B', 'A', 'B', 'C', 'B', 'A', 'D', 'B',
'B', 'A', 'A', 'C', 'D', 'F', 'F', 'A', 'B', 'C',
'F', 'B', 'D', 'A', 'A', 'F', 'B', 'D', 'B', 'A',
'F', 'D', 'A', 'A', 'C', 'B', 'B', 'C', 'C', 'B',
'F', 'A', 'A', 'B', 'B', 'B', 'F', 'A', 'B', 'C',
'A', 'A', 'A', 'B', 'B', 'A', 'A', 'A', 'B', 'B']})
df_sample=pd.DataFrame()
for i in df.strat.unique():
temp=df[df['strat']==i]
if len(temp) < 21:
strat=temp.sample(len(temp))
elif len(temp) > 20:
strat = temp.sample(frac=0.5)
df_sample=pd.concat([df_sample, strat])
您可以groupby
“strat”并计算每个“strat”中的条目数,然后找出少于21个条目的strats并打乱它们。然后取剩余的 strats(那些条目超过 20 的)并对其中的 50% 进行抽样。最后连接两个DataFrame:
msk1 = df.groupby('strat')['strat'].count() < 21
less_than_21 = msk1.index[msk1]
msk2 = df['strat'].isin(less_than_21)
out = pd.concat((df[~msk2].groupby('strat').sample(frac=0.5), df[msk2].sample(msk2.sum())))
输出:
ID strat
110 110 A
72 72 A
46 46 A
31 31 A
92 92 A
.. ... ...
18 18 F
9 9 C
23 23 C
42 42 F
82 82 D
[82 rows x 2 columns]
为所有具有计数的组创建掩码,然后分别处理每个组:
m = df.groupby('strat')['strat'].transform('size').lt(21)
df = pd.concat((df[~m].groupby('strat').sample(frac=0.5),
df[m].sample(frac=1)),
ignore_index=True)
print (df)
ID strat
0 71 A
1 31 A
2 72 A
3 39 A
4 83 A
.. .. ...
77 37 C
78 85 F
79 19 C
80 34 C
81 73 C
[82 rows x 2 columns]
备选方案:
m = df['strat'].map(df['strat'].value_counts()).lt(21)
df = pd.concat((df[~m].groupby('strat').sample(frac=0.5), df[m].sample(frac=1)))
其他解决方案可能会更快。这是另一个,以防 readability/maintainability 更重要。
def sample_stratum(stratum):
nrows = stratum.shape[0]
if nrows < 21:
output = stratum.sample(nrows)
else:
output = stratum.sample(frac=0.5)
return output
# Index may be retained if needed
sampled_df = df.groupby(by=['strat']).apply(sample_stratum).reset_index(drop=True)
# ID strat
# 0 12 A
# 1 7 A
# 2 50 A
# 3 58 A
# 4 0 A
# .. .. ...
# 77 41 F
# 78 42 F
# 79 16 F
# 80 76 F
# 81 90 F
# [82 rows x 2 columns]
我有一个 df,其中包含表示层 (strat) 的列。我想遍历这些层并将行拉出到新的 df,df_sample
。如果案例很少,我想拉出一个层中的所有行。
我已经尝试了下面的方法,并且有效。但是我想知道是否有更好的解决方案来解决这个问题。也许 pd.concat
很慢,例如,当我以后使用真正大得多的数据时。
df=pd.DataFrame({'ID': range(0,120),
'strat': ['A', 'B', 'B', 'A', 'B', 'A', 'D', 'A', 'B', 'C',
'A', 'D', 'A', 'A', 'A', 'D', 'F', 'D', 'F', 'C',
'B', 'A', 'A', 'C', 'A', 'A', 'B', 'D', 'B', 'C',
'C', 'A', 'C', 'A', 'C', 'A', 'D', 'C', 'C', 'A',
'B', 'F', 'F', 'C', 'B', 'D', 'A', 'A', 'B', 'B',
'A', 'C', 'A', 'A', 'F', 'A', 'A', 'B', 'A', 'D',
'C', 'B', 'B', 'A', 'B', 'C', 'B', 'A', 'D', 'B',
'B', 'A', 'A', 'C', 'D', 'F', 'F', 'A', 'B', 'C',
'F', 'B', 'D', 'A', 'A', 'F', 'B', 'D', 'B', 'A',
'F', 'D', 'A', 'A', 'C', 'B', 'B', 'C', 'C', 'B',
'F', 'A', 'A', 'B', 'B', 'B', 'F', 'A', 'B', 'C',
'A', 'A', 'A', 'B', 'B', 'A', 'A', 'A', 'B', 'B']})
df_sample=pd.DataFrame()
for i in df.strat.unique():
temp=df[df['strat']==i]
if len(temp) < 21:
strat=temp.sample(len(temp))
elif len(temp) > 20:
strat = temp.sample(frac=0.5)
df_sample=pd.concat([df_sample, strat])
您可以groupby
“strat”并计算每个“strat”中的条目数,然后找出少于21个条目的strats并打乱它们。然后取剩余的 strats(那些条目超过 20 的)并对其中的 50% 进行抽样。最后连接两个DataFrame:
msk1 = df.groupby('strat')['strat'].count() < 21
less_than_21 = msk1.index[msk1]
msk2 = df['strat'].isin(less_than_21)
out = pd.concat((df[~msk2].groupby('strat').sample(frac=0.5), df[msk2].sample(msk2.sum())))
输出:
ID strat
110 110 A
72 72 A
46 46 A
31 31 A
92 92 A
.. ... ...
18 18 F
9 9 C
23 23 C
42 42 F
82 82 D
[82 rows x 2 columns]
为所有具有计数的组创建掩码,然后分别处理每个组:
m = df.groupby('strat')['strat'].transform('size').lt(21)
df = pd.concat((df[~m].groupby('strat').sample(frac=0.5),
df[m].sample(frac=1)),
ignore_index=True)
print (df)
ID strat
0 71 A
1 31 A
2 72 A
3 39 A
4 83 A
.. .. ...
77 37 C
78 85 F
79 19 C
80 34 C
81 73 C
[82 rows x 2 columns]
备选方案:
m = df['strat'].map(df['strat'].value_counts()).lt(21)
df = pd.concat((df[~m].groupby('strat').sample(frac=0.5), df[m].sample(frac=1)))
其他解决方案可能会更快。这是另一个,以防 readability/maintainability 更重要。
def sample_stratum(stratum):
nrows = stratum.shape[0]
if nrows < 21:
output = stratum.sample(nrows)
else:
output = stratum.sample(frac=0.5)
return output
# Index may be retained if needed
sampled_df = df.groupby(by=['strat']).apply(sample_stratum).reset_index(drop=True)
# ID strat
# 0 12 A
# 1 7 A
# 2 50 A
# 3 58 A
# 4 0 A
# .. .. ...
# 77 41 F
# 78 42 F
# 79 16 F
# 80 76 F
# 81 90 F
# [82 rows x 2 columns]