Como um usuário de R
e python
, já vi esse tipo de pergunta algumas vezes.
Em R, eles têm a função interna do pacote tidyr
chamada unnest
. Mas em Python
( pandas
) não há função incorporada para este tipo de pergunta.
Sei que as object
colunas type
sempre tornam os dados difíceis de converter com uma pandas
função '. Quando recebi dados como esses, a primeira coisa que me veio à mente foi 'nivelar' ou desaninhar as colunas.
Estou usando pandas
e python
funções para este tipo de pergunta. Se você está preocupado com a velocidade das soluções acima, verifique a resposta do usuário3483203, pois ele está usando numpy
e na maioria das vezes numpy
é mais rápido. Eu recomendo Cpython
e numba
se a velocidade é importante no seu caso.
Método 0 [pandas> = 0,25]
Começando com pandas 0,25 , se você só precisa explodir uma coluna, pode usar a explode
função:
df.explode('B')
A B
0 1 1
1 1 2
0 2 1
1 2 2
Método 1
apply + pd.Series
(fácil de entender, mas em termos de desempenho não recomendado).
df.set_index('A').B.apply(pd.Series).stack().reset_index(level=0).rename(columns={0:'B'})
Out[463]:
A B
0 1 1
1 1 2
0 2 1
1 2 2
Método 2
Usando repeat
com o DataFrame
construtor, recrie seu dataframe (bom em desempenho, não bom em colunas múltiplas)
df=pd.DataFrame({'A':df.A.repeat(df.B.str.len()),'B':np.concatenate(df.B.values)})
df
Out[465]:
A B
0 1 1
0 1 2
1 2 1
1 2 2
Método 2.1,
por exemplo, além de A, temos A.1 ..... An Se ainda usarmos o método ( Método 2 ) acima, será difícil recriar as colunas uma por uma.
Solução: join
ou merge
com o index
depois de 'unnest' as colunas individuais
s=pd.DataFrame({'B':np.concatenate(df.B.values)},index=df.index.repeat(df.B.str.len()))
s.join(df.drop('B',1),how='left')
Out[477]:
B A
0 1 1
0 2 1
1 1 2
1 2 2
Se você precisa da ordem das colunas exatamente como antes, adicione reindex
no final.
s.join(df.drop('B',1),how='left').reindex(columns=df.columns)
Método 3
recriar olist
pd.DataFrame([[x] + [z] for x, y in df.values for z in y],columns=df.columns)
Out[488]:
A B
0 1 1
1 1 2
2 2 1
3 2 2
Se houver mais de duas colunas, use
s=pd.DataFrame([[x] + [z] for x, y in zip(df.index,df.B) for z in y])
s.merge(df,left_on=0,right_index=True)
Out[491]:
0 1 A B
0 0 1 1 [1, 2]
1 0 2 1 [1, 2]
2 1 1 2 [1, 2]
3 1 2 2 [1, 2]
Método 4
usando reindex
ouloc
df.reindex(df.index.repeat(df.B.str.len())).assign(B=np.concatenate(df.B.values))
Out[554]:
A B
0 1 1
0 1 2
1 2 1
1 2 2
#df.loc[df.index.repeat(df.B.str.len())].assign(B=np.concatenate(df.B.values))
Método 5
quando a lista contém apenas valores únicos:
df=pd.DataFrame({'A':[1,2],'B':[[1,2],[3,4]]})
from collections import ChainMap
d = dict(ChainMap(*map(dict.fromkeys, df['B'], df['A'])))
pd.DataFrame(list(d.items()),columns=df.columns[::-1])
Out[574]:
B A
0 1 1
1 2 1
2 3 2
3 4 2
Método 6
usando numpy
para alto desempenho:
newvalues=np.dstack((np.repeat(df.A.values,list(map(len,df.B.values))),np.concatenate(df.B.values)))
pd.DataFrame(data=newvalues[0],columns=df.columns)
A B
0 1 1
1 1 2
2 2 1
3 2 2
Método 7
usando a função de base itertools
cycle
e chain
: Solução Python pura apenas por diversão
from itertools import cycle,chain
l=df.values.tolist()
l1=[list(zip([x[0]], cycle(x[1])) if len([x[0]]) > len(x[1]) else list(zip(cycle([x[0]]), x[1]))) for x in l]
pd.DataFrame(list(chain.from_iterable(l1)),columns=df.columns)
A B
0 1 1
1 1 2
2 2 1
3 2 2
Generalizando para várias colunas
df=pd.DataFrame({'A':[1,2],'B':[[1,2],[3,4]],'C':[[1,2],[3,4]]})
df
Out[592]:
A B C
0 1 [1, 2] [1, 2]
1 2 [3, 4] [3, 4]
Função de autodefinição:
def unnesting(df, explode):
idx = df.index.repeat(df[explode[0]].str.len())
df1 = pd.concat([
pd.DataFrame({x: np.concatenate(df[x].values)}) for x in explode], axis=1)
df1.index = idx
return df1.join(df.drop(explode, 1), how='left')
unnesting(df,['B','C'])
Out[609]:
B C A
0 1 1 1
0 2 2 1
1 3 3 2
1 4 4 2
Desaninhamento de colunas
Todo o método acima está falando sobre o desninhamento vertical e explodir, se você precisar expandir a lista horizontal , verifique com o pd.DataFrame
construtor
df.join(pd.DataFrame(df.B.tolist(),index=df.index).add_prefix('B_'))
Out[33]:
A B C B_0 B_1
0 1 [1, 2] [1, 2] 1 2
1 2 [3, 4] [3, 4] 3 4
Função atualizada
def unnesting(df, explode, axis):
if axis==1:
idx = df.index.repeat(df[explode[0]].str.len())
df1 = pd.concat([
pd.DataFrame({x: np.concatenate(df[x].values)}) for x in explode], axis=1)
df1.index = idx
return df1.join(df.drop(explode, 1), how='left')
else :
df1 = pd.concat([
pd.DataFrame(df[x].tolist(), index=df.index).add_prefix(x) for x in explode], axis=1)
return df1.join(df.drop(explode, 1), how='left')
Resultado do teste
unnesting(df, ['B','C'], axis=0)
Out[36]:
B0 B1 C0 C1 A
0 1 2 1 2 1
1 3 4 3 4 2