如何将数据框的字符串列拆分成两列?

371

我有一个包含一个(字符串)列的数据框,我希望将其拆分成两个(字符串)列,一个列标题为'fips',另一个为'row'

我的数据框df如下所示:

          row
0    00000 UNITED STATES
1    01000 ALABAMA
2    01001 Autauga County, AL
3    01003 Baldwin County, AL
4    01005 Barbour County, AL

我不知道如何使用df.row.str[:]来分割行单元格。我可以使用df['fips'] = hello添加新列并用hello填充它。有任何想法吗?

         fips       row
0    00000 UNITED STATES
1    01000 ALABAMA 
2    01001 Autauga County, AL
3    01003 Baldwin County, AL
4    01005 Barbour County, AL

4
你是如何将数据加载到 pandas 中的?你可以使用 read_table()read_fwf() 来以所需格式加载数据。 - zach
“如何拆分列”取决于列是字符串、列表还是其他格式(例如像地址一样的“格式化字符串”,您可能需要使用正则表达式)。在这里,您有一个固定宽度格式的字符串列(“ZZZZZ placename…”),因此我们知道邮政编码是字符0:4,地名是字符6:。 - smci
12个回答

781

简短版本:

对于简单的情况:

  • 我有一个带有分隔符的文本列,我想要两列

最简单的解决方案是:

df[['A', 'B']] = df['AB'].str.split(' ', n=1, expand=True)

如果您的字符串拆分数量不均匀,并且希望使用expand=TrueNone替换为缺失值,那么您必须使用expand=True

无论哪种情况,都不需要使用.tolist()方法。也不需要使用zip()

详细说明:

Andy Hayden的解决方案非常出色地展示了str.extract()方法的强大之处。

但是对于已知分隔符的简单拆分(例如,通过破折号拆分或通过空格拆分),.str.split()方法就足够了1。它适用于字符串的列(Series),并返回一个列表的列(Series):

>>> import pandas as pd
>>> df = pd.DataFrame({'AB': ['A1-B1', 'A2-B2']})
>>> df

      AB
0  A1-B1
1  A2-B2
>>> df['AB_split'] = df['AB'].str.split('-')
>>> df

      AB  AB_split
0  A1-B1  [A1, B1]
1  A2-B2  [A2, B2]

1:如果你不确定.str.split()的前两个参数是什么意思,我建议你查看纯Python版本的方法的文档。
但是,你如何从一个包含两个元素列表的列转换为包含各自元素的两列呢?
嗯,我们需要更仔细地看一下列的.str属性。
它是一个神奇的对象,用于收集将列中的每个元素视为字符串处理的方法,并在每个元素中尽可能高效地应用相应的方法。
>>> upper_lower_df = pd.DataFrame({"U": ["A", "B", "C"]})
>>> upper_lower_df

   U
0  A
1  B
2  C
>>> upper_lower_df["L"] = upper_lower_df["U"].str.lower()
>>> upper_lower_df

   U  L
0  A  a
1  B  b
2  C  c

但它还有一个“索引”接口,可以通过索引获取字符串的每个元素。
>>> df['AB'].str[0]

0    A
1    A
Name: AB, dtype: object

>>> df['AB'].str[1]

0    1
1    2
Name: AB, dtype: object

当然,这个.str的索引接口并不在乎它所索引的每个元素实际上是不是一个字符串,只要它可以被索引就可以。所以:
>>> df['AB'].str.split('-', 1).str[0]

0    A1
1    A2
Name: AB, dtype: object

>>> df['AB'].str.split('-', 1).str[1]

0    B1
1    B2
Name: AB, dtype: object

然后,只需要利用Python元组解包可迭代对象的简单方法来完成。
>>> df['A'], df['B'] = df['AB'].str.split('-', n=1).str
>>> df

      AB  AB_split   A   B
0  A1-B1  [A1, B1]  A1  B1
1  A2-B2  [A2, B2]  A2  B2

当然,从拆分字符串列中获取一个DataFrame非常有用,.str.split()方法可以通过expand=True参数为您完成此操作。
>>> df['AB'].str.split('-', n=1, expand=True)

    0   1
