目前没有流行病学方式做到这一点。
这个答案原本是关于使用多态性解决问题的,但结果证明这是一个非常糟糕的想法。
然后,在另一个答案中出现了numpy.piecewise
函数,但没有太多解释,因此我想澄清如何使用此函数。
Numpy方法使用 piecewise(内存占用高)
np.piecewise
函数可用于生成自定义连接的行为。这涉及大量开销,并不是非常有效,但它能胜任工作。
生成连接条件
import pandas as pd
from datetime import datetime
presidents = pd.DataFrame({"name": ["Bush", "Obama", "Trump"],
"president_id":[43, 44, 45]})
terms = pd.DataFrame({'start_date': pd.date_range('2001-01-20', periods=5, freq='48M'),
'end_date': pd.date_range('2005-01-21', periods=5, freq='48M'),
'president_id': [43, 43, 44, 44, 45]})
war_declarations = pd.DataFrame({"date": [datetime(2001, 9, 14), datetime(2003, 3, 3)],
"name": ["War in Afghanistan", "Iraq War"]})
start_end_date_tuples = zip(terms.start_date.values, terms.end_date.values)
conditions = [(war_declarations.date.values >= start_date) &
(war_declarations.date.values <= end_date) for start_date, end_date in start_end_date_tuples]
> conditions
[array([ True, True], dtype=bool),
array([False, False], dtype=bool),
array([False, False], dtype=bool),
array([False, False], dtype=bool),
array([False, False], dtype=bool)]
这是一个数组列表,每个数组告诉我们每个战争宣布的时间跨度是否匹配。 随着数据集的增大,条件可能会变得复杂,因为左侧df和右侧df的长度将被乘以。
分段“魔法”
现在,piecewise将从术语中获取 president_id
并将其放置在相应战争的 war_declarations
数据帧中。
war_declarations['president_id'] = np.piecewise(np.zeros(len(war_declarations)),
conditions,
terms.president_id.values)
date name president_id
0 2001-09-14 War in Afghanistan 43.0
1 2003-03-03 Iraq War 43.0
现在,为了完成这个例子,我们只需要定期合并总统的名称。
war_declarations.merge(presidents, on="president_id", suffixes=["_war", "_president"])
date name_war president_id name_president
0 2001-09-14 War in Afghanistan 43.0 Bush
1 2003-03-03 Iraq War 43.0 Bush
多态(不起作用)
我想分享一下我的研究成果,即使这个不能解决问题,我希望它至少能作为一个有用的答复在这里得以保存。由于很难发现错误,其他人可能会尝试这个方法,并认为他们有一个可行的解决方案,但实际上并不是这样。
我唯一想到的另一种方法是创建两个新类,一个是PointInTime,另一个是Timespan。
它们都应该有__eq__
方法,在这个方法中如果一个PointInTime被与包含它的Timespan比较,则返回true。
然后,您可以使用这些对象填充DataFrame,并根据它们所处的列进行连接。
就像这样:
class PointInTime(object):
def __init__(self, year, month, day):
self.dt = datetime(year, month, day)
def __eq__(self, other):
return other.start_date < self.dt < other.end_date
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return "{}-{}-{}".format(self.dt.year, self.dt.month, self.dt.day)
class Timespan(object):
def __init__(self, start_date, end_date):
self.start_date = start_date
self.end_date = end_date
def __eq__(self, other):
return self.start_date < other.dt < self.end_date
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return "{}-{}-{} -> {}-{}-{}".format(self.start_date.year, self.start_date.month, self.start_date.day,
self.end_date.year, self.end_date.month, self.end_date.day)
重要提示:我不会继承datetime,因为pandas将考虑datetime对象列的数据类型为datetime dtype,而由于时间跨度不同,pandas会默默地拒绝在它们上面合并。
如果我们实例化这些类的两个对象,它们现在可以进行比较:
pit = PointInTime(2015,1,1)
ts = Timespan(datetime(2014,1,1), datetime(2015,2,2))
pit == ts
True
我们也可以用这些对象填充两个数据帧:
df = pd.DataFrame({"pit":[PointInTime(2015,1,1), PointInTime(2015,2,2), PointInTime(2015,3,3)]})
df2 = pd.DataFrame({"ts":[Timespan(datetime(2015,2,1), datetime(2015,2,5)), Timespan(datetime(2015,2,1), datetime(2015,4,1))]})
然后合并的方式就会生效:
pd.merge(left=df, left_on='pit', right=df2, right_on='ts')
pit ts
0 2015-2-2 2015-2-1 -> 2015-2-5
1 2015-2-2 2015-2-1 -> 2015-4-1
但只是在某种程度上。
PointInTime(2015,3,3)
也应该包含在这个连接中:Timespan(datetime(2015,2,1), datetime(2015,4,1))
,但它并没有被包含进去。
我猜测Pandas将PointInTime(2015,3,3)
与PointInTime(2015,2,2)
进行比较,并假设它们不相等,因此认为PointInTime(2015,3,3)
不能等于Timespan(datetime(2015,2,1), datetime(2015,4,1))
,因为这个时间段等于PointInTime(2015,2,2)
。
有点像这样:
Rose == Flower
Lilly != Rose
因此:
Lilly != Flower
编辑:
我尝试使所有的“PointInTime”相等,这改变了连接的行为,包括2015-3-3,但2015-2-2仅包括在时间跨度2015-2-1 -> 2015-2-5中,因此这加强了我上面的假设。
如果有其他想法,请留言让我尝试。