Dividir (explodir) a entrada de sequência de dados do pandas para separar linhas

200

Eu tenho um pandas dataframeem que uma coluna de seqüências de texto contém valores separados por vírgula. Quero dividir cada campo CSV e criar uma nova linha por entrada (suponha que o CSV esteja limpo e só precise ser dividido em ','). Por exemplo, adeve se tornar 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

Até agora, tentei várias funções simples, mas o .applymétodo parece aceitar apenas uma linha como valor de retorno quando usado em um eixo, e não consigo .transformtrabalhar. Qualquer sugestão será muito bem-vinda!

Dados de exemplo:

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

Sei que isso não funcionará porque perdemos os metadados do DataFrame passando por numpy, mas isso deve lhe dar uma idéia do que tentei:

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)
Vincent
fonte
2
outras soluções nesta página estão funcionando, mas achei uma curta e eficaz. stackoverflow.com/questions/27263805/…
desaiankitb 28/11
1
Para outras pessoas que chegam a esta página e procuram uma solução que mantenha várias colunas, dê uma olhada nesta pergunta: stackoverflow.com/questions/17116814/…
Sos

Respostas:

81

Que tal algo como isso:

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

Então você só precisa renomear as colunas

Chang She
fonte
1
Parece que isso vai funcionar. Obrigado pela ajuda! No entanto, em geral, existe uma abordagem preferida para Split-Apply-Combine em que o Apply retorna um quadro de dados de tamanho arbitrário (mas consistente para todos os pedaços) e o Combine apenas vstacks os DFs retornados?
Vincent
GroupBy.apply deve funcionar (eu apenas tentei contra o mestre). No entanto, nesse caso, você realmente não precisa seguir a etapa extra do agrupamento, pois está gerando os dados por linha, certo?
Chang She
1
Ei pessoal. Desculpe entrar tão tarde, mas me perguntando se não há uma solução melhor para isso. Estou tentando experimentar as seguintes linhas pela primeira vez, pois esse parece ser o bilhete para isso. Também estou confuso com a solução proposta. O que o "_" representa? Você pode explicar como a solução funciona? --Obrigado
horatio1701d
11
A solução pode ser estendida para mais de duas colunas?
horatio1701d
1
por favor verifique esta abordagem vetorizada ...
MaxU 02/02
146

UPDATE2: função vetorizada mais genérica, que funcionará para várias normale múltiplas listcolunas

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

Demo:

Várias listcolunas - todas as listcolunas devem ter o mesmo número de elementos em cada linha:

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

preservando os valores do índice original:

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

Configuração:

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

Coluna 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

usando este pequeno truque, podemos converter colunas semelhantes a CSV em listcolunas:

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

ATUALIZAR: abordagem vetorizada genérica (funcionará também para várias colunas):

DF original:

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

Solução:

primeiro vamos converter strings CSV em listas:

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

Agora podemos fazer isso:

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

Resposta ANTIGA:

Inspirado na solução @AFinkelstein , eu queria torná-lo um pouco mais generalizado, que poderia ser aplicado ao DF com mais de duas colunas e tão rápido, quase quase, tão rápido quanto a solução da 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
MaxU
fonte
7
cara, se você pode abrir uma discussão nos pandas do Git, acho que precisamos de uma função de construção como essa !!! Eu vi tantos pergunta sobre unlistify e unnesting no SO para pandas
YOBEN_S
como usar isso para várias colunas. Como se eu tivesse dados separados por vírgula em 2 colunas e quisesse fazê-lo em sequência?
Jaskaran Singh Puri
@JaskaranSinghPuri, primeiro você deseja converter todas as colunas CSV em listas.
MaxU 21/08/19
1
Não é necessário que não funcione se os elementos da sua lista forem tuplas. Mas depois de converter a tupla inteira em string, funciona como um encanto!
Guido
2
Parece que o apelo de WenBen foi ouvido pelos deuses dos pandas, eles instalaram um .explode()método na API (veja também esta resposta ).
cs95
117

Após uma dolorosa experimentação para encontrar algo mais rápido que a resposta aceita, consegui que isso funcionasse. Ele foi executado 100x mais rápido no conjunto de dados em que eu experimentei.

