拆分(分解)pandas 数据帧字符串条目以分隔行

Split (explode) pandas dataframe string entry to separate rows

提问人:Vincent 提问时间:10/2/2012 最后编辑:MaxU - stand with UkraineVincent 更新时间:1/5/2023 访问量:406019

问:

我有一个文本字符串列包含逗号分隔的值。我想拆分每个 CSV 字段并为每个条目创建一个新行(假设 CSV 是干净的,只需要在“,”上拆分)。例如,应变为:pandas dataframeab

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}])

我知道这是行不通的,因为我们通过numpy丢失了DataFrame元数据,但它应该让你了解我试图做什么:

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)
python pandas numpy 数据帧

评论

3赞 desaiankitb 11/28/2018
此页面上的其他解决方案正在工作,但我发现以下一个简短而有效的解决方案。stackoverflow.com/questions/27263805/......
2赞 Sos 12/15/2018
对于到达此页面并寻找保留多列的解决方案的其他人,请查看以下问题:stackoverflow.com/questions/17116814/...

答:

104赞 Chang She 10/2/2012 #1

像这样的东西怎么样:

In [55]: pd.concat([Series(row['var2'], row['var1'].split(','))              
                    for _, row in a.iterrows()]).reset_index()
Out[55]: 
  index  0
0     a  1
1     b  1
2     c  1
3     d  2
4     e  2
5     f  2

然后,您只需要重命名列即可

评论

1赞 Vincent 10/2/2012
看起来这会起作用。感谢您的帮助!但是,一般来说,是否有一种首选的 Split-Apply-Combine 方法,其中 Apply 返回任意大小的数据帧(但对于所有块都是一致的),而 Combine 仅对返回的 DF 进行 vstack?
1赞 horatio1701d 6/26/2014
嘿,伙计们。很抱歉这么晚才进入这个,但想知道是否有更好的解决方案。我第一次尝试尝试迭代,因为这似乎是这张票。我也对提出的解决方案感到困惑。“_”代表什么?您能解释一下解决方案是如何工作的吗?--谢谢
18赞 horatio1701d 6/26/2014
该解决方案可以扩展到两根以上的色谱柱吗?
1赞 MaxU - stand with Ukraine 2/3/2017
请检查此矢量化方法...
5赞 bernando_vialli 7/18/2019
为什么这有时有效(在某些数据帧上),但似乎不适用于其他数据帧?我已经让它在一个数据集上工作,但是现在在另一个数据集上尝试时,我得到“NameError: name 'Series' is not defined”
145赞 DMulligan 1/28/2015 #2

经过痛苦的实验,找到比公认的答案更快的东西,我让它起作用了。在我尝试的数据集上,它的运行速度快了大约 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

评论

4赞 cyril 4/15/2017
该解决方案的运行速度明显更快,并且似乎使用更少的内存,
3赞 Dennis Golomazov 1/6/2018
这是一个不错的矢量化熊猫解决方案,我一直在寻找它。谢谢!
1赞 user5359531 8/24/2018
当我在自己的数据集上尝试这个时,我一直在第一步(TypeError: object of type 'float' has no len()DataFrame(df.var1.str.split(',').tolist()))
0赞 Flair 10/2/2018
@user5359531您的数据集中可能有一些,因此替换是NaNb = DataFrame(a.var1.str.split(',').values.tolist(), index=a.var2).stack()
1赞 hhbilly 5/21/2019
仅供参考,这里有一个很好的例子,上面写着这个解决方案。
0赞 Pavel 3/18/2015 #3

对于这个问题,我想出了以下解决方案:

def iter_var1(d):
    for _, row in d.iterrows():
        for v in row["var1"].split(","):
            yield (v, row["var2"])

new_a = DataFrame.from_records([i for i in iter_var1(a)],
        columns=["var1", "var2"])
8赞 jlln 4/21/2015 #4

我为具有任意列数的数据帧提出了一个解决方案(同时一次仍然只分隔一列的条目)。

def splitDataFrameList(df,target_column,separator):
    ''' df = dataframe to split,
    target_column = the column containing the values to split
    separator = the symbol used to perform the split

    returns: a dataframe with each entry for the target column separated, with each element moved into a new row. 
    The values in the other columns are duplicated across the newly divided rows.
    '''
    def splitListToRows(row,row_accumulator,target_column,separator):
        split_row = row[target_column].split(separator)
        for s in split_row:
            new_row = row.to_dict()
            new_row[target_column] = s
            row_accumulator.append(new_row)
    new_rows = []
    df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator))
    new_df = pandas.DataFrame(new_rows)
    return new_df

