Python Pandas Dataframe - 基于条件的分组和平均值

Posted

技术标签:

【中文标题】Python Pandas Dataframe - 基于条件的分组和平均值【英文标题】:Python Pandas Dataframe - Groupby and Average based on Condition 【发布时间】:2016-01-18 02:40:06 【问题描述】:

我有一个如下所示的数据框:

id  start       end         diff mindiff
1   2015-01-02  2015-07-01  180 57
2   2015-02-03  2015-05-12  98  56
3   2015-01-15  2015-01-20  5   5
4   2015-02-04  2015-04-15  70  55
5   2015-03-15  2015-05-01  47  46
6   2015-02-22  2015-03-01  7   7
7   2015-03-21  2015-04-12  22  22
8   2015-04-11  2015-06-15  65  50
9   2015-04-11  2015-05-01  20  20
10  2015-03-30  2015-04-01  2   2
11  2015-04-28  2015-06-15  48  33
12  2015-05-01  2015-06-01  31  31
13  2015-05-10  2015-06-09  30  30
14  2015-05-19  2015-07-01  43  42
15  2015-06-01  2015-06-06  5   5
16  2015-06-02  2015-06-29  27  27
17  2015-04-29  2015-05-21  22  22
18  2015-05-25  2015-07-01  37  36
19  2015-06-04  2015-06-26  22  22
20  2015-06-21  2015-07-01  10  10
21  2015-05-30  2015-06-06  7   7
22  2015-06-30  2015-07-01  1   1

