用pandas将数据框按最接近的时间戳进行合并。

6
我希望能够在三个列(email, subject和timestamp)上合并两个数据框。由于数据框之间的时间戳不同,因此我需要为一组电子邮件和主题识别最接近的匹配时间戳。
以下是一个可复现的示例,使用了this问题中建议的查找最接近匹配的函数。
import numpy as np
import pandas as pd
from pandas.io.parsers import StringIO

def find_closest_date(timepoint, time_series, add_time_delta_column=True):
   # takes a pd.Timestamp() instance and a pd.Series with dates in it
   # calcs the delta between `timepoint` and each date in `time_series`
   # returns the closest date and optionally the number of days in its time delta
   deltas = np.abs(time_series - timepoint)
   idx_closest_date = np.argmin(deltas)
   res = {"closest_date": time_series.ix[idx_closest_date]}
   idx = ['closest_date']
   if add_time_delta_column:
      res["closest_delta"] = deltas[idx_closest_date]
      idx.append('closest_delta')
   return pd.Series(res, index=idx)


a = """timestamp,email,subject
2016-07-01 10:17:00,a@gmail.com,subject3
2016-07-01 02:01:02,a@gmail.com,welcome
2016-07-01 14:45:04,a@gmail.com,subject3
2016-07-01 08:14:02,a@gmail.com,subject2
2016-07-01 16:26:35,a@gmail.com,subject4
2016-07-01 10:17:00,b@gmail.com,subject3
2016-07-01 02:01:02,b@gmail.com,welcome
2016-07-01 14:45:04,b@gmail.com,subject3
2016-07-01 08:14:02,b@gmail.com,subject2
2016-07-01 16:26:35,b@gmail.com,subject4
"""

b = """timestamp,email,subject,clicks,var1
2016-07-01 02:01:14,a@gmail.com,welcome,1,1
2016-07-01 08:15:48,a@gmail.com,subject2,2,2
2016-07-01 10:17:39,a@gmail.com,subject3,1,7
2016-07-01 14:46:01,a@gmail.com,subject3,1,2
2016-07-01 16:27:28,a@gmail.com,subject4,1,2
2016-07-01 10:17:05,b@gmail.com,subject3,0,0
2016-07-01 02:01:03,b@gmail.com,welcome,0,0
2016-07-01 14:45:05,b@gmail.com,subject3,0,0
2016-07-01 08:16:00,b@gmail.com,subject2,0,0
2016-07-01 17:00:00,b@gmail.com,subject4,0,0
"""

请注意,对于a@gmail.com,最接近的时间戳为10:17:39,而对于b@gmail.com,最接近的匹配是10:17:05。
a = """timestamp,email,subject
2016-07-01 10:17:00,a@gmail.com,subject3
2016-07-01 10:17:00,b@gmail.com,subject3
"""

b = """timestamp,email,subject,clicks,var1
2016-07-01 10:17:39,a@gmail.com,subject3,1,7
2016-07-01 10:17:05,b@gmail.com,subject3,0,0
"""
df1 = pd.read_csv(StringIO(a), parse_dates=['timestamp'])
df2 = pd.read_csv(StringIO(b), parse_dates=['timestamp'])

df1[['closest', 'time_bt_x_and_y']] = df1.timestamp.apply(find_closest_date, args=[df2.timestamp])
df1

df3 = pd.merge(df1, df2, left_on=['email','subject','closest'], right_on=['email','subject','timestamp'],how='left')

df3
timestamp_x        email   subject             closest  time_bt_x_and_y         timestamp_y  clicks  var1
  2016-07-01 10:17:00  a@gmail.com  subject3 2016-07-01 10:17:05         00:00:05                 NaT     NaN   NaN
  2016-07-01 02:01:02  a@gmail.com   welcome 2016-07-01 02:01:03         00:00:01                 NaT     NaN   NaN
  2016-07-01 14:45:04  a@gmail.com  subject3 2016-07-01 14:45:05         00:00:01                 NaT     NaN   NaN
  2016-07-01 08:14:02  a@gmail.com  subject2 2016-07-01 08:15:48         00:01:46 2016-07-01 08:15:48     2.0   2.0
  2016-07-01 16:26:35  a@gmail.com  subject4 2016-07-01 16:27:28         00:00:53 2016-07-01 16:27:28     1.0   2.0
  2016-07-01 10:17:00  b@gmail.com  subject3 2016-07-01 10:17:05         00:00:05 2016-07-01 10:17:05     0.0   0.0
  2016-07-01 02:01:02  b@gmail.com   welcome 2016-07-01 02:01:03         00:00:01 2016-07-01 02:01:03     0.0   0.0
  2016-07-01 14:45:04  b@gmail.com  subject3 2016-07-01 14:45:05         00:00:01 2016-07-01 14:45:05     0.0   0.0
  2016-07-01 08:14:02  b@gmail.com  subject2 2016-07-01 08:15:48         00:01:46                 NaT     NaN   NaN
  2016-07-01 16:26:35  b@gmail.com  subject4 2016-07-01 16:27:28         00:00:53                 NaT     NaN   NaN