评论

3赞 MAQ 6/23/2016
不错,但可悲的是,由于这个 todict() 转换:(
19赞 inodb 6/25/2015 #5

与以下问题类似的问题: pandas:如何将一列中的文本拆分为多行?

您可以执行以下操作:

>> a=pd.DataFrame({"var1":"a,b,c d,e,f".split(),"var2":[1,2]})
>> s = a.var1.str.split(",").apply(pd.Series, 1).stack()
>> s.index = s.index.droplevel(-1)
>> del a['var1']
>> a.join(s)
   var2 var1
0     1    a
0     1    b
0     1    c
1     2    d
1     2    e
1     2    f

评论

3赞 Jesse 6/4/2017
再添加一个重命名代码后即可工作s.name = 'var1'
1赞 Derryn Webster-Knife 6/19/2016 #6

刚刚使用了上面 jiln 的出色答案,但需要扩展以拆分多列。以为我会分享。

def splitDataFrameList(df,target_column,separator):
''' df = dataframe to split,
target_column = the column containing the values to split
separator = the symbol used to perform the split

returns: a dataframe with each entry for the target column separated, with each element moved into a new row. 
The values in the other columns are duplicated across the newly divided rows.
'''
def splitListToRows(row, row_accumulator, target_columns, separator):
    split_rows = []
    for target_column in target_columns:
        split_rows.append(row[target_column].split(separator))
    # Seperate for multiple columns
    for i in range(len(split_rows[0])):
        new_row = row.to_dict()
        for j in range(len(split_rows)):
            new_row[target_columns[j]] = split_rows[j][i]
        row_accumulator.append(new_row)
new_rows = []
df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator))
new_df = pd.DataFrame(new_rows)
return new_df
56赞 Daniel Himmelstein 10/10/2016 #7

这是我为这个常见任务编写的一个函数。它比 / 方法更有效。保留列顺序和名称。Seriesstack

def tidy_split(df, column, sep='|', keep=False):
    """
    Split the values of a column and expand so the new DataFrame has one split
    value per row. Filters rows where the column is missing.

    Params
    ------
    df : pandas.DataFrame
        dataframe with the column to split and expand
    column : str
        the column to split and expand
    sep : str
        the string used to split the column's values
    keep : bool
        whether to retain the presplit value as it's own row

    Returns
    -------
    pandas.DataFrame
        Returns a dataframe with the same columns as `df`.
    """
    indexes = list()
    new_values = list()
    df = df.dropna(subset=[column])
    for i, presplit in enumerate(df[column].astype(str)):
        values = presplit.split(sep)
        if keep and len(values) > 1:
            indexes.append(i)
            new_values.append(presplit)
        for value in values:
            indexes.append(i)
            new_values.append(value)
    new_df = df.iloc[indexes, :].copy()
    new_df[column] = new_values
    return new_df

有了这个函数,原来的问题就这么简单了:

tidy_split(a, 'var1', sep=',')

评论

2赞 Anurag N. Sharma 6/10/2020
这太快了!非常感谢。
250赞 MaxU - stand with Ukraine 11/6/2016 #8

更新 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]