0  A1  B1
1  A2  B2

所以,实现我们想要的另一种方法是这样做:
>>> df = df[['AB']]
>>> df

      AB
0  A1-B1
1  A2-B2

>>> df.join(df['AB'].str.split('-', n=1, expand=True).rename(columns={0:'A', 1:'B'}))

      AB   A   B
0  A1-B1  A1  B1
1  A2-B2  A2  B2
expand=True版本虽然较长,但与元组解包方法相比具有明显优势。元组解包在处理不同长度的拆分时表现不佳。
>>> df = pd.DataFrame({'AB': ['A1-B1', 'A2-B2', 'A3-B3-C3']})
>>> df
         AB
0     A1-B1
1     A2-B2
2  A3-B3-C3
>>> df['A'], df['B'], df['C'] = df['AB'].str.split('-')
Traceback (most recent call last):
  [...]    
ValueError: Length of values does not match length of index
>>> 

但是expand=True很好地处理了这个问题,它会在没有足够的“分割”时,在列中放置None
>>> df.join(
...     df['AB'].str.split('-', expand=True).rename(
...         columns={0:'A', 1:'B', 2:'C'}
...     )
... )
         AB   A   B     C
0     A1-B1  A1  B1  None
1     A2-B2  A2  B2  None
2  A3-B3-C3  A3  B3    C3

1
df['A'],df['B'] = df['AB'].str.split(' ', 1).str 在split(' ', 1)中的'1'是表示分割出第一个空格之后的字符串。 - Hariprasad
1
@Hariprasad,这是最大分割数。我已经添加了一个链接到 Python版本.split()方法的文档,它比Pandas文档更好地解释了前两个参数。 - LeoRochael
9
pandas 1.0.0 报告了一个“FutureWarning: Columnar iteration over characters will be deprecated in future releases.”的警告。意思是在未来的版本中,对字符进行列迭代将被弃用。 - Frank
3
这段代码适用于Python 1.0.1版本。df.join(df['AB'].str.split('-', 1, expand=True).rename(columns={0:'A', 1:'B'}))该行代码的作用是将列名为'AB'的列按照'-'进行拆分,然后将拆分后的结果按照'A'和'B'两个列名进行重命名,最后与原始数据框df进行连接操作。 - Martien Lubberink
1
@DataGirl,我建议使用Series.str.extract和适当编写的正则表达式。 - LeoRochael
显示剩余14条评论

184

也许有更好的方法,但这里是一种方法:

                            row
    0       00000 UNITED STATES
    1             01000 ALABAMA
    2  01001 Autauga County, AL
    3  01003 Baldwin County, AL
    4  01005 Barbour County, AL
df = pd.DataFrame(df.row.str.split(' ',1).tolist(),
                                 columns = ['fips','row'])
   fips                 row
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

12
请注意,.tolist() 操作将删除原有的任何索引,因此你的新 DataFrame 将从 0 开始重新索引。(在你的具体情况下无关紧要) - Crashthatch
16
@Crashthatch -- 但是你只需添加 index = df.index 就可以了。 - root
1
如果一个单元格无法分裂怎么办? - Nisba
@Nisba:如果任何单元格无法被拆分(例如,字符串不包含任何空格),它仍然可以工作,但拆分的一部分将为空。 如果在列中混合了类型并且至少有一个单元格包含任何数字类型,则会发生其他情况。 然后 split 方法返回 NaN,而 tolist 方法将按原样返回此值(NaN),这将导致 ValueError(为克服此问题,您可以在拆分之前将其转换为字符串类型)。 我建议您自己尝试,这是最好的学习方式 :-) - Nerxis
1
@techkuz:你确定你的dfrow列标题吗?你可能认为它是某种DataFrame属性,但很明显这是列的名称。你可以自己创建和定义列标题,所以如果你使用不同的标题,请使用它(例如df.my_column_name.split(...))。 - Nerxis

79
您可以使用正则表达式模式相当整洁地提取不同的部分:extract
In [11]: df.row.str.extract('(?P<fips>\d{5})((?P<state>[A-Z ]*$)|(?P<county>.*?), (?P<state_code>[A-Z]{2}$))')
Out[11]: 
    fips                    1           state           county state_code
