【问题标题】:How to merge with wildcard? - Pandas如何与通配符合并? - 熊猫
【发布时间】:2018-05-08 09:44:24
【问题描述】:

我有两个要合并的数据框。右侧数据框的连接列可能包含 通配符 值(例如:“ALL”),该值应与左侧数据框连接列中的 每个 值匹配。

想想下面这个最小的例子:

entities = pandas.DataFrame.from_dict([
    { 'name' : 'Boson', 'type' : 'Material' },
    { 'name' : 'Atman', 'type' : 'Ideal' },
])

recommendations = pandas.DataFrame.from_dict([
    { 'action' : 'recognize', 'entity_type' : 'ALL'},
    { 'action' : 'disdain', 'entity_type' : 'Material'},
    { 'action' : 'worship', 'entity_type' : 'Ideal'},
])

recommendations 可以解释为一组建议 “认识所有实体,无论其类型如何,蔑视物质实体,崇拜理想实体”)。我现在想要一个包含所有实体及其推荐操作的数据框。因此,在此示例中,生成的数据框应该看起来像

    name recommendation      type
0  Boson      recognize  Material
1  Boson        disdain  Material
2  Atman      recognize     Ideal
3  Atman        worship     Ideal

有没有熊猫的方法可以做到这一点?

我知道如何通过创建一个包含entitiesrecommendations 的笛卡尔积的数据框然后根据条件将其削减。

我还可以想到一个解决方案,让我得到entitities 中存在的所有types 的系列,并为recommendations 中的每一行使用通配符类型为每种类型创建一行。

但在我真正的问题中,我实际上有多个列,我想用通配符值加入这些列。所以一个聪明高效的熊猫方式对我有很大帮助。

【问题讨论】:

  • 我要说的是,您可以将建议分成两个框架:一个有通配符,一个没有通配符。但是你说你有很多包含通配符的列的最后一部分使这没有吸引力。我认为您最好只遍历列并构建一个扩展所有通配符的大框架。生成的扩展框架是否会太大而无法实用?
  • @JohnZwinck 我认为,你所说的“一个扩展了所有通配符的大框架”的意思与我想用“我得到实体中存在的一系列所有类型的解决方案”相同并为具有通配符类型的推荐中的每一行创建每一行”。是的,我可以这样做。但最好有一个使用 pandas dataFrame 结构的函数。如果我这样做,我觉得失去了 pandas.merge 的优势。但当然有可能,
  • @JohnZwinck 我的方法摆脱了迭代你怎么看?

标签: python pandas join merge wildcard


【解决方案1】:

一种可能的解决方案是我从所有其他元素中替换通配符,然后将它们合并,即

数据

edf = pd.DataFrame.from_dict([
    { 'name' : 'Boson', 'type' : 'Material' },
    { 'name' : 'Atman', 'type' : 'Ideal' },
])

rdf = pd.DataFrame.from_dict([
    { 'action' : 'recognize', 'entity_type' : 'ALL'},
    { 'action' : 'disdain', 'entity_type' : 'Material'},
    { 'action' : 'worship', 'entity_type' : 'Ideal'},
])

预处理

mask = rdf['entity_type']=='ALL'

# Join all the elements from `edf['type']` with `;` since you might have `,`s in types and we need to use set to get rid of duplicates (Thank you @John  )
all_ =  ';'.join(set(edf['type'])) # all_ : Material,Ideal

# Replace all by newly obatined string 
rdf['entity_type'] = np.where(mask,all_,rdf['entity_type'])

rdf
      action     entity_type
0  recognize  Material;Ideal
1    disdain        Material
2    worship           Ideal

# Split and stack so we can make `entity_type` one dimensional
rdf = rdf.set_index('action')['entity_type'].str.split(';',expand=True)\
        .stack().reset_index('action').rename(columns={0:'type'})

rdf
          action     type
 0  recognize    Material
 1  recognize       Ideal
 0    disdain    Material
 0    worship       Ideal

合并

ndf = edf.merge(rdf,on='type').rename(columns={'action':'recommendation'})

ndf

   name      type recommendation
0  Boson  Material      recognize
1  Boson  Material        disdain
2  Atman     Ideal      recognize
3  Atman     Ideal        worship

在不同数据帧上运行的示例:

