合并一个值在另外两个之间的熊猫数据框[重复]
Posted
技术标签:
【中文标题】合并一个值在另外两个之间的熊猫数据框[重复]【英文标题】:Merge pandas dataframes where one value is between two others [duplicate] 【发布时间】:2015-08-18 03:04:05 【问题描述】:我需要在一个标识符上合并两个 pandas 数据帧,以及一个数据帧中的日期在另一个数据帧中的两个日期之间的条件。
数据框 A 有一个日期(“fdate”)和一个 ID(“cusip”):
我需要将它与这个数据框 B 合并:
A.cusip==B.ncusip
和 A.fdate
介于 B.namedt
和 B.nameenddt
之间。
在 SQL 中这将是微不足道的,但我可以看到如何在 pandas 中执行此操作的唯一方法是首先无条件地合并标识符,然后过滤日期条件:
df = pd.merge(A, B, how='inner', left_on='cusip', right_on='ncusip')
df = df[(df['fdate']>=df['namedt']) & (df['fdate']<=df['nameenddt'])]
这真的是最好的方法吗?如果可以在合并中进行过滤,以避免在合并之后但在过滤完成之前出现可能非常大的数据框,似乎会好得多。
【问题讨论】:
老实说,这就是我的做法,合并参数只允许在索引或列上完全匹配,而不是使用你想要的特殊标准,据我所知 @EdChum,你认为在 GitHub 上提出功能请求有意义吗?您认为这不是一项功能的深层原因吗? 在某种程度上,当涉及到 dfs 时,不应期望类似 SQL 的功能,尤其是当您可以在合并之前/之后过滤 dfs 时,您可以添加一个请求,因为我不是开发人员,但他们虽然反应灵敏 如果还没有完成,在 github 上的功能请求对我来说听起来是个好主意。这类问题在这里出现的频率很高,我从来没有见过一个真正好的答案——就像处理 pandas 一样好。 我想这里的问题是为此定义一个合理的 api,目前有命名的 args,如果你开始允许小于、大于等的东西......那么它可能会变得混乱,否则您可以允许一个可以评估的用户字符串,但即使这样也可能很麻烦,因为在比较时顺序可能很重要,这就是我对此的想法 【参考方案1】:目前没有流行的方式来做到这一点。
这个答案曾经是关于解决多态性问题,结果证明这是一个非常糟糕的主意。
然后numpy.piecewise
函数出现在另一个答案中,但几乎没有解释,所以我想我会澄清如何使用这个函数。
Numpy 方式与分段(内存重)
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 的长度相乘。
分段的“魔法”
现在分段将从条款中取出 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 对象列的 dtype 视为 datetime dtype,并且由于 timespan 不是,pandas 默默地拒绝合并它们。
如果我们实例化这些类的两个对象,现在可以比较它们:
pit = PointInTime(2015,1,1)
ts = Timespan(datetime(2014,1,1), datetime(2015,2,2))
pit == ts
True
我们还可以用这些对象填充两个 DataFrame:
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 仅包含在 Timespan 2015-2-1 -> 2015-2-5,所以这加强了我的上述假设。
如果有人有其他想法,请评论,我可以试试。
【讨论】:
__neq__
不行,你需要__ne__
2020年读到这里,“大流行”让人想起“大流行”。不过答案很好。
注意谁在使用 numpy picewise,如果不满足任何条件,则分段输入默认值 0。这可能会导致误导性行为【参考方案2】:
如果实现类似于 R 中 data.table 包中的 foverlaps() ,pandas 解决方案会很棒。到目前为止,我发现 numpy 的 piecewise() 是有效的。我根据之前的讨论提供了代码Merging dataframes based on date range
A['permno'] = np.piecewise(np.zeros(A.count()[0]),
[ (A['cusip'].values == id) & (A['fdate'].values >= start) & (A['fdate'].values <= end) for id, start, end in zip(B['ncusip'].values, B['namedf'].values, B['nameenddt'].values)],
B['permno'].values).astype(int)
【讨论】:
【参考方案3】:正如你所说,这在 SQL 中很容易,为什么不在 SQL 中做呢?
import pandas as pd
import sqlite3
#We'll use firelynx's tables:
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"])
#Make the db in memory
conn = sqlite3.connect(':memory:')
#write the tables
terms.to_sql('terms', conn, index=False)
presidents.to_sql('presidents', conn, index=False)
war_declarations.to_sql('wars', conn, index=False)
qry = '''
select
start_date PresTermStart,
end_date PresTermEnd,
wars.date WarStart,
presidents.name Pres
from
terms join wars on
date between start_date and end_date join presidents on
terms.president_id = presidents.president_id
'''
df = pd.read_sql_query(qry, conn)
df:
PresTermStart PresTermEnd WarStart Pres
0 2001-01-31 00:00:00 2005-01-31 00:00:00 2001-09-14 00:00:00 Bush
1 2001-01-31 00:00:00 2005-01-31 00:00:00 2003-03-03 00:00:00 Bush
【讨论】:
您是否介意针对此解决方案与其他解决方案的执行情况提供一些基准测试?这个答案感觉它根本没有回答问题,更像是规避问题,除非这实际上是一个高性能的解决方案 适合这项工作的工具。 我今天用这个想法解决了一个相关问题,效果很好。值得注意的是,对于大型数据集,您可能会通过为连接条件中使用的任何列创建索引来显着提高速度。 我只是用谷歌高级搜索这么久才重新找到这个答案。这解决了无数其他问题,我将在许多项目中使用它。多么棒的答案。 我发现这个解决方案比这里的解决方案快 10 倍以上:***.com/a/46526249/972136【参考方案4】:您现在应该可以使用包pandasql 执行此操作
import pandasql as ps
sqlcode = '''
select A.cusip
from A
inner join B on A.cusip=B.ncusip
where A.fdate >= B.namedt and A.fdate <= B.nameenddt
group by A.cusip
'''
newdf = ps.sqldf(sqlcode,locals())
我认为@ChuHo 的回答很好。我相信 pandasql 也在为你做同样的事情。我没有对这两者进行基准测试,但它更容易阅读。
【讨论】:
为什么不在 ON 条件中包含 where 条件:FROM A INNER JOIN B ON A.cusip = B.ncusip AND A.fdate>= B.namedt AND fdate @WaelHussein 我相信内部连接意味着它没关系。你应该做上面它是一个外连接。以上是关于合并一个值在另外两个之间的熊猫数据框[重复]的主要内容,如果未能解决你的问题,请参考以下文章