结果是错误的,主要是因为最接近的日期不正确,因为它没有考虑电子邮件和主题。
期望的结果是:

enter image description here

修改该函数,使其能够为给定的电子邮件和主题提供最接近的时间戳,这将是有帮助的。
df1.groupby(['email','subject'])['timestamp'].apply(find_closest_date, args=[df1.timestamp])

但是这会导致错误,因为该函数不能用于组对象。怎么做才是最好的方法?


1
请不要使用PNG格式来存储代码或数据。 - Merlin
好的,您想要什么格式? - TinaW
你的预期输出是文本;将其作为文本添加到您的帖子中,而不是作为图像。 - Martijn Pieters
2个回答

6
请注意,如果您在emailsubject上合并df1df2,则结果将具有所有可能的相关时间戳配对。
In [108]: result = pd.merge(df1, df2, how='left', on=['email','subject'], suffixes=['', '_y']); result
Out[108]: 
             timestamp        email   subject         timestamp_y  clicks  var1
0  2016-07-01 10:17:00  a@gmail.com  subject3 2016-07-01 10:17:39       1     7
1  2016-07-01 10:17:00  a@gmail.com  subject3 2016-07-01 14:46:01       1     2
2  2016-07-01 02:01:02  a@gmail.com   welcome 2016-07-01 02:01:14       1     1
3  2016-07-01 14:45:04  a@gmail.com  subject3 2016-07-01 10:17:39       1     7
4  2016-07-01 14:45:04  a@gmail.com  subject3 2016-07-01 14:46:01       1     2
5  2016-07-01 08:14:02  a@gmail.com  subject2 2016-07-01 08:15:48       2     2
6  2016-07-01 16:26:35  a@gmail.com  subject4 2016-07-01 16:27:28       1     2
7  2016-07-01 10:17:00  b@gmail.com  subject3 2016-07-01 10:17:05       0     0
8  2016-07-01 10:17:00  b@gmail.com  subject3 2016-07-01 14:45:05       0     0
9  2016-07-01 02:01:02  b@gmail.com   welcome 2016-07-01 02:01:03       0     0
10 2016-07-01 14:45:04  b@gmail.com  subject3 2016-07-01 10:17:05       0     0
11 2016-07-01 14:45:04  b@gmail.com  subject3 2016-07-01 14:45:05       0     0
12 2016-07-01 08:14:02  b@gmail.com  subject2 2016-07-01 08:16:00       0     0
13 2016-07-01 16:26:35  b@gmail.com  subject4 2016-07-01 17:00:00       0     0

现在,您可以针对每一行的时间戳差异取绝对值:

result['diff'] = (result['timestamp_y'] - result['timestamp']).abs()

并且使用

idx = result.groupby(['timestamp','email','subject'])['diff'].idxmin()
result = result.loc[idx]

根据['timestamp','email','subject'],找到每个组中差异最小的行。


import numpy as np
import pandas as pd
from pandas.io.parsers import StringIO

a = """timestamp,email,subject
2016-07-01 10:17:00,a@gmail.com,subject3
2016-07-01 02:01:02,a@gmail.com,welcome
2016-07-01 14:45:04,a@gmail.com,subject3
2016-07-01 08:14:02,a@gmail.com,subject2
2016-07-01 16:26:35,a@gmail.com,subject4
2016-07-01 10:17:00,b@gmail.com,subject3
2016-07-01 02:01:02,b@gmail.com,welcome
2016-07-01 14:45:04,b@gmail.com,subject3
2016-07-01 08:14:02,b@gmail.com,subject2
2016-07-01 16:26:35,b@gmail.com,subject4
"""

b = """timestamp,email,subject,clicks,var1
2016-07-01 02:01:14,a@gmail.com,welcome,1,1
2016-07-01 08:15:48,a@gmail.com,subject2,2,2
2016-07-01 10:17:39,a@gmail.com,subject3,1,7
2016-07-01 14:46:01,a@gmail.com,subject3,1,2
2016-07-01 16:27:28,a@gmail.com,subject4,1,2
2016-07-01 10:17:05,b@gmail.com,subject3,0,0
2016-07-01 02:01:03,b@gmail.com,welcome,0,0
2016-07-01 14:45:05,b@gmail.com,subject3,0,0
2016-07-01 08:16:00,b@gmail.com,subject2,0,0
2016-07-01 17:00:00,b@gmail.com,subject4,0,0
"""

df1 = pd.read_csv(StringIO(a), parse_dates=['timestamp'])
df2 = pd.read_csv(StringIO(b), parse_dates=['timestamp'])

