当前位置: 首页 > 知识库问答 >
问题:

拆分(爆炸)熊猫数据帧字符串条目以分离行

璩珂
2023-03-14

我有一个数据框,其中一列文本字符串包含逗号分隔的值。我想分割每个CSV字段,并为每个条目创建一个新行(假设CSV是干净的,只需要在“,”上分割)。例如,a应该变成b

In [7]: a
Out[7]: 
    var1  var2
0  a,b,c     1
1  d,e,f     2

In [8]: b
Out[8]: 
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

到目前为止,我已经尝试了各种简单的函数,但是.apply方法在轴上使用时似乎只接受一行作为返回值,并且我无法让.transform工作。任何建议都将不胜感激!

示例数据:

from pandas import DataFrame
import numpy as np
a = DataFrame([{'var1': 'a,b,c', 'var2': 1},
               {'var1': 'd,e,f', 'var2': 2}])
b = DataFrame([{'var1': 'a', 'var2': 1},
               {'var1': 'b', 'var2': 1},
               {'var1': 'c', 'var2': 1},
               {'var1': 'd', 'var2': 2},
               {'var1': 'e', 'var2': 2},
               {'var1': 'f', 'var2': 2}])

我知道这是行不通的,因为我们失去了DataFrame元数据通过通过Numpy,但它应该给你一个感觉,我试图做什么:

def fun(row):
    letters = row['var1']
    letters = letters.split(',')
    out = np.array([row] * len(letters))
    out['var1'] = letters
a['idx'] = range(a.shape[0])
z = a.groupby('idx')
z.transform(fun)

共有3个答案

洪育
2023-03-14

Series和DataFrame方法定义一个.爆炸()方法,该方法将列表爆炸成单独的行。请参阅关于爆炸类似列表的列的文档部分。

由于您有一个以逗号分隔的字符串列表,所以将字符串拆分为逗号以获得元素列表,然后在该列上调用爆炸

df = pd.DataFrame({'var1': ['a,b,c', 'd,e,f'], 'var2': [1, 2]})
df
    var1  var2
0  a,b,c     1
1  d,e,f     2

df.assign(var1=df['var1'].str.split(',')).explode('var1')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
1    f     2

请注意,爆炸仅适用于单个列(目前)。要同时爆炸多个列,请参阅下文。

南斯和空名单得到了他们应得的待遇,而你不必费尽心机去做正确的事情。

df = pd.DataFrame({'var1': ['d,e,f', '', np.nan], 'var2': [1, 2, 3]})
df
    var1  var2
0  d,e,f     1
1            2
2    NaN     3

df['var1'].str.split(',')

0    [d, e, f]
1           []
2          NaN

df.assign(var1=df['var1'].str.split(',')).explode('var1')

  var1  var2
0    d     1
0    e     1
0    f     1
1          2  # empty list entry becomes empty string after exploding 
2  NaN     3  # NaN left un-touched

与基于ravel/repeat的解决方案(完全忽略空列表,并阻塞NAN)相比,这是一个巨大的优势。

请注意,explode一次仅对一列有效,但您可以使用apply一次分解多列:

df = pd.DataFrame({'var1': ['a,b,c', 'd,e,f'], 
                   'var2': ['i,j,k', 'l,m,n'], 
                   'var3': [1, 2]})
df
    var1   var2  var3
0  a,b,c  i,j,k     1
1  d,e,f  l,m,n     2

(df.set_index(['var3']) 
   .apply(lambda col: col.str.split(',').explode())
   .reset_index()
   .reindex(df.columns, axis=1))

df
  var1 var2  var3
0    a    i     1
1    b    j     1
2    c    k     1
3    d    l     2
4    e    m     2
5    f    n     2

其思想是将所有不应分解的列设置为索引,然后通过apply分解其余的列。当列表大小相同时,这种方法效果很好。

田志
2023-03-14

在痛苦的实验之后,我找到了一个比公认答案更快的答案,我成功了。它在我试用过的数据集上运行速度快了100倍左右。

如果有人知道如何使其更加优雅,请务必修改我的代码。我找不到一种方法,如果不设置您想要保留的其他列作为索引,然后重置索引并重新命名列,就可以工作,但是我想还有其他方法可以工作。