0  00000        UNITED STATES   UNITED STATES              NaN        NaN
1  01000              ALABAMA         ALABAMA              NaN        NaN
2  01001   Autauga County, AL             NaN   Autauga County         AL
3  01003   Baldwin County, AL             NaN   Baldwin County         AL
4  01005   Barbour County, AL             NaN   Barbour County         AL

[5 rows x 5 columns]
为了解释这个有些冗长的正则表达式:
(?P<fips>\d{5})
  • 匹配五个数字(\d),并将其命名为"fips"

接下来的部分:

((?P<state>[A-Z ]*$)|(?P<county>.*?), (?P<state_code>[A-Z]{2}$))

执行两个操作中的任意一个 (|).

(?P<state>[A-Z ]*$)
  • 匹配任意数量的大写字母或空格([A-Z ]),并在字符串结束前($)将其命名为"state"

(?P<county>.*?), (?P<state_code>[A-Z]{2}$))
  • 匹配任意字符 (.*) 然后
  • 逗号和一个空格,然后
  • 匹配字符串结尾前的两位数字 state_code ($)。

例如:
请注意,前两行匹配到了“state”(在县和state_code列中留下NaN),而最后三行匹配到了县、state_code(在state列中留下NaN)。


2
这绝对是最好的解决方案,但对于一些人来说,非常广泛的正则表达式可能有点压倒性。为什么不将其作为第二部分,并在第一部分中只使用fips和row列呢? - Little Bobby Tables
3
@josh,这是一个很好的观点。虽然正则表达式中的单个部分“容易”理解,但长的正则表达式很快就会变得复杂。我为未来的读者添加了一些说明!(我还不得不更新文档链接,以解释 (?P<label>...) 语法!我不知道为什么我选择了更复杂的正则表达式,显然简单的也可以工作。嗯嗯) - Andy Hayden
2
看起来友好多了。我很高兴你这样做,因为它让我查看文档以理解“<group_name>”。现在我知道了,它使我的代码非常简洁。 - Little Bobby Tables
但是这样做是否只返回重复模式的第一个匹配项,例如如果您使用“(\d +)”搜索数字,则仅返回123-456?换句话说,您必须知道要搜索的确切模式,它不能动态增长以适应123123-456123-456-789 - ruslaniv

55
df[['fips', 'row']] = df['row'].str.split(' ', n=1, expand=True)

30
你可以使用 str.split 函数来按空格(默认分隔符)进行分割,并使用参数expand=True将其分配到新的列中:DataFrame
df = pd.DataFrame({'row': ['00000 UNITED STATES', '01000 ALABAMA', 
                           '01001 Autauga County, AL', '01003 Baldwin County, AL', 
                           '01005 Barbour County, AL']})
print (df)
                        row
0       00000 UNITED STATES
1             01000 ALABAMA
2  01001 Autauga County, AL
3  01003 Baldwin County, AL
4  01005 Barbour County, AL



df[['a','b']] = df['row'].str.split(n=1, expand=True)
print (df)
                        row      a                   b
0       00000 UNITED STATES  00000       UNITED STATES
1             01000 ALABAMA  01000             ALABAMA
2  01001 Autauga County, AL  01001  Autauga County, AL
3  01003 Baldwin County, AL  01003  Baldwin County, AL
4  01005 Barbour County, AL  01005  Barbour County, AL

如果需要修改,可以使用DataFrame.pop来删除原始列。

df[['a','b']] = df.pop('row').str.split(n=1, expand=True)
print (df)
       a                   b
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

同样的是什么意思:

df[['a','b']] = df['row'].str.split(n=1, expand=True)
df = df.drop('row', axis=1)
print (df)

       a                   b
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL
如果出现错误:
#remove n=1 for split by all whitespaces
df[['a','b']] = df['row'].str.split(expand=True)

值错误:列的长度必须与键相同

您可以检查并返回4列的DataFrame,而不仅仅是2列:

print (df['row'].str.split(expand=True))
       0        1        2     3