result = pd.merge(df1, df2, how='left', on=['email','subject'], suffixes=['', '_y'])
result['diff'] = (result['timestamp_y'] - result['timestamp']).abs()
idx = result.groupby(['timestamp','email','subject'])['diff'].idxmin()
result = result.loc[idx].drop(['timestamp_y','diff'], axis=1)
result = result.sort_index()
print(result)

产量
             timestamp        email   subject  clicks  var1
0  2016-07-01 10:17:00  a@gmail.com  subject3       1     7
2  2016-07-01 02:01:02  a@gmail.com   welcome       1     1
4  2016-07-01 14:45:04  a@gmail.com  subject3       1     2
5  2016-07-01 08:14:02  a@gmail.com  subject2       2     2
6  2016-07-01 16:26:35  a@gmail.com  subject4       1     2
7  2016-07-01 10:17:00  b@gmail.com  subject3       0     0
9  2016-07-01 02:01:02  b@gmail.com   welcome       0     0
11 2016-07-01 14:45:04  b@gmail.com  subject3       0     0
12 2016-07-01 08:14:02  b@gmail.com  subject2       0     0
13 2016-07-01 16:26:35  b@gmail.com  subject4       0     0

1
你希望对每个“电子邮件”和“主题”组应用最接近的时间戳逻辑。
a = """timestamp,email,subject
2016-07-01 10:17:00,a@gmail.com,subject3
2016-07-01 02:01:02,a@gmail.com,welcome
2016-07-01 14:45:04,a@gmail.com,subject3
2016-07-01 08:14:02,a@gmail.com,subject2
2016-07-01 16:26:35,a@gmail.com,subject4
2016-07-01 10:17:00,b@gmail.com,subject3
2016-07-01 02:01:02,b@gmail.com,welcome
2016-07-01 14:45:04,b@gmail.com,subject3
2016-07-01 08:14:02,b@gmail.com,subject2
2016-07-01 16:26:35,b@gmail.com,subject4
"""

b = """timestamp,email,subject,clicks,var1
2016-07-01 02:01:14,a@gmail.com,welcome,1,1
2016-07-01 08:15:48,a@gmail.com,subject2,2,2
2016-07-01 10:17:39,a@gmail.com,subject3,1,7
2016-07-01 14:46:01,a@gmail.com,subject3,1,2
2016-07-01 16:27:28,a@gmail.com,subject4,1,2
2016-07-01 10:17:05,b@gmail.com,subject3,0,0
2016-07-01 02:01:03,b@gmail.com,welcome,0,0
2016-07-01 14:45:05,b@gmail.com,subject3,0,0
2016-07-01 08:16:00,b@gmail.com,subject2,0,0
2016-07-01 17:00:00,b@gmail.com,subject4,0,0
"""

df1 = pd.read_csv(StringIO(a), parse_dates=['timestamp'])
df2 = pd.read_csv(StringIO(b), parse_dates=['timestamp'])
df2 = df2.set_index(['email', 'subject'])

def find_closest_date(timepoint, time_series, add_time_delta_column=True):
    # takes a pd.Timestamp() instance and a pd.Series with dates in it
    # calcs the delta between `timepoint` and each date in `time_series`
    # returns the closest date and optionally the number of days in its time delta
    time_series = time_series.values
    timepoint = np.datetime64(timepoint)
    deltas = np.abs(np.subtract(time_series, timepoint))
    idx_closest_date = np.argmin(deltas)
    res = {"closest_date": time_series[idx_closest_date]}
    idx = ['closest_date']
    if add_time_delta_column:
        res["closest_delta"] = deltas[idx_closest_date]
        idx.append('closest_delta')
    return pd.Series(res, index=idx)

# Then group df1 as needed
grouped = df1.groupby(['email', 'subject'])

# Finally loop over the group items, finding the closest timestamps
join_ts = pd.DataFrame()
for name, group in grouped:
    try:
        join_ts = pd.concat([join_ts, group['timestamp']\
                             .apply(find_closest_date, time_series=df2.loc[name, 'timestamp'])],
                            axis=0)
    except KeyError:
        pass

df3 = pd.merge(pd.concat([df1, join_ts], axis=1), df2, left_on=['closest_date'], right_on=['timestamp'])

抱歉,这并没有得到预期的结果。 - TinaW
那么它给出了什么?错误信息,还是其他什么?你能再具体一些吗?拜托了。 - Kartik
我的帖子中的图片显示了预期结果。主要问题是最接近的时间戳是错误的,因为它没有考虑到另外两个维度,即电子邮件和主题。如果您查看内部连接的结果,它只包含5封电子邮件,但应该显示10封(请参见我帖子中的图片)。 - TinaW
哦,明白了!您想要给定电子邮件和主题的最接近时间戳。我正在编辑我的答案,修改后应该可以正常工作。 - Kartik
谢谢Kathik!! 这非常有帮助!输出几乎正确。请注意,b@gmail.com应该只有0次点击和var1.将df3.sort_values(['email_x'])与预期输出进行比较。在所有情况下,电子邮件B都为零。 - TinaW
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接