Se alguém souber uma maneira de tornar isso mais elegante, modifique meu código. Não consegui encontrar uma maneira de funcionar sem definir as outras colunas que você deseja manter como índice e, em seguida, redefinir o índice e renomear as colunas, mas imagino que exista outra coisa que funcione.

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
DMulligan
fonte
2
Esta solução funcionou significativamente mais rápido e parece usar menos memória,
Cyril
1
Esta é uma boa solução de pandas vetorizados, eu estava procurando por isso. Obrigado!
Dennis Golomazov
Quando eu tento isso no meu próprio conjunto de dados, eu continuo recebendo TypeError: object of type 'float' has no len()na primeira etapa ( DataFrame(df.var1.str.split(',').tolist()))
user5359531
@ user5359531 seu conjunto de dados provavelmente tem alguns NaNnessa coluna; portanto, a substituição éb = DataFrame(a.var1.str.split(',').values.tolist(), index=a.var2).stack()
Flair
Apenas fyi, aqui está uma boa redação desta solução com exemplo.
Hhbilly 20/05/19
46

Aqui está uma função que escrevi para esta tarefa comum. É mais eficiente que os métodos Series/ stack. A ordem e os nomes das colunas são mantidos.

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

Com esta função, a pergunta original é tão simples quanto:

tidy_split(a, 'var1', sep=',')
Daniel Himmelstein
fonte
1
Isso é incrivelmente rápido! Muito obrigado por isso.
Anurag N. Sharma
42

Pandas> = 0,25

Os métodos Series e DataFrame definem um .explode()método que explode listas em linhas separadas. Consulte a seção de documentos em Explodindo uma coluna do tipo lista .

Como você tem uma lista de sequências separadas por vírgula, divida a sequência em vírgula para obter uma lista de elementos e chame explodea coluna.

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

Observe que explode funciona apenas em uma única coluna (por enquanto).


NaNs e listas vazias recebem o tratamento que merecem sem que você precise pular os aros para acertar.

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

Essa é uma séria vantagem sobre as soluções baseadas em ravel+repeat (que ignoram completamente as listas vazias e bloqueiam os NaNs).

cs95
fonte
4
Este é o mais fácil e se encaixa melhor no meu caso! obrigado!
Isaac Sim
14

Pergunta semelhante a: pandas: como faço para dividir o texto em uma coluna em várias linhas?

Você poderia fazer:

>> 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
inodb
fonte
2
Ele funciona depois de adicionar um código mais renomeação s.name = 'var1'
Jesse
14

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

Demonstração

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

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

Vamos criar um novo quadro de dados dque tenha listas

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

Comentários gerais

Vou usar np.arangecom repeatpara produzir posições de índice de quadro de dados que eu possa usar iloc.

Perguntas frequentes

Por que eu não uso loc?

Como o índice pode não ser exclusivo e usar loc retornará todas as linhas que correspondem a um índice consultado.

Por que você não usa o values atributo e o divide?

Ao chamar values, se a totalidade do quadro de dados estiver em um "bloco" coeso, o Pandas retornará uma visão da matriz que é o "bloco". Caso contrário, os pandas precisarão criar uma nova matriz. Ao pavimentar, essa matriz deve ser de um tipo uniforme. Frequentemente, isso significa retornar uma matriz com o tipo dtype object. Usando em ilocvez de cortar ovalues atributo, eu me alivio de ter que lidar com isso.

Por que você usa assign ?

Quando eu uso assign o mesmo nome de coluna que estou explodindo, sobrescrevo a coluna existente e mantenho sua posição no quadro de dados.

Por que os valores do índice se repetem?

Em virtude do uso ilocem posições repetidas, o índice resultante mostra o mesmo padrão repetido. Uma repetição para cada elemento da lista ou sequência.
Isso pode ser redefinido comreset_index(drop=True)


For Strings

Eu não quero ter que dividir as cordas prematuramente. Então, em vez disso, conto as ocorrências do separgumento assumindo que, se eu fosse dividido, o comprimento da lista resultante seria um a mais que o número de separadores.

Eu, então, usar isso seppara joinas cordas, em seguida 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)})

Para listas

Semelhante ao das strings, exceto que eu não preciso contar ocorrências de sep porque já está dividido.

Eu uso o Numpy concatenatepara juntar as listas.

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

piRSquared
fonte
Eu gosto deste. Realmente conciso e o desempenho também deve ser muito bom. Uma pergunta é: df.iloc [i] é o mesmo que repetir linhas do dataframe ou é mais eficiente que isso? Obrigado!
Tim
7

Existe a possibilidade de dividir e explodir o quadro de dados sem alterar a estrutura do quadro de dados

Dividir e expandir dados de colunas específicas

Entrada:

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



#Get the indexes which are repetative with the split 
temp = df['var1'].str.split(',')
df = df.reindex(df.index.repeat(temp.apply(len)))


df['var1'] = np.hstack(temp)

Fora:

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

Edit-1

Divisão e expansão de linhas para várias colunas

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]

Indexar novamente com base na coluna de referência e alinhar as informações do valor da coluna com a pilha

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

Fora:

                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
Naga kiran
fonte
5