0  00000   UNITED   STATES  None
1  01000  ALABAMA     None  None
2  01001  Autauga  County,    AL
3  01003  Baldwin  County,    AL
4  01005  Barbour  County,    AL

那么解决方案是通过join添加新的DataFrame

df = pd.DataFrame({'row': ['00000 UNITED STATES', '01000 ALABAMA', 
                           '01001 Autauga County, AL', '01003 Baldwin County, AL', 
                           '01005 Barbour County, AL'],
                    'a':range(5)})
print (df)
   a                       row
0  0       00000 UNITED STATES
1  1             01000 ALABAMA
2  2  01001 Autauga County, AL
3  3  01003 Baldwin County, AL
4  4  01005 Barbour County, AL

df = df.join(df['row'].str.split(expand=True))
print (df)

   a                       row      0        1        2     3
0  0       00000 UNITED STATES  00000   UNITED   STATES  None
1  1             01000 ALABAMA  01000  ALABAMA     None  None
2  2  01001 Autauga County, AL  01001  Autauga  County,    AL
3  3  01003 Baldwin County, AL  01003  Baldwin  County,    AL
4  4  01005 Barbour County, AL  01005  Barbour  County,    AL

删除原始列(如果还有其他列):

df = df.join(df.pop('row').str.split(expand=True))
print (df)
   a      0        1        2     3
0  0  00000   UNITED   STATES  None
1  1  01000  ALABAMA     None  None
2  2  01001  Autauga  County,    AL
3  3  01003  Baldwin  County,    AL
4  4  01005  Barbour  County,    AL   

1
如果有两个需要拆分的重叠列,它会返回以下错误信息:ValueError: columns overlap but no suffix specified: - learner

26

如果您不想创建新的数据框,或者您的数据框除了要拆分的列以外还有更多的列,您可以:

df["flips"], df["row_name"] = zip(*df["row"].str.split().tolist())
del df["row"]  

1
我遇到了一个“zip argument #1 must support iteration”错误,这是在Python 2.7中发生的。 - Allan Ruin

13

使用df.assign创建一个新的数据帧。请参见https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.assign.html

split = df_selected['name'].str.split(',', 1, expand=True)
df_split = df_selected.assign(first_name=split[0], last_name=split[1])
df_split.drop('name', 1, inplace=True)

或者以方法链的形式:

df_split = (df_selected
            .assign(list_col=lambda df: df['name'].str.split(',', 1, expand=False),
                    first_name=lambda df: df.list_col.str[0],
                    last_name=lambda df: df.list_col.str[1])
            .drop(columns=['list_col']))

12

如果您想根据分隔符将字符串拆分为超过两列,可以省略“最大拆分”参数。
您可以使用:

df['column_name'].str.split('/', expand=True)

这将自动创建与初始字符串中包含的任何字段的最大数量相同的列。


11

我很惊讶还没有看到这个。如果你只需要两次拆分,我强烈推荐使用……

Series.str.partition

partition在分隔符上执行一次拆分,并且通常非常高效。

df['row'].str.partition(' ')[[0, 2]]

       0                   2
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

如果您需要重命名行,请执行以下操作:

df['row'].str.partition(' ')[[0, 2]].rename({0: 'fips', 2: 'row'}, axis=1)

    fips                 row
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

如果您需要将其连接回原始值,请使用joinconcat

df.join(df['row'].str.partition(' ')[[0, 2]])

pd.concat([df, df['row'].str.partition(' ')[[0, 2]]], axis=1)

                        row      0                   2
0       00000 UNITED STATES  00000       UNITED STATES
1             01000 ALABAMA  01000             ALABAMA
2  01001 Autauga County, AL  01001  Autauga County, AL
3  01003 Baldwin County, AL  01003  Baldwin County, AL
4  01005 Barbour County, AL  01005  Barbour County, AL

3
我看到没有人使用过slice方法,所以我在这里提供我的建议。原始答案可以翻译为“最初的回答”。请注意保留HTML标签。
df["<col_name>"].str.slice(stop=5)
df["<col_name>"].str.slice(start=6)

这个方法会创建两个新的列。 "Original Answer" 翻译成 "最初的回答"。

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