字段为 id、start(日期)、end(日期)、diff(开始和结束之间的天数)、mindiff(最小值(diff 和距离开始 x 个月后的最后一天)。

在这种情况下,x 为 1(因此比开始日期“晚”一个月)

我想要完成的是找到 mindiff 的平均值(平均值),按“结束”的年/月分组,但仅对每个组具有“开始”年/月 x 的记录进行平均(上面定义的)几个月前到groupedby 月份。来自上述数据集的示例,id 1 只会在 2015/1 和 2015/1+x (2015/2) 年/月进行平均。

这是一个表格,标记了每条记录以及我希望在哪个月份进行平均:

    Months                      
id  1   2   3   4   5   6   7
1   1   1                   
2       1   1               
3   1                       
4       1   1               
5           1   1           
6       1   1               
7           1   1           
8               1   1       
9               1   1       
10          1   1           
11              1   1       
12                  1   1   
13                  1   1   
14                  1   1   
15                      1   
16                      1   
17              1   1       
18                  1   1   
19                      1   
20                      1   1
21                  1   1   
22                      1   1

这是我正在寻找的 mindiffs 和由此产生的 AVG/月:

    Months                      
id  1   2   3   4   5   6   7
1   57  57                  
2       56  56              
3   5                       
4       55  55              
5           46  46          
6       7   7               
7           22  22          
8               50  50      
9               20  20      
10          2   2           
11              33  33      
12                  31  31  
13                  30  30  
14                  42  42  
15                      5   
16                      27  
17              22  22      
18                  36  36  
19                      22  
20                      10  10
21                  7   7   
22                      1   1
AVG 31  43.8    31.3    27.9    30.1    21.1    5.5

最后,这是我正在寻找的数据框:

Month   Avg Diff Trailing x months
2015-01 31
2015-02 43.75
2015-03 31.33333333
2015-05 27.85714286
2015-05 30.11111111
2015-06 21.1
2015-07 5.5

我知道这可以通过循环实现,但我的直觉认为 GROUPBY 更符合 Python 风格并且可能更高效。但是,我如何才能在“结束”年/月的 groupby 中仅获得“开始”月份的特定滚动 mindiff 值。谢谢您的帮助。

【问题讨论】:

【参考方案1】:

首先我创建了不同年份的测试数据,并将最后一行的开始设置为 12 月。然后我将startend 列转换为句点-periodSperiodE 列。

我在month 列中使用函数groupby 并从Avg 列中计算平均值:

g = df1.groupby('months')['Avg'].mean().reset_index()
import pandas as pd
import numpy as np
import io

temp=u"""id;start;end
1;2014-01-02;2014-07-01
2;2014-02-03;2014-05-12
3;2014-01-15;2014-01-20
4;2014-02-04;2014-04-15
5;2014-03-15;2014-05-01
6;2014-02-22;2014-03-01
7;2015-03-21;2015-04-12
8;2015-04-11;2015-06-15
9;2015-04-11;2015-05-01
10;2015-03-30;2015-04-01
11;2015-04-28;2015-06-15
12;2015-05-01;2015-06-01
13;2015-05-10;2015-06-09
14;2016-05-19;2016-07-01
15;2016-06-01;2016-06-06
16;2016-06-02;2016-06-29
17;2016-04-29;2016-05-21
18;2016-05-25;2016-07-01
19;2017-06-04;2017-06-26
20;2017-06-21;2017-07-01
21;2017-05-30;2017-06-06
22;2017-12-30;2018-02-01"""

df = pd.read_csv(io.StringIO(temp), sep=";", index_col=[0])
print df
def last_day_of_next_month(any_day):
    next_month = any_day.replace(day=28) + pd.Timedelta(days=36)  # this will never fail
    return next_month - pd.Timedelta(days=next_month.day)

df['mindiff'] = (pd.to_datetime(df['start']).apply(last_day_of_next_month) - pd.to_datetime(df['start'])).astype('timedelta64[D]')
df['diff'] = (pd.to_datetime(df['end']) - pd.to_datetime(df['start'])).astype('timedelta64[D]')
df['mindiff'] = df[['mindiff', 'diff']].apply(lambda x: min(x), axis=1)
#print df

#set day of start and end to periodindex
df['periodS'] =  pd.to_datetime(df['start']).dt.to_period('M')
df['periodE'] =  pd.to_datetime(df['end']).dt.to_period('M')

#if period end is higher as period start, add one month else NaN
df['period'] = np.where(df['periodE'] > df['periodS'],df['periodS'] + 1, np.nan)
#print df
#df from subset
df1 = df[['mindiff', 'periodS', 'period']]
#pivot data (from rows to columns)
df1 = df1.set_index('mindiff').stack().reset_index()
#rename columns names
df1.columns = ['Avg', 'tmp', 'months']
#groupby by column month and count mean from column Avg
g = df1.groupby('months')['Avg'].mean().reset_index()
print g
#     months        Avg
#0   2014-01  31.000000
#1   2014-02  43.750000
#2   2014-03  41.000000
#3   2014-04  46.000000
#4   2015-03  12.000000
#5   2015-04  25.400000
#6   2015-05  32.800000
#7   2015-06  30.500000
#8   2016-04  22.000000
#9   2016-05  33.333333
#10  2016-06  27.500000
#11  2017-05   7.000000
#12  2017-06  13.000000
#13  2017-07  10.000000
#14  2017-12  32.000000
#15  2018-01  32.000000

【讨论】:

@jezreal - 谢谢。这看起来很有希望。我会经历它然后回来。在几个月内使用 resample 会更pythonic吗? (与 dt.month 列相反 你是说你的方式是对 resample('M') 的改进吗?此外,如果日期跨越多年,这将不起作用,因为您假设从 12 月到 1 月将增加 1 个月。对此也有什么想法吗?感谢您的帮助。 如果我帮助你,你可以投票并accept 回答。 more info 太棒了。比如下个月的处理……也可以加2个月减1天? 感谢您的支持和接受。我在哪里可以加 2 个月和减去一天?

以上是关于Python Pandas Dataframe - 基于条件的分组和平均值的主要内容,如果未能解决你的问题,请参考以下文章

Python Pandas -- DataFrame

Python pandas DataFrame的切片取值

利用Python进行数据分析:Pandas(Series+DataFrame)

python: pandas.DataFrame,如何避免keyerror?

小白学 Python 数据分析:Pandas 数据结构 DataFrame

Python数据分析pandas之dataframe初识