对于多个列(对于 Pandas 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:更通用的矢量化函数,适用于多列和多列normallist

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

演示:

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

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

评论

25赞 BENY 9/2/2017
伙计,如果你能在 Git pandas 中打开讨论,我认为我们确实需要这样的内置函数!!我看到了很多关于在 SO 中为熊猫取消列出和取消嵌套的问题
1赞 Jaskaran Singh Puri 8/21/2018
如何将其用于多个列。就像我在 2 列中用逗号分隔的数据并想按顺序执行一样?
1赞 Guido 11/28/2018
不幸的是,如果你的列表元素是元组,它就不起作用。但是在将整个元组转换为字符串后,它就像一个魅力!
9赞 cs95 7/20/2019
看起来 WenBen 的请求被熊猫神听到了,他们已经在 API 中安装了 a.explode() 方法(另请参阅此答案)。
0赞 Ankit Maheshwari 6/18/2017 #9

另一个使用 python 复制包的解决方案

import copy
new_observations = list()
def pandas_explode(df, column_to_explode):
    new_observations = list()
    for row in df.to_dict(orient='records'):
        explode_values = row[column_to_explode]
        del row[column_to_explode]
        if type(explode_values) is list or type(explode_values) is tuple:
            for explode_value in explode_values:
                new_observation = copy.deepcopy(row)
                new_observation[column_to_explode] = explode_value
                new_observations.append(new_observation) 
        else:
            new_observation = copy.deepcopy(row)
            new_observation[column_to_explode] = explode_values
            new_observations.append(new_observation) 
    return_df = pd.DataFrame(new_observations)
    return return_df

df = pandas_explode(df, column_name)
4赞 Ted Petrou 11/5/2017 #10

这是一条相当简单的消息,它使用 pandas 访问器中的方法,然后使用 NumPy 将每一行展平为单个数组。splitstr

通过使用 重复非拆分列正确的次数来检索相应的值。np.repeat

var1 = df.var1.str.split(',', expand=True).values.ravel()
var2 = np.repeat(df.var2.values, len(var1) / len(df))

pd.DataFrame({'var1': var1,
              'var2': var2})

  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

评论

1赞 Michael Dorner 6/21/2018
这可能是一个非常美丽的答案。不幸的是,它不能缩放很多列,是吗?
2赞 Dennis Golomazov 1/6/2018 #11

基于出色的 @DMulligan 解决方案,这里有一个通用的矢量化(无循环)函数,它将 DataFrame 的一列拆分为多行,并将其合并回原始 DataFrame。它还使用了这个答案中的一个很好的泛型函数。change_column_order

def change_column_order(df, col_name, index):
    cols = df.columns.tolist()
    cols.remove(col_name)
    cols.insert(index, col_name)
    return df[cols]

def split_df(dataframe, col_name, sep):
    orig_col_index = dataframe.columns.tolist().index(col_name)
    orig_index_name = dataframe.index.name
    orig_columns = dataframe.columns
    dataframe = dataframe.reset_index()  # we need a natural 0-based index for proper merge
    index_col_name = (set(dataframe.columns) - set(orig_columns)).pop()
    df_split = pd.DataFrame(
        pd.DataFrame(dataframe[col_name].str.split(sep).tolist())
        .stack().reset_index(level=1, drop=1), columns=[col_name])
    df = dataframe.drop(col_name, axis=1)
    df = pd.merge(df, df_split, left_index=True, right_index=True, how='inner')
    df = df.set_index(index_col_name)
    df.index.name = orig_index_name
    # merge adds the column to the last place, so we need to move it back
    return change_column_order(df, col_name, orig_col_index)

例:

df = pd.DataFrame([['a:b', 1, 4], ['c:d', 2, 5], ['e:f:g:h', 3, 6]], 
                  columns=['Name', 'A', 'B'], index=[10, 12, 13])
df
        Name    A   B
    10   a:b     1   4
    12   c:d     2   5
    13   e:f:g:h 3   6

split_df(df, 'Name', ':')
    Name    A   B
10   a       1   4
10   b       1   4
12   c       2   5
12   d       2   5
13   e       3   6
13   f       3   6    
13   g       3   6    
13   h       3   6    

请注意,它保留了列的原始索引和顺序。它还适用于具有非顺序索引的数据帧。

评论

2赞 Evan 2/1/2018
这为我破解了这个,干得好:stackoverflow.com/a/48554655/6672746
2赞 cgels 6/6/2018 #12

字符串函数 split 可以采用一个选项布尔参数 'expand'。

以下是使用此参数的解决方案:

(a.var1
  .str.split(",",expand=True)
  .set_index(a.var2)
  .stack()
  .reset_index(level=1, drop=True)
  .reset_index()
  .rename(columns={0:"var1"}))
18赞 piRSquared 8/9/2018 #13

TL;博士

import pandas as pd
import numpy as np

def explode_str(df, col, sep):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.count(sep) + 1)
    return df.iloc[i].assign(**{col: sep.join(s).split(sep)})

def explode_list(df, col):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.len())
    return df.iloc[i].assign(**{col: np.concatenate(s)})

示范

explode_str(a, 'var1', ',')

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

让我们创建一个包含列表的新数据帧d