edf = pd.DataFrame.from_dict([
    { 'name' : 'Boson', 'type' : 'Material' },
    { 'name' : 'Atman', 'type' : 'Ideal' },
    { 'name' : 'Chaos', 'type' : 'Void, but emphasized' },
    { 'name' : 'Tohuwabohu', 'type' : 'Void' },
]) 

rdf = pd.DataFrame.from_dict([
    { 'action' : 'recognize', 'entity_type' : 'ALL'},
    { 'action' : 'disdain', 'entity_type' : 'Material'},
    { 'action' : 'worship', 'entity_type' : 'Ideal'},
    { 'action' : 'drink', 'entity_type' : 'ALL'}
])

然后:

mask = rdf['entity_type']=='ALL'
all_ =  ';'.join(set(edf['type']))
rdf['entity_type'] = np.where(mask,all_,rdf['entity_type'])

rdf = rdf.set_index('action')['entity_type'].str.split(';',expand=True)\
        .stack().reset_index('action').rename(columns={0:'type'})
ndf = edf.merge(rdf,on='type').rename(columns={'action':'recommendation'})

ndf

         name                  type recommendation
0       Boson              Material      recognize
1       Boson              Material        disdain
2       Boson              Material          drink
3       Atman                 Ideal      recognize
4       Atman                 Ideal        worship
5       Atman                 Ideal          drink
6       Chaos  Void, but emphasized      recognize
7       Chaos  Void, but emphasized          drink
8  Tohuwabohu                  Void      recognize
9  Tohuwabohu                  Void          drink

与笛卡尔积相比,这种方法速度快,消耗的内存更少。希望对你有帮助:)

【讨论】:

  • 我猜你应该在构造all_时使用.unique(),否则你可能会得到重复。
  • 这接近解决方案。但如果rdf 不包含所有实体类型,例如rdf = pd.DataFrame.from_dict([ { 'action' : 'recognize', 'entity_type' : 'ALL'}]),它就会失败。所以,实体类型应该取自edf,而不是rdf。我知道,我可能不是 100% 清楚地在我的问题中指定这一点。但是对于 wildcard,我的意思是“与左表连接列中的每个(更好地说:arbirtrary)值匹配的值”。
  • 正如我所说,很难找到适合此类问题的所有案例的解决方案。您可能需要开放赏金以获得最佳解决方案。这就是我能提供的所有帮助。我需要写作业。祝你有美好的一天
  • 我正在考虑一种涉及 str.get_dummies 的方法......仍在测试但不能保证。这似乎是最可行的解决方案,即使它有点难看。
  • @Barath:只是交换角色只会转移问题。我认为,最好先转义我们在type 列中使用的分隔符,然后再应用通配符合并然后取消转义。
【解决方案2】:

考虑到这一点,我认为使用两个数据帧的笛卡尔积的方式可能并没有我之前想的那么糟糕。因此,对于将来阅读此主题的任何人,我只想展示如何做到这一点:

# get Cartesian Product of the two dfs
entities['join'] = recommendations['join'] = 0
results = entities.merge(recommendations, on='join')
# extract matching rows
results = results[(( results['type'] == results['entity_type']) | (results['entity_type'] == "ALL"))]
results = results[['name', 'type', 'action']]

有输入

entities = pd.DataFrame.from_dict([
    { 'name' : 'Boson', 'type' : 'Material' },
    { 'name' : 'Atman', 'type' : 'Ideal' },
    { 'name' : 'Chaos', 'type' : 'Void, but emphasized' },
    { 'name' : 'Tohuwabohu', 'type' : 'Void' },
])

recommendations = pd.DataFrame.from_dict([
    { 'action' : 'recognize', 'entity_type' : 'ALL'},
    { 'action' : 'disdain', 'entity_type' : 'Material'},
    { 'action' : 'disdain', 'entity_type' : 'Void'},
    { 'action' : 'worship', 'entity_type' : 'Ideal'},
])

这导致results

          name                  type     action
0        Boson              Material  recognize
1        Boson              Material    disdain
4        Atman                 Ideal  recognize
7        Atman                 Ideal    worship
8        Chaos  Void, but emphasized  recognize
12  Tohuwabohu                  Void  recognize
14  Tohuwabohu                  Void    disdain

【讨论】:

  • 但也要确保如果你有一个巨大的数据帧,这会很慢,消耗大量的内存,因为你正在做笛卡尔积
猜你喜欢
  • 2020-10-26
  • 2018-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-25
  • 2017-06-12
  • 1970-01-01
  • 2017-02-11
相关资源
最近更新 更多