b = DataFrame(a.var1.str.split(',').tolist(), index=a.var2).stack()
b = b.reset_index()[[0, 'var2']] # var1 variable is currently labeled 0
b.columns = ['var1', 'var2'] # renaming var1
蒋航
2023-03-14

更新3:使用Series.explode()/DataFrame.explode()方法(在Pandas 0.25.0中实现,在Pandas 1.3.0中扩展以支持多列分解)更有意义,如使用示例所示:

对于单个列:

In [1]: df = pd.DataFrame({'A': [[0, 1, 2], 'foo', [], [3, 4]],
   ...:                    'B': 1,
   ...:                    'C': [['a', 'b', 'c'], np.nan, [], ['d', 'e']]})

In [2]: df
Out[2]:
           A  B          C
0  [0, 1, 2]  1  [a, b, c]
1        foo  1        NaN
2         []  1         []
3     [3, 4]  1     [d, e]

In [3]: df.explode('A')
Out[3]:
     A  B          C
0    0  1  [a, b, c]
0    1  1  [a, b, c]
0    2  1  [a, b, c]
1  foo  1        NaN
2  NaN  1         []
3    3  1     [d, e]
3    4  1     [d, e]

对于多列(适用于1.3.0):

In [4]: df.explode(['A', 'C'])
Out[4]:
     A  B    C
0    0  1    a
0    1  1    b
0    2  1    c
1  foo  1  NaN
2  NaN  1  NaN
3    3  1    d
3    4  1    e

更新2:更通用的矢量化函数,可用于多个normal和多个list

def explode(df, lst_cols, fill_value='', preserve_index=False):
    # make sure `lst_cols` is list-alike
    if (lst_cols is not None
        and len(lst_cols) > 0
        and not isinstance(lst_cols, (list, tuple, np.ndarray, pd.Series))):
        lst_cols = [lst_cols]
    # all columns except `lst_cols`
    idx_cols = df.columns.difference(lst_cols)
    # calculate lengths of lists
    lens = df[lst_cols[0]].str.len()
    # preserve original index values    
    idx = np.repeat(df.index.values, lens)
    # create "exploded" DF
    res = (pd.DataFrame({
                col:np.repeat(df[col].values, lens)
                for col in idx_cols},
                index=idx)
             .assign(**{col:np.concatenate(df.loc[lens>0, col].values)
                            for col in lst_cols}))
    # append those rows that have empty lists
    if (lens == 0).any():
        # at least one list in cells is empty
        res = (res.append(df.loc[lens==0, idx_cols], sort=False)
                  .fillna(fill_value))
    # revert the original index order
    res = res.sort_index()
    # reset index if requested
    if not preserve_index:        
        res = res.reset_index(drop=True)
    return res

演示:

多个列表列-所有列表列在每行中必须具有相同的元素:

In [134]: df
Out[134]:
   aaa  myid        num          text
0   10     1  [1, 2, 3]  [aa, bb, cc]
1   11     2         []            []
2   12     3     [1, 2]      [cc, dd]
3   13     4         []            []

In [135]: explode(df, ['num','text'], fill_value='')
Out[135]:
   aaa  myid num text
0   10     1   1   aa
1   10     1   2   bb
2   10     1   3   cc
3   11     2
4   12     3   1   cc
5   12     3   2   dd
6   13     4

保留原始索引值:

In [136]: explode(df, ['num','text'], fill_value='', preserve_index=True)
Out[136]:
   aaa  myid num text
0   10     1   1   aa
0   10     1   2   bb
0   10     1   3   cc
1   11     2
2   12     3   1   cc
2   12     3   2   dd
3   13     4

设置:

df = pd.DataFrame({
 'aaa': {0: 10, 1: 11, 2: 12, 3: 13},
 'myid': {0: 1, 1: 2, 2: 3, 3: 4},
 'num': {0: [1, 2, 3], 1: [], 2: [1, 2], 3: []},
 'text': {0: ['aa', 'bb', 'cc'], 1: [], 2: ['cc', 'dd'], 3: []}
})

CSV列:

In [46]: df
Out[46]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [47]: explode(df.assign(var1=df.var1.str.split(',')), 'var1')
Out[47]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

使用此小技巧,我们可以将类似CSV的列转换为list列:

In [48]: df.assign(var1=df.var1.str.split(','))
Out[48]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

更新:通用矢量化方法(也适用于多列):

原始DF:

In [177]: df
Out[177]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

解决方案:

首先让我们将CSV字符串转换为列表:

In [178]: lst_col = 'var1' 

In [179]: x = df.assign(**{lst_col:df[lst_col].str.split(',')})

In [180]: x
Out[180]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

现在我们可以这样做:

In [181]: pd.DataFrame({
     ...:     col:np.repeat(x[col].values, x[lst_col].str.len())
     ...:     for col in x.columns.difference([lst_col])
     ...: }).assign(**{lst_col:np.concatenate(x[lst_col].values)})[x.columns.tolist()]
     ...:
Out[181]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

旧答案:

受@AFinkelstein解决方案的启发,我想让它更加泛化,可以应用于具有两列以上的DF,并且像AFinkelstein的解决方案一样快,几乎一样快):

In [2]: df = pd.DataFrame(
   ...:    [{'var1': 'a,b,c', 'var2': 1, 'var3': 'XX'},
   ...:     {'var1': 'd,e,f,x,y', 'var2': 2, 'var3': 'ZZ'}]
   ...: )

In [3]: df
Out[3]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [4]: (df.set_index(df.columns.drop('var1',1).tolist())
   ...:    .var1.str.split(',', expand=True)
   ...:    .stack()
   ...:    .reset_index()
   ...:    .rename(columns={0:'var1'})
   ...:    .loc[:, df.columns]
   ...: )
Out[4]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ
 类似资料:
  • 问题内容: 我有一个文本字符串的一列包含逗号分隔的值。我想拆分每个CSV字段并为每个条目创建一个新行(假定CSV干净,只需要在’,’上拆分)。例如,a应变为b: 到目前为止,我已经尝试了各种简单的函数,但是该.apply方法似乎只在轴上使用一行作为返回值,而我无法开始.transform工作。我们欢迎所有的建议! 示例数据: 我知道这是行不通的,因为我们通过numpy丢失了DataFrame元数据

  • 我已经用下面的代码获得了训练集和测试集的索引。 我想知道是否有更快的方法可以使用我检索到的行索引将它们分别分成两个数据帧。

  • 问题内容: 我有一个字符串,例如: “ 0123456789” 并且需要将每个EACH字符拆分为一个数组。 我为它的地狱尝试: 但这给了我显而易见的 警告:警告:爆炸中未定义分隔符 。 我怎么会遇到这个?我看不到任何可用的方法,尤其是一个函数 问题答案: str_split采用可选的第二个参数,即块长度(默认为1),因此您可以执行以下操作: 您还可以通过将字符串视为数组来获得字符串的一部分:

  • 问题内容: 我有一个问题,我有一个字符串数组,并且我想在其他定界符中爆炸。例如 我需要一个在@或vs中爆炸的数组。 我已经写了一个解决方案,但是如果每个人都有更好的解决方案,请在此处发布。 问题答案: 怎么用

  • 问题内容: 简单吧?好吧,这不起作用:-\ 问题答案: 最佳实践 如第一个答案的注释中所述,最佳实践是使用PHP常量PHP_EOL,它代表当前系统的EOL(行 尾 )。 警告 这些常数使您的页面系统独立,但是当将常数与存储在另一系统上的数据一起使用时,从一个系统移动到另一个系统时可能会遇到问题。新系统的常数可能与先前系统的常数不同,并且存储的数据可能不再起作用。因此,在存储数据之前先完全解析您的数

  • 问题 你想拆分一个字符串。 解决方案 使用 JavaScript 字符串的 split() 方法: "foo bar baz".split " " # => [ 'foo', 'bar', 'baz' ] 讨论 String 的这个 split() 方法是标准的 JavaScript 方法。可以用来基于任何分隔符——包括正则表达式来拆分字符串。这个方法还可以接受第二个参数,用于指定返回的子字符串数