首页 文章

将pandas数据帧字符串条目拆分(爆炸)到单独的行

提问于
浏览
107

我有一个 pandas dataframe ,其中一列文本字符串包含逗号分隔值 . 我想拆分每个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}])

我知道这不起作用,因为我们通过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)

14 回答

  • 30

    这样的事情怎么样:

    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
    

    然后你只需要重命名列

  • 11

    经过痛苦的实验,找到比接受的答案更快的东西,我得到了这个工作 . 它在我试用的数据集上运行速度快了大约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
    
  • 44

    UPDATE2: 更通用的矢量化函数,适用于多个 normal 和多个 list

    def explode(df, lst_cols, fill_value=''):
        # make sure `lst_cols` is a list
        if lst_cols and not isinstance(lst_cols, list):
            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()
    
        if (lens > 0).all():
            # ALL lists in cells aren't empty
            return pd.DataFrame({
                col:np.repeat(df[col].values, lens)
                for col in idx_cols
            }).assign(**{col:np.concatenate(df[col].values) for col in lst_cols}) \
              .loc[:, df.columns]
        else:
            # at least one list in cells is empty
            return pd.DataFrame({
                col:np.repeat(df[col].values, lens)
                for col in idx_cols
            }).assign(**{col:np.concatenate(df[col].values) for col in lst_cols}) \
              .append(df.loc[lens==0, idx_cols]).fillna(fill_value) \
              .loc[:, df.columns]
    

    演示:

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

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

    Build :

    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: [1, 2], 2: [], 3: []},
     'text': {0: ['aa', 'bb', 'cc'], 1: ['cc', 'dd'], 2: [], 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
    

    UPDATE: generic vectorized approach (will work also for multiple columns):

    原DF:

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

    Solution:

    首先让我们将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
    

    OLD answer:

    受到@AFinkelstein solution的启发,我想让它更加通用化,可以应用于具有两列以上的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
    
  • 5

    对于这个常见任务,这是一个function I wrote . 它比 Series / stack 方法更有效 . 列顺序和名称将保留 .

    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
    

    使用此功能,original question非常简单:

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

    像类似的问题:pandas: How do I split text in a column into multiple rows?

    你可以这样做:

    >> 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
    
  • 1

    我想出了一个具有任意数量列的数据帧的解决方案(同时仍然只分离一列的条目) .

    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
    
  • 2

    这是一条相当简单的消息,它使用pandas str accessor中的 split 方法,然后使用NumPy将每一行展平为一个数组 .

    通过使用 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
    
  • 4

    TL; DR

    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
    

    一般评论

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

    常见问题

    为什么我不使用loc?

    因为索引可能不是唯一的,并且使用 loc 将返回与查询索引匹配的每一行 .

    为什么不使用values属性和slice?

    当调用 values 时,如果整个数据帧在一个内聚"block"中,Pandas将返回一个数组的视图"block" . 否则,熊猫将不得不拼凑一个新阵列 . 在cobbling时,该数组必须是统一的dtype . 通常这意味着返回一个dtype为 object 的数组 . 通过使用 iloc 而不是切割 values 属性,我减轻了自己不必处理的问题 .

    为什么使用assign?

    当我使用 assign 使用我正在爆炸的相同列名时,我会覆盖现有列并保持其在数据框中的位置 .

    为什么索引值重复?

    通过在重复位置上使用 iloc ,结果索引显示相同的重复模式 . 列表或字符串的每个元素重复一次 .
    这可以通过 reset_index(drop=True) 重置


    For Strings

    我不想过早地拆分弦乐 . 因此,我计算 sep 参数的出现,假设如果我要拆分,结果列表的长度将比分隔符的数量多一个 .

    然后我使用 sepjoin 字符串然后 split .

    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

    基于优秀的@ DMulligan的solution,这里是一个通用的矢量化(无循环)函数,它将数据帧的一列拆分成多行,并将其合并回原始数据帧 . 它还使用了一个很棒的通用 change_column_order 函数answer .

    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
    

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

  • 77

    刚从上面使用了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
    
  • 78

    字符串函数split可以选择boolean参数'expand' .

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

    a.var1.str.split(",",expand=True).set_index(a.var2).stack().reset_index(level=1, drop=True).reset_index().rename(columns={0:"var1"})
    
  • 0

    我已经提出了以下解决这个问题的方法:

    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"])
    
  • 1

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

    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)
    
  • 0

    有可能在不改变数据帧结构的情况下拆分和分解数据帧

    输入:

    var1    var2
    0   a,b,c   1
    1   d,e,f   2
    
    
    
    #Get the indexes which are repetative with the split 
    df = df.reindex(df.index.repeat(df.var1.str.split(',').apply(len)))
    #Assign the split values to dataframe column  
    df['var1'] = sum(df.drop_duplicates(keep='first')['var1'].str.split(','),[])
    

    日期:

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

相关问题