【问题标题】:Python Pandas Sum Values in Columns If date between 2 datesPython Pandas 列中的总和值如果日期在 2 个日期之间
【发布时间】:2018-06-14 16:33:37
【问题描述】:

我有一个数据框df,可以用这个来创建:

data={'id':[1,1,1,1,2,2,2,2],
      'date1':[datetime.date(2016,1,1),datetime.date(2016,1,2),datetime.date(2016,1,3),datetime.date(2016,1,4),
               datetime.date(2016,1,2),datetime.date(2016,1,4),datetime.date(2016,1,3),datetime.date(2016,1,1)],
      'date2':[datetime.date(2016,1,5),datetime.date(2016,1,3),datetime.date(2016,1,5),datetime.date(2016,1,5),
               datetime.date(2016,1,4),datetime.date(2016,1,5),datetime.date(2016,1,4),datetime.date(2016,1,1)],
      'score1':[5,7,3,2,9,3,8,3],
      'score2':[1,3,0,5,2,20,7,7]}
df=pd.DataFrame.from_dict(data)

And looks like this:
   id       date1       date2  score1  score2
0   1  2016-01-01  2016-01-05       5       1
1   1  2016-01-02  2016-01-03       7       3
2   1  2016-01-03  2016-01-05       3       0
3   1  2016-01-04  2016-01-05       2       5
4   2  2016-01-02  2016-01-04       9       2
5   2  2016-01-04  2016-01-05       3      20
6   2  2016-01-03  2016-01-04       8       7
7   2  2016-01-01  2016-01-01       3       7

我需要做的是为每个score1score2 创建一个列,根据usedate 是否介于date1date2usedate 是通过获取介于 date1 最小值和 date2 最大值之间的所有日期创建的。我用它来创建日期范围:

drange=pd.date_range(df.date1.min(),df.date2.max())    

生成的数据框 newdf 应如下所示:

     usedate  score1sum  score2sum
0 2016-01-01          8          8
1 2016-01-02         21          6
2 2016-01-03         32         13
3 2016-01-04         30         35
4 2016-01-05         13         26

为了澄清,在 usedate 2016-01-01 上,score1sum 是 8,这是通过查看 df 中的行计算得出的,其中 2016-01-01 介于 date1 和 @987654340 之间并包括在内@,将 row0(5) 和 row8(3) 相加。在usedate 2016-01-04 上,score2sum 是 35,这是通过查看 df 中的行计算得出的,其中 2016-01-04 介于 date1date2 之间,包括 date2,它们总和 row0( 1)、row3(0)、row4(5)、row5(2)、row6(20)、row7(7)。

也许是某种groupby,或者melt,然后是groupby

【问题讨论】:

  • 你是从 0 还是从 1 开始计算行数?您的第二个示例存在混淆。在第一个示例中,我没有看到 row8 是 3。回到第二个示例,2016-01-04 为何介于 2017-05-28 and 2017-09-222015-11-01 and 2015-11-09 之间?
  • 另外,你的最小日期(df.date1.min())是2015-11-01,你的usedate栏怎么能从2016-01-01开始?
  • @Fatih Akici,非常抱歉。我用更少的日期重新创建了示例数据框,却忘记了更改我最初拥有的内容。我已经用正确的示例更新了问题。

标签: python pandas dataframe pandas-groupby melt


【解决方案1】:

方法 1:列表推导

这很不雅,但是,嘿,它有效! (编辑:在下面添加了第二种方法。)

# Convert datetime.date to pandas timestamps for easier comparisons
df['date1'] = pd.to_datetime(df['date1'])
df['date2'] = pd.to_datetime(df['date2'])

# solution
newdf = pd.DataFrame(data=drange, columns=['usedate'])
# for each usedate ud, get all df rows whose dates contain ud,
# then sum the scores of these rows
newdf['score1sum'] = [df[(df['date1'] <= ud) & (df['date2'] >= ud)]['score1'].sum() for ud in drange]
newdf['score2sum'] = [df[(df['date1'] <= ud) & (df['date2'] >= ud)]['score2'].sum() for ud in drange]

