如何根据两组列之间的值组合创建列?

Posted

技术标签:

【中文标题】如何根据两组列之间的值组合创建列?【英文标题】:How to create columns based on combinations of values between 2 sets of columns? 【发布时间】:2018-12-09 16:54:45 【问题描述】:

我有一个带有逗号分隔值的数据框,我使用 pd.concat 将其拆分出来。

原始df:

org    country         type
Orange   USA, GBR, AUS   OWF, PMR, KIQ
Red      AUS, RUS, NZL   DOG, MOP, LOF

拆分列会给我一个 df,我们称之为 df_wide,

org        country_1    country_2   country_3   type_1   type_2   type_3
Orange        USA          GBR         AUS         OWF      PMR      KIQ
Watermelon    AUS          RUS         NZL         ODG      MOP      LOF

从上面的数据框中,我需要以长格式获取单个国家和单个类型的所有可能组合:

org     country    type
Orange  USA        OWF
Orange  USA        PMR
Orange  USA        KIQ
Orange  GBR        OWF
Orange  GBR        PMR
Orange  GBR        KIQ

..等等

这就是我卡住的地方。我错误地认为我可以使用 pd.wide_to_long 转换数据框,但我认为我的答案围绕使用 itertools。我已经搜索了与此问题相关的论坛,但我仍然没有完全弄清楚。寻找任何建议!原始 df 列中的逗号分隔值也可能是几十个值,因此我不知道我的宽 df 将有多少列宽。

【问题讨论】:

【参考方案1】:

这是使用itertools.product 的一种解决方案。它不需要您创建的中间数据框。

from itertools import chain, product

df = pd.DataFrame('org': ['Orange', 'Red'],
                   'country': ['USA, GBR, AUS', 'AUS, RUS, NZL'],
                   'type': ['OWF, PMR, KIQ', 'DOG, MOP, LOF'])

split1 = df['country'].str.split(', ')
split2 = df['type'].str.split(', ')

lens = split1.map(len) * split2.map(len)

c_list, t_list = zip(*chain.from_iterable(map(product, split1, split2)))

res = pd.DataFrame('org': np.repeat(df['org'], lens),
                    'country': c_list,
                    'type': t_list)

说明

魔法发生在这行:

c_list, t_list = zip(*chain.from_iterable(map(product, split1, split2)))

由内而外的工作:

计算split1 / split2 中每对项目的笛卡尔积。 将它们链接在一起,形成一个非嵌套的可迭代结果。 解压并压缩到国家和类型。

结果

print(res)

      org country type
0  Orange     USA  OWF
0  Orange     USA  PMR
0  Orange     USA  KIQ
0  Orange     GBR  OWF
0  Orange     GBR  PMR
0  Orange     GBR  KIQ
0  Orange     AUS  OWF
0  Orange     AUS  PMR
0  Orange     AUS  KIQ
1     Red     AUS  DOG
1     Red     AUS  MOP
1     Red     AUS  LOF
1     Red     RUS  DOG
1     Red     RUS  MOP
1     Red     RUS  LOF
1     Red     NZL  DOG
1     Red     NZL  MOP
1     Red     NZL  LOF

【讨论】:

【参考方案2】:

只需借用 jpp 的设置,使用pd.MultiIndex.from_product

df['country'] = df['country'].str.split(', ')
df['type'] = df['type'].str.split(', ')
s=[pd.MultiIndex.from_product(x).tolist() for x in list(zip(df['country'],df['type']))]

df=pd.DataFrame('org':df.org.repeat(list(map(len,s)))).reset_index(drop=True)

df[['country','type']]=pd.DataFrame(sum(s,[]))
df
       org country type
0   Orange     USA  OWF
1   Orange     USA  PMR
2   Orange     USA  KIQ
3   Orange     GBR  OWF
4   Orange     GBR  PMR
5   Orange     GBR  KIQ
6   Orange     AUS  OWF
7   Orange     AUS  PMR
8   Orange     AUS  KIQ
9      Red     AUS  DOG
10     Red     AUS  MOP
11     Red     AUS  LOF
12     Red     RUS  DOG
13     Red     RUS  MOP
14     Red     RUS  LOF
15     Red     NZL  DOG
16     Red     NZL  MOP
17     Red     NZL  LOF

【讨论】:

【参考方案3】:

我从设置 df 开始:

import pandas
records = [
    
        "org": "Orange",
        "country_1": "USA",
        "country_2": "GBR",
        "country_3": "AUS",
        "type_1": "OWF",
        "type_2": "PMR",
        "type_3": "KIQ"
    ,
    
        "org": "Watermelon",
        "country_1": "AUS",
        "country_2": "RUS",
        "country_3": "NZL",
        "type_1": "ODG",
        "type_2": "MOP",
        "type_3": "LOF"
    
]

df = pandas.DataFrame(records)

首先,您可以使用pandas.DataFrame.filter 方法通过正则表达式选择列(如here 所示):

>>> df_countries = df.filter(regex=("country_.*"))
  country_1 country_2 country_3
0       USA       GBR       AUS
1       AUS       RUS       NZL

>>> df_types = df.filter(regex=("type_.*"))
  type_1 type_2 type_3
0    OWF    PMR    KIQ
1    ODG    MOP    LOF

然后你可以得到所有独特的国家和类型:

>>> countries_all = df_countries.values.flatten()
array(['USA', 'GBR', 'AUS', 'AUS', 'RUS', 'NZL'], dtype=object)
>>> types_all = df_types.values.flatten()
array(['OWF', 'PMR', 'KIQ', 'ODG', 'MOP', 'LOF'], dtype=object)

然后将它们组合起来就是使用来自itertools 的笛卡尔积:

>>> pandas.DataFrame(list(itertools.product(*[list(countries_all), list(types_all)])))
      0    1
0   USA  OWF
1   USA  PMR
2   USA  KIQ
3   USA  ODG
4   USA  MOP
5   USA  LOF
6   GBR  OWF
7   GBR  PMR
8   GBR  KIQ
9   GBR  ODG
10  GBR  MOP
11  GBR  LOF
12  AUS  OWF
13  AUS  PMR
14  AUS  KIQ
15  AUS  ODG
16  AUS  MOP
17  AUS  LOF
18  AUS  OWF
19  AUS  PMR
20  AUS  KIQ
21  AUS  ODG
22  AUS  MOP
23  AUS  LOF
24  RUS  OWF
25  RUS  PMR
26  RUS  KIQ
27  RUS  ODG
28  RUS  MOP
29  RUS  LOF
30  NZL  OWF
31  NZL  PMR
32  NZL  KIQ
33  NZL  ODG
34  NZL  MOP
35  NZL  LOF

现在我知道您可能希望按照 org 执行此操作,在这种情况下,我会在执行过滤器之前对数据帧进行子集化:

orgs = pandas.unique(df["org"])
for org in orgs:
    df_org = df[df["org"] == org]
    df_countries = df_org.filter(regex=("country_.*"))
    df_types = df_org.filter(regex=("type_.*"))
    # do rest of the process here and concatenate in the end through `pandas.concat`

希望对你有帮助

【讨论】:

以上是关于如何根据两组列之间的值组合创建列?的主要内容,如果未能解决你的问题,请参考以下文章

如何在pyspark中对一组列进行分桶?

根据另一列的位置从一组列中返回值

Excel如何比较2列范围

如何编写 R 脚本来检查直线;即,对于任何给定的行,一组列中的所有值是不是具有相同的值

如何按对象计算熊猫组列中的不同值?

在 Pandas 中按列名选择两组列