d = a.assign(var1=lambda d: d.var1.str.split(','))

explode_list(d, 'var1')

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

总评

我将使用 with 来生成可以与 一起使用的数据帧索引位置。np.arangerepeatiloc

常见问题

我为什么不使用 ?loc

因为索引可能不是唯一的,using 将返回与查询的索引匹配的每一行。loc

你为什么不使用属性并切片呢?values

调用 时,如果整个 DataFrame 位于一个内聚的“块”中,则 Pandas 将返回作为“块”的数组视图。否则,Pandas 将不得不拼凑一个新数组。拼凑时,该数组必须具有统一的 dtype。通常,这意味着返回一个 dtype 为 的数组。通过使用而不是切片属性,我减轻了自己处理这个问题的负担。valuesobjectilocvalues

你为什么使用?assign

当我使用与要分解的相同的列名时,我会覆盖现有列并保持其在数据帧中的位置。assign

为什么索引值会重复?

由于在重复位置上使用,生成的索引显示出相同的重复模式。对列表或字符串的每个元素重复一次。
这可以通过以下方式重置
ilocreset_index(drop=True)


对于字符串

我不想过早地拆分字符串。因此,我计算参数的出现次数,假设如果我要拆分,结果列表的长度将比分隔符的数量多 1。sep

然后我把它用到字符串上。sepjoinsplit

def explode_str(df, col, sep):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.count(sep) + 1)
    return df.iloc[i].assign(**{col: sep.join(s).split(sep)})

对于列表

与字符串类似,只是我不需要计算出现的次数,因为它已经拆分了。sep

我使用 Numpy 将列表塞在一起。concatenate

import pandas as pd
import numpy as np

def explode_list(df, col):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.len())
    return df.iloc[i].assign(**{col: np.concatenate(s)})

评论

0赞 Tim 3/27/2019
我喜欢这个。真的很简洁,性能也应该非常好。但有一个问题:df.iloc[i] 是否与数据帧的重复行相同,或者它比这更有效?谢谢!
19赞 Naga kiran 10/25/2018 #14

可以在不更改数据帧结构的情况下拆分和分解数据帧

拆分和扩展特定列的数据

输入:

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



#Get the indexes which are repetative with the split 
df['var1'] = df['var1'].str.split(',')
df = df.explode('var1')

外:

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

编辑-1

拆分和扩展多列的行

