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='|') 

我想将idaddress 分组,并根据以下条件计算mean_ratioresult_count

    mean_ratio: 是 groupby idaddress 并计算满足以下条件的行的平均值:status is finished and start_date is in the range of 2019-09 and 2019-10 result_count:是groupby idaddress,计算行数满足以下条件:status要么是finished要么是failed,而start_date2019-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_date2019-092019-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])]

要过滤行状态为finishedfailed,我使用:

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 中添加所有唯一对idaddress,最后替换中的缺失值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中的多个条件计算计数和均值的主要内容,如果未能解决你的问题,请参考以下文章

(pandas) 根据 groupby 和 column 条件填充 NaN

Python pandas数据框根据条件分组

pandas groupby 根据条件连接

如何在 pandas 中使用过滤条件和 groupby

Pandas —— 数据分组

Pandas - 带条件公式的 Groupby