# output
newdf
     usedate  score1sum  score2sum
  2016-01-01          8          8
  2016-01-02         21          6
  2016-01-03         32         13
  2016-01-04         30         35
  2016-01-05         13         26

方法2:使用transform(或apply)的辅助函数

newdf = pd.DataFrame(data=drange, columns=['usedate'])

def sum_scores(d):
    return df[(df['date1'] <= d) & (df['date2'] >= d)][['score1', 'score2']].sum()

# apply works here too, and is about equally fast in my testing
newdf[['score1sum', 'score2sum']] = newdf['usedate'].transform(sum_scores)

# newdf is same to above

时间是可比的

# Jupyter timeit cell magic
%%timeit 
newdf['score1sum'] = [df[(df['date1'] <= d) & (df['date2'] >= d)]['score1'].sum() for d in drange]
newdf['score1sum'] = [df[(df['date1'] <= d) & (df['date2'] >= d)]['score2'].sum() for d in drange]

100 loops, best of 3: 10.4 ms per loop

# Jupyter timeit line magic
%timeit newdf[['score1sum', 'score2sum']] = newdf['usedate'].transform(sum_scores) 

100 loops, best of 3: 8.51 ms per loop

【讨论】:

  • 当我运行这个时,我得到'Series Object has no attribute 'transform'。 newdf['usedate'] 是一个系列。您是如何让转换工作的?
  • @clg4 我认为你需要升级你的熊猫?正在运行什么版本的 pandas?
  • 熊猫版本 19.2
  • @clg4,就是这样:DataFrame.transform 是在 0.20.0 中引入的。
  • 来自 OP 的问题:drange=pd.date_range(df.date1.min(),df.date2.max())
【解决方案2】:

您可以将apply 与 lambda 函数一起使用:

df['date1'] = pd.to_datetime(df['date1'])

df['date2'] = pd.to_datetime(df['date2'])

df1 = pd.DataFrame(index=pd.date_range(df.date1.min(), df.date2.max()), columns = ['score1sum', 'score2sum'])

df1[['score1sum','score2sum']] = df1.apply(lambda x: df.loc[(df.date1 <= x.name) & 
                                                            (x.name <= df.date2),
                                                            ['score1','score2']].sum(), axis=1)

df1.rename_axis('usedate').reset_index()

输出:

     usedate  score1sum  score2sum
0 2016-01-01          8          8
1 2016-01-02         21          6
2 2016-01-03         32         13
3 2016-01-04         30         35
4 2016-01-05         13         26

【讨论】:

  • 上面的 x.name 指的是什么?
  • x.name 是指应用正在处理的列。 Apply 一次获取数据帧的每一列。确定您正在处理哪一列,您可以将 x.name 用于该系列名称。
  • 我很困惑,你能应用你用来获取输出的实际列名吗?我假设您的代码将同时适用于 score1 和 score2。
  • 我很抱歉....因为我们使用axis = 1的apply,x.name是指我们正在处理的数据框的行索引。因此,对于 row1 x.name 将是索引中的第一个日期。根据轴,应用在时间上作用于一列或一行。因此,每一个都作为“pd.Series”传入,索引为名称。
  • 没有。在这种情况下,x.name 指的是我们在数据框中构建的已使用数据。因此,当您使用axis = 1申请时。每一行都作为一个系列传入,系列索引是数据框的列标题,系列的名称是该数据框的行索引。
【解决方案3】:

来自pyjanitorconditional_join可能有助于抽象/方便:

# pip install pyjanitor
import pandas as pd
import janitor as jn

drange = pd.DataFrame(drange, columns=['dates'])
df['date1'] = pd.to_datetime(df['date1'])
df['date2'] = pd.to_datetime(df['date2'])

(drange.conditional_join(df, 
                         ('dates', 'date1', '>='), 
                         ('dates', 'date2', '<='))
.droplevel(0, 1)
.select_columns('dates', 'score*')
.groupby('dates')
.sum()
.add_suffix('num')
) 
            score1num  score2num
dates                           
2016-01-01          8          8
2016-01-02         21          6
2016-01-03         32         13
2016-01-04         30         35
2016-01-05         13         26

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-11-14
    • 2022-11-14
    • 1970-01-01
    • 2016-06-24
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多