Filename    RGB                                             RGB_type
0   A   [[0, 1650, 6, 39], [0, 1691, 1, 59], [50, 1402...   [r, g, b]
1   B   [[0, 1423, 16, 38], [0, 1445, 16, 46], [0, 141...   [r, g, b]

基于引用列重新编制索引,并将列值信息与堆栈对齐

df = df.reindex(df.index.repeat(df['RGB_type'].apply(len)))
df = df.groupby('Filename').apply(lambda x:x.apply(lambda y: pd.Series(y.iloc[0])))
df.reset_index(drop=True).ffill()

外:

                Filename    RGB_type    Top 1 colour    Top 1 frequency Top 2 colour    Top 2 frequency
    Filename                            
 A  0       A   r   0   1650    6   39
    1       A   g   0   1691    1   59
    2       A   b   50  1402    49  187
 B  0       B   r   0   1423    16  38
    1       B   g   0   1445    16  46
    2       B   b   0   1419    16  39
4赞 krassowski 1/23/2019 #15

我一直在为使用各种方式来爆炸我的列表而苦苦挣扎,因此我准备了一些基准来帮助我决定要点赞的答案。我测试了五个场景,其中列表长度与列表数量的比例不同。分享结果如下:

时间:(越少越好,点击查看大图)

Speed

峰值内存使用量:(越少越好)

Peak memory usage

结论

  • @MaxU的答案(更新 2),代号连接在几乎所有情况下都提供了最佳速度,同时保持较低的 PEEK 内存使用率,
  • 如果您需要处理大量具有相对较小的列表的行,并且能够承受增加的峰值内存,请参阅 @DMulligan 的答案(代号堆栈),
  • 已接受的 @Chang 的答案适用于具有几行但列表非常大的数据框。

完整的详细信息(函数和基准测试代码)在此 GitHub 要点中。请注意,基准测试问题已简化,不包括将字符串拆分到列表中 - 大多数解决方案都以类似的方式执行。

评论

0赞 MaxU - stand with Ukraine 1/24/2019
不错的比较!你介意发布一个代码,你用来绘制基准吗?
1赞 krassowski 1/24/2019
请参阅此链接: gist.github.com/krassowski/0259a2cd2ba774ccd9f69bbcc3187fbf(已包含在答案中) - IMO 将其全部粘贴到此处有点太长了。
1赞 Shahar Katz 5/27/2019 #16

升级了 MaxU 的答案,支持 MultiIndex

def explode(df, lst_cols, fill_value='', preserve_index=False):
    """
    usage:
        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
    """
    # 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)
    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)

    # if original index is MultiIndex build the dataframe from the multiindex
    # create "exploded" DF
    if isinstance(df.index, pd.MultiIndex):
        res = res.reindex(
            index=pd.MultiIndex.from_tuples(
                res.index,
                names=['number', 'color']
            )
    )
    return res
148赞 cs95 7/20/2019 #17

熊猫>= 0.25

Series 和 DataFrame 方法定义一个 .explode() 方法,该方法将列表分解为单独的行。请参阅分解类似列表的列的文档部分。

由于您有一个逗号分隔的字符串列表,因此将字符串拆分为逗号以获取元素列表,然后调用该列。explode

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

请注意,explode 仅适用于单个列(目前)。要一次分解多个列,请参见下文。

NaN 和空列表得到了他们应得的待遇,而你不必跳过箍来做正确的事情。

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)。


分解多个列

Pandas 1.3 更新

df.explode从 Pandas 1.3 开始适用于多个列:

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(['var1', 'var2'])
       .reset_index()
       .reindex(df.columns, axis=1))

  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

在旧版本中,您将在应用程序内移动列,这性能要低得多:explode

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

这个想法是将所有不应该分解的列设置为索引,然后通过 分解剩余的列。当列表大小相同时,这很有效。apply

评论

1赞 Avinash 9/1/2020
+1.但是当我使用新列时,它没有按预期工作。比如你能帮忙吗?我用df.assign(var3=df['var1'].str.split(',')).explode('var1')var1var3
0赞 cs95 9/2/2020
@Avinash explode 的参数也应该是 var3。这是一个基本问题,所以花几分钟时间了解原因。
0赞 Vega 1/12/2021
如果你有包含字符串和整数的行,你需要 .astype(str),否则你会得到整数的 NaN 值。
2赞 Devyzr 4/15/2021
我不知道为什么这个简单、可读的解决方案不是得票最多的答案,而是一个复杂、难以理解的答案。就我而言,要做到这一点,我所要做的就是像这样分配返回值。为了便于阅读,我还将该过程拆分为多行。df = df.assign(var1=df['var1'].str.split(',')).explode('var1')
0赞 matt123788 8/18/2021
注意:如果你想继续使用这个分解的 DataFrame,那么你需要显式地将它分配给它自己,即 .这不支持就地函数调用df = df.explode('var1')
0赞 Harsha Reddy 8/2/2019 #18

这里有很多答案,但我很惊讶没有人提到内置的熊猫爆炸功能。查看下面的链接: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html#pandas.DataFrame.explode

由于某种原因,我无法访问该函数,因此我使用了以下代码:

import pandas_explode
pandas_explode.patch()
df_zlp_people_cnt3 = df_zlp_people_cnt2.explode('people')

enter image description here

以上是我的数据示例。正如你所看到的,人物专栏有一系列的人,我试图把它炸毁。我给出的代码适用于列表类型数据。因此,请尝试将逗号分隔的文本数据转换为列表格式。此外,由于我的代码使用内置函数,因此它比自定义/应用函数快得多。

注意:您可能需要使用 pip 安装 pandas_explode。

4赞 1'' 12/17/2019 #19

单行 using 和 and 参数:split(___, expand=True)levelnamereset_index()

>>> b = a.var1.str.split(',', expand=True).set_index(a.var2).stack().reset_index(level=0, name='var1')
>>> b
   var2 var1
0     1    a
1     1    b
2     1    c
0     2    d
1     2    e
2     2    f

如果您需要看起来与问题中完全相同,还可以执行以下操作:b

>>> b = b.reset_index(drop=True)[['var1', 'var2']]
>>> b
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2
0赞 Zhiwei 6/17/2020 #20

我有一个类似的问题,我的解决方案是先将数据帧转换为字典列表,然后进行转换。功能如下:

import re
import pandas as pd

def separate_row(df, column_name):
    ls = []
    for row_dict in df.to_dict('records'):
        for word in re.split(',', row_dict[column_name]):
            row = row_dict.copy()
            row[column_name]=word
            ls.append(row)
    return pd.DataFrame(ls)

例:

>>> from pandas import DataFrame
>>> import numpy as np
>>> a = DataFrame([{'var1': 'a,b,c', 'var2': 1},
               {'var1': 'd,e,f', 'var2': 2}])
>>> a
    var1  var2
0  a,b,c     1
1  d,e,f     2
>>> separate_row(a, "var1")
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

您还可以稍微更改该函数以支持分隔列表类型行。

1赞 darkhipo 8/18/2020 #21

我的解决方案版本添加到这个集合中!:-)