Eu vim com uma solução para quadros de dados com números arbitrários de colunas (enquanto ainda separava apenas as entradas de uma coluna por vez).

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
jlln
fonte
2
bom, mas infelizmente lento por causa desta conversão todict () :(
MAQ
4

Aqui está uma mensagem bastante direta que usa o splitmétodo dos pandasstr acessador e, em seguida, usa o NumPy para achatar cada linha em uma única matriz.

Os valores correspondentes são recuperados repetindo a coluna não dividida o número correto de vezes com 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
Ted Petrou
fonte
1
Essa poderia ser uma resposta muito bonita. Infelizmente, não é escalável para muitas colunas, é?
Michael Dorner
3

Eu tenho lutado com a experiência de falta de memória usando várias maneiras de explodir minhas listas, então preparei alguns parâmetros de referência para me ajudar a decidir quais respostas aprovar. Testei cinco cenários com proporções variadas do tamanho da lista com o número de listas. Compartilhando os resultados abaixo:

Tempo: (quanto menos, melhor, clique para ver a versão ampliada)

Rapidez

Pico de uso da memória: (menos é melhor)

Pico de uso da memória

Conclusões :

  • @ Resposta da MaxU (atualização 2), codinome concatenate oferece a melhor velocidade em quase todos os casos, mantendo baixo o uso de memória espiada,
  • consulte a resposta do @ DMulligan ( pilha de codinome ) se você precisar processar muitas linhas com listas relativamente pequenas e puder aumentar o pico de memória,
  • a resposta aceita do @ Chang funciona bem para quadros de dados que têm poucas linhas, mas listas muito grandes.

Detalhes completos (funções e código de benchmarking) estão nesta essência do GitHub . Observe que o problema do benchmark foi simplificado e não incluiu a divisão de strings na lista - que a maioria das soluções executava de maneira semelhante.

Krassowski
fonte
Boa comparação! Você se importa em publicar um código que você usou para traçar os benchmarks?
MaxU 23/01/19
1
Por favor, consulte este link: gist.github.com/krassowski/0259a2cd2ba774ccd9f69bbcc3187fbf (já incluído na resposta) - OMI demoraria um pouco para colar tudo aqui.
precisa saber é o seguinte
2

Com base na excelente solução do @ DMulligan , aqui está uma função genérica vetorizada (sem loops) que divide uma coluna de um quadro de dados em várias linhas e a funde de volta ao quadro de dados original. Ele também usa uma ótima change_column_orderfunção genérica dessa resposta .

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)

Exemplo:

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    

Observe que ele preserva o índice original e a ordem das colunas. Também funciona com quadros de dados que possuem índice não sequencial.

Dennis Golomazov
fonte
2
este rachado este para mim, bom trabalho: stackoverflow.com/a/48554655/6672746
Evan
2

A divisão da função string pode usar um argumento booleano de opção 'expand'.

Aqui está uma solução usando este argumento:

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

Acabei de usar a excelente resposta de jiln de cima, mas precisava expandir para dividir várias colunas. Pensei em compartilhar.

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
Derryn Webster-Knife
fonte
1

atualizou a resposta do MaxU com suporte a 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
Shahar Katz
fonte
1

Uso de uma linha split(___, expand=True)e os argumentos levele namepara reset_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

Se você precisar bse parecer exatamente com a pergunta, poderá fazer adicionalmente:

>>> 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
1 ''
fonte
0

Eu vim com a seguinte solução para esse problema:

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"])
Pavel
fonte
0

Outra solução que usa pacote de cópias 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)
Ankit Maheshwari
fonte
0

Há muitas respostas aqui, mas estou surpreso que ninguém tenha mencionado a função de explosão dos pandas incorporados. Confira o link abaixo: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html#pandas.DataFrame.explode

Por alguma razão, não consegui acessar essa função, então usei o código abaixo:

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

insira a descrição da imagem aqui

Acima está uma amostra dos meus dados. Como você pode ver as pessoas coluna tinha uma série de pessoas, e eu estava tentando explodir. O código que eu dei funciona para dados do tipo lista. Portanto, tente colocar os dados de texto separados por vírgula no formato de lista. Além disso, como meu código usa funções integradas, é muito mais rápido que as funções custom / apply.

Nota: Pode ser necessário instalar o pandas_explode com o pip.

Harsha Reddy
fonte
0

Eu tive um problema semelhante, minha solução foi converter o dataframe em uma lista de dicionários primeiro e depois fazer a transição. Aqui está a função:

import copy
import re

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 = copy.deepcopy(row_dict)
            row[column_name]=word
            ls(row)
    return pd.DataFrame(ls)

Exemplo:

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

Você também pode alterar um pouco a função para oferecer suporte à separação de linhas do tipo lista.

Zhiwei
fonte