Groupby并根据Pandas中的多个条件计算计数和均值
Posted
技术标签:
【中文标题】Groupby并根据Pandas中的多个条件计算计数和均值【英文标题】:Groupby and calculate count and means based on multiple conditions in Pandas 【发布时间】:2020-06-24 03:33:00 【问题描述】:对于给定的数据框如下:
id|address|sell_price|market_price|status|start_date|end_date
1|7552 Atlantic Lane|1170787.3|1463484.12|finished|2019/8/2|2019/10/1
1|7552 Atlantic Lane|1137782.02|1422227.52|finished|2019/8/2|2019/10/1
2|888 Foster Street|1066708.28|1333385.35|finished|2019/8/2|2019/10/1
2|888 Foster Street|1871757.05|1416757.05|finished|2019/10/14|2019/10/15
2|888 Foster Street|NaN|763744.52|current|2019/10/12|2019/10/13
3|5 Pawnee Avenue|NaN|928366.2|current|2019/10/10|2019/10/11
3|5 Pawnee Avenue|NaN|2025924.16|current|2019/10/10|2019/10/11
3|5 Pawnee Avenue|Nan|4000000|forward|2019/10/9|2019/10/10
3|5 Pawnee Avenue|2236138.9|1788938.9|finished|2019/10/8|2019/10/9
4|916 W. Mill Pond St.|2811026.73|1992026.73|finished|2019/9/30|2019/10/1
4|916 W. Mill Pond St.|13664803.02|10914803.02|finished|2019/9/30|2019/10/1
4|916 W. Mill Pond St.|3234636.64|1956636.64|finished|2019/9/30|2019/10/1
5|68 Henry Drive|2699959.92|NaN|failed|2019/10/8|2019/10/9
5|68 Henry Drive|5830725.66|NaN|failed|2019/10/8|2019/10/9
5|68 Henry Drive|2668401.36|1903401.36|finished|2019/12/8|2019/12/9
#copy above data and run below code to reproduce dataframe
df = pd.read_clipboard(sep='|')
我想将id
和address
分组,并根据以下条件计算mean_ratio
和result_count
:
mean_ratio
: 是 groupby id
和 address
并计算满足以下条件的行的平均值:status
is finished
and start_date
is in the range of 2019-09
and 2019-10
result_count
:是groupby id
和address
,计算行数满足以下条件:status
要么是finished
要么是failed
,而start_date
在2019-09
和@987654343的范围内@
所需的输出将如下所示:
id address mean_ratio result_count
0 1 7552 Atlantic Lane NaN 0
1 2 888 Foster Street 1.32 1
2 3 5 Pawnee Avenue 1.25 1
3 4 916 W. Mill Pond St. 1.44 3
4 5 68 Henry Drive NaN 2
到目前为止我已经尝试过:
# convert date
df[['start_date', 'end_date']] = df[['start_date', 'end_date']].apply(lambda x: pd.to_datetime(x, format = '%Y/%m/%d'))
# calculate ratio
df['ratio'] = round(df['sell_price']/df['market_price'], 2)
为了过滤start_date
在2019-09
和2019-10
的范围内:
L = [pd.Period('2019-09'), pd.Period('2019-10')]
c = ['start_date']
df = df[np.logical_or.reduce([df[x].dt.to_period('m').isin(L) for x in c])]
要过滤行状态为finished
或failed
,我使用:
mask = df['status'].str.contains('finished|failed')
df[mask]
但我不知道如何使用这些来获得最终结果。提前感谢您的帮助。
【问题讨论】:
抱歉,我用 excel 创建了数据框,然后使用了pd.read_clipboard()
,我不知道如何将其转换为代码。
我已经编辑了数据,不知道你是否可以。
检查how-to-provide-a-reproducible-copy-of-the-dataframe-with-to-clipboard或添加df.to_clipboard(sep=',', index=False)
的输出
我认为您的数据有误。 id = 2
的最后一行缺少列值。
不确定我是否理解正确,有一些行sell_price
是NaN
。
【参考方案1】:
一些帮手
def mean_ratio(idf):
# filtering data
idf = idf[
(idf['start_date'].between('2019-09-01', '2019-10-31')) &
(idf['mean_ratio'].notnull()) ]
return np.round(idf['mean_ratio'].mean(), 2)
def result_count(idf):
idf = idf[
(idf['status'].isin(['finished', 'failed'])) &
(idf['start_date'].between('2019-09-01', '2019-10-31')) ]
return idf.shape[0]
# We can caluclate `mean_ratio` before hand
df['mean_ratio'] = df['sell_price'] / df['market_price']
df = df.astype('start_date': np.datetime64, 'end_date': np.datetime64)
# Group the df
g = df.groupby(['id', 'address'])
mean_ratio = g.apply(lambda idf: mean_ratio(idf)).to_frame('mean_ratio')
result_count = g.apply(lambda idf: result_count(idf)).to_frame('result_count')
# Final result
pd.concat((mean_ratio, result_count), axis=1)
【讨论】:
谢谢你,在你的第二个函数中,你为什么用df['start_date'].between('2019-09-01', '2019-10-31')
而不是idf['start_date'].between('2019-09-01', '2019-10-31')
?是拼写错误吗?
@ahbon 这是一个错字 :)。我已经更新了答案
我已经测试了你的两种方法,我得到了相同的结果。非常感谢。【参考方案2】:
我认为您需要GroupBy.agg
,但由于某些行被排除在外,例如id=1
,然后将它们添加到DataFrame.join
,并在df2
中添加所有唯一对id
和address
,最后替换中的缺失值result_count
列:
df2 = df[['id','address']].drop_duplicates()
print (df2)
id address
0 1 7552 Atlantic Lane
2 2 888 Foster Street
5 3 5 Pawnee Avenue
9 4 916 W. Mill Pond St.
12 5 68 Henry Drive
df[['start_date', 'end_date']] = df[['start_date', 'end_date']].apply(lambda x: pd.to_datetime(x, format = '%Y/%m/%d'))
df['ratio'] = round(df['sell_price']/df['market_price'], 2)
L = [pd.Period('2019-09'), pd.Period('2019-10')]
c = ['start_date']
mask = df['status'].str.contains('finished|failed')
mask1 = np.logical_or.reduce([df[x].dt.to_period('m').isin(L) for x in c])
df = df[mask1 & mask]
df1 = df.groupby(['id', 'address']).agg(mean_ratio=('ratio','mean'),
result_count=('ratio','size'))
df1 = df2.join(df1, on=['id','address']).fillna('result_count': 0)
print (df1)
id address mean_ratio result_count
0 1 7552 Atlantic Lane NaN 0.0
2 2 888 Foster Street 1.320000 1.0
5 3 5 Pawnee Avenue 1.250000 1.0
9 4 916 W. Mill Pond St. 1.436667 3.0
12 5 68 Henry Drive NaN 2.0
【讨论】:
非常棒,太棒了。 顺便说一句,你如何用索引处理我的问题中的数据? @ahbon - 你觉得0,2,5,9,12
吗?
@ahbon - 如果需要默认只需将df2 = df[['id','address']].drop_duplicates()
更改为df2 = df[['id','address']].drop_duplicates().reset_index(drop=True)
不,我想知道您在读取我从read_clipboard
粘贴的数据时是否有问题,我尝试使用pd.read_clipboard(sep=',', index=False)
但它会引发错误。以上是关于Groupby并根据Pandas中的多个条件计算计数和均值的主要内容,如果未能解决你的问题,请参考以下文章