# Original problem
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}])
### My solution
import pandas as pd
import functools
def expand_on_cols(df, fuse_cols, delim=","):
    def expand_on_col(df, fuse_col):
        col_order = df.columns
        df_expanded = pd.DataFrame(
            df.set_index([x for x in df.columns if x != fuse_col])[fuse_col]
            .apply(lambda x: x.split(delim))
            .explode()
        ).reset_index()
        return df_expanded[col_order]
    all_expanded = functools.reduce(expand_on_col, fuse_cols, df)
    return all_expanded

assert(b.equals(expand_on_cols(a, ["var1"], delim=",")))
0赞 Kranti 8/25/2020 #22

从此页面上的所有解决方案中添加一些零碎的东西后,我能够得到这样的东西(对于需要立即使用它的人)。 该函数的参数是 df(input DataFrame) 和 key(具有分隔符分隔字符串的列)。如果分隔符与分号“;”不同,只需替换为分隔符即可。

def split_df_rows_for_semicolon_separated_key(key, df):
    df=df.set_index(df.columns.drop(key,1).tolist())[key].str.split(';', expand=True).stack().reset_index().rename(columns={0:key}).loc[:, df.columns]
    df=df[df[key] != '']
    return df
2赞 Bernard Agbemadon 12/8/2020 #23

我真的很欣赏“长社”的回答,但该函数在大型数据集上需要很长时间。我遇到了这个问题,我来到了这里。iterrows()

# First, reset_index to make the index a column
a = a.reset_index().rename(columns={'index':'duplicated_idx'})

# Get a longer series with exploded cells to rows
series = pd.DataFrame(a['var1'].str.split('/')
                      .tolist(), index=a.duplicated_idx).stack()

# New df from series and merge with the old one
b = series.reset_index([0, 'duplicated_idx'])
b = b.rename(columns={0:'var1'})

# Optional & Advanced: In case, there are other columns apart from var1 & var2
b.merge(
    a[a.columns.difference(['var1'])],
    on='duplicated_idx')

# Optional: Delete the "duplicated_index"'s column, and reorder columns
b = b[a.columns.difference(['duplicated_idx'])]
2赞 Mykola Zotko 5/28/2021 #24

使用 assignexplode 的单行代码:

    col1  col2
0  a,b,c     1
1  d,e,f     2

df.assign(col1 = df.col1.str.split(',')).explode('col1', ignore_index=True)

输出:

  col1  col2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2
0赞 Hamza usman ghani 5/28/2021 #25

尝试:

vals = np.array(a.var1.str.split(",").values.tolist())    
var = np.repeat(a.var2, vals.shape[1])

out = pd.DataFrame(np.column_stack((var, vals.ravel())), columns=a.columns)
display(out)

      var1 var2
    0   1   a
    1   1   b
    2   1   c
    3   2   d
    4   2   e
    5   2   f

0赞 user2110417 8/4/2021 #26

在最新版本的 pandas 中,您可以使用后跟splitexplode

a.assign(var1=a['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
1赞 ali bakhtiari 1/5/2023 #27

一种简短而简单的方法来更改列的格式,以便它可以被以下人员使用:.apply().explod()

import string
import pandas as pd
from io import StringIO

file = StringIO("""    var1  var2
0  a,b,c     1
1  d,e,f     2""")

df = pd.read_csv(file, sep=r'\s\s+')

df['var1'] = df['var1'].apply(lambda x : str(x).split(','))

df.explode('var1')

输出:

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