목록의 팬더 열, 각 목록 요소에 대한 행을 만듭니다.


163

일부 셀에 여러 값 목록이 포함 된 데이터 프레임이 있습니다. 셀에 여러 값을 저장하는 대신 목록의 각 항목이 다른 모든 열에서 동일한 값을 가진 자체 행을 갖도록 데이터 프레임을 확장하고 싶습니다. 그래서 내가 가지고 있다면 :

import pandas as pd
import numpy as np

df = pd.DataFrame(
    {'trial_num': [1, 2, 3, 1, 2, 3],
     'subject': [1, 1, 1, 2, 2, 2],
     'samples': [list(np.random.randn(3).round(2)) for i in range(6)]
    }
)

df
Out[10]: 
                 samples  subject  trial_num
0    [0.57, -0.83, 1.44]        1          1
1    [-0.01, 1.13, 0.36]        1          2
2   [1.18, -1.46, -0.94]        1          3
3  [-0.08, -4.22, -2.05]        2          1
4     [0.72, 0.79, 0.53]        2          2
5    [0.4, -0.32, -0.13]        2          3

긴 형식으로 변환하는 방법 (예 :

   subject  trial_num  sample  sample_num
0        1          1    0.57           0
1        1          1   -0.83           1
2        1          1    1.44           2
3        1          2   -0.01           0
4        1          2    1.13           1
5        1          2    0.36           2
6        1          3    1.18           0
# etc.

인덱스는 중요하지 않으므로 기존 열을 인덱스로 설정해도되며 최종 순서는 중요하지 않습니다.


11
팬더 0.25 df.explode('samples')에서이 문제를 해결하는 데 사용할 수도 있습니다 . explode지금은 하나의 열 분해 만 지원할 수 있습니다.
cs95

답변:


48
lst_col = 'samples'

r = pd.DataFrame({
      col:np.repeat(df[col].values, df[lst_col].str.len())
      for col in df.columns.drop(lst_col)}
    ).assign(**{lst_col:np.concatenate(df[lst_col].values)})[df.columns]

결과:

In [103]: r
Out[103]:
    samples  subject  trial_num
0      0.10        1          1
1     -0.20        1          1
2      0.05        1          1
3      0.25        1          2
4      1.32        1          2
5     -0.17        1          2
6      0.64        1          3
7     -0.22        1          3
8     -0.71        1          3
9     -0.03        2          1
10    -0.65        2          1
11     0.76        2          1
12     1.77        2          2
13     0.89        2          2
14     0.65        2          2
15    -0.98        2          3
16     0.65        2          3
17    -0.30        2          3

추신 : 여기에 좀 더 일반적인 해결책이 있습니다.


업데이트 : 일부 설명 : IMO이 코드를 이해하는 가장 쉬운 방법은 단계별로 실행하는 것입니다.

다음 줄에서 한 열에 값을 반복합니다. N여기서 N-는 해당 목록의 길이입니다.

In [10]: np.repeat(df['trial_num'].values, df[lst_col].str.len())
Out[10]: array([1, 1, 1, 2, 2, 2, 3, 3, 3, 1, 1, 1, 2, 2, 2, 3, 3, 3], dtype=int64)

스칼라 값을 포함하는 모든 열에 대해 일반화 할 수 있습니다.

In [11]: pd.DataFrame({
    ...:           col:np.repeat(df[col].values, df[lst_col].str.len())
    ...:           for col in df.columns.drop(lst_col)}
    ...:         )
Out[11]:
    trial_num  subject
0           1        1
1           1        1
2           1        1
3           2        1
4           2        1
5           2        1
6           3        1
..        ...      ...
11          1        2
12          2        2
13          2        2
14          2        2
15          3        2
16          3        2
17          3        2

[18 rows x 2 columns]

를 사용하여 열 ( ) np.concatenate()의 모든 값을 평평하게 하고 1D 벡터를 얻을 수 있습니다.listsamples

In [12]: np.concatenate(df[lst_col].values)
Out[12]: array([-1.04, -0.58, -1.32,  0.82, -0.59, -0.34,  0.25,  2.09,  0.12,  0.83, -0.88,  0.68,  0.55, -0.56,  0.65, -0.04,  0.36, -0.31])

이 모든 것을 하나로 모으기 :

In [13]: pd.DataFrame({
    ...:           col:np.repeat(df[col].values, df[lst_col].str.len())
    ...:           for col in df.columns.drop(lst_col)}
    ...:         ).assign(**{lst_col:np.concatenate(df[lst_col].values)})
Out[13]:
    trial_num  subject  samples
0           1        1    -1.04
1           1        1    -0.58
2           1        1    -1.32
3           2        1     0.82
4           2        1    -0.59
5           2        1    -0.34
6           3        1     0.25
..        ...      ...      ...
11          1        2     0.68
12          2        2     0.55
13          2        2    -0.56
14          2        2     0.65
15          3        2    -0.04
16          3        2     0.36
17          3        2    -0.31

[18 rows x 3 columns]

를 사용 pd.DataFrame()[df.columns]하면 원래 순서대로 열을 선택할 수 있습니다.


3
이것이 정답입니다. 현재 받아 들여지는 대답은 이것에 비해 훨씬 느립니다.
irene

1
이 문제를 해결하는 방법을 알 수 없습니다. TypeError : 'safe'규칙에 따라 dtype ( 'float64')에서 dtype ( 'int64')으로 배열 데이터를 캐스트 할 수 없습니다
Greg

1
이것은 스택을 검색하는 전체 시간 동안 발견 된 10 + 중 나를 위해 일한 유일한 대답입니다. 감사 MaxU 🙏
olisteadman

1
이렇게하면 빈 목록이있는 행이 lst_col완전히 삭제됩니다. 이러한 행을 유지하고 자신의 채울 lst_col과를 np.nan, 당신은 할 수있는 df[lst_col] = df[lst_col].apply(lambda x: x if len(x) > 0 else [np.nan])이 방법을 사용하기 전에. 분명히 .mask목록을 반환하지 않으므로 .apply.
Charles Davis

이것은 받아 들여야 할 훌륭한 답변입니다. 비록 그것은 마술 수준의 대답이지만, 나는이 단계가 실제로 무엇을하는지에 대한 설명을 부탁드립니다.
ifly6

129

예상보다 조금 길다 :

>>> df
                samples  subject  trial_num
0  [-0.07, -2.9, -2.44]        1          1
1   [-1.52, -0.35, 0.1]        1          2
2  [-0.17, 0.57, -0.65]        1          3
3  [-0.82, -1.06, 0.47]        2          1
4   [0.79, 1.35, -0.09]        2          2
5   [1.17, 1.14, -1.79]        2          3
>>>
>>> s = df.apply(lambda x: pd.Series(x['samples']),axis=1).stack().reset_index(level=1, drop=True)
>>> s.name = 'sample'
>>>
>>> df.drop('samples', axis=1).join(s)
   subject  trial_num  sample
0        1          1   -0.07
0        1          1   -2.90
0        1          1   -2.44
1        1          2   -1.52
1        1          2   -0.35
1        1          2    0.10
2        1          3   -0.17
2        1          3    0.57
2        1          3   -0.65
3        2          1   -0.82
3        2          1   -1.06
3        2          1    0.47
4        2          2    0.79
4        2          2    1.35
4        2          2   -0.09
5        2          3    1.17
5        2          3    1.14
5        2          3   -1.79

순차 색인을 원하는 경우 reset_index(drop=True)결과에 적용 할 수 있습니다.

업데이트 :

>>> res = df.set_index(['subject', 'trial_num'])['samples'].apply(pd.Series).stack()
>>> res = res.reset_index()
>>> res.columns = ['subject','trial_num','sample_num','sample']
>>> res
    subject  trial_num  sample_num  sample
0         1          1           0    1.89
1         1          1           1   -2.92
2         1          1           2    0.34
3         1          2           0    0.85
4         1          2           1    0.24
5         1          2           2    0.72
6         1          3           0   -0.96
7         1          3           1   -2.72
8         1          3           2   -0.11
9         2          1           0   -1.33
10        2          1           1    3.13
11        2          1           2   -0.65
12        2          2           0    0.10
13        2          2           1    0.65
14        2          2           2    0.15
15        2          3           0    0.64
16        2          3           1   -0.10
17        2          3           2   -0.76

감사합니다. 각 항목을 자체 열에 적용하는 첫 단계조차 큰 도움이됩니다. 나는 약간 다른 방법으로 할 수 있었지만 여전히 공정한 몇 가지 단계가 있습니다. 분명히 이것은 Pandas에서 간단하지 않습니다!
Marius

1
좋은 대답입니다. 당신은 대체하여 그것을 조금 단축 할 수 df.apply(lambda x: pd.Series(x['samples']),axis=1)와 함께 df.samples.apply(pd.Series).
데니스 골 로마 조프

1
독자에 대한 참고 사항 : 성능 문제가 심각합니다. numpy를 사용하여 훨씬 성능이 뛰어난 솔루션을 보려면 여기 를 참조 하십시오 .
cs95

2
샘플 수가 모든 행에 대해 동일하지 않은 경우 해결책은 무엇입니까?
SarahData

@SarahData 여기에df.explode() 표시된 대로 사용 하십시오.
cs95

63

팬더> = 0.25

Series 및 DataFrame 메서드는 .explode()목록을 별도의 행으로 분해하는 메서드를 정의합니다 . 목록 같은 열 분해 에 대한 문서 섹션을 참조하십시오 .

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

df.explode('var1')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
2  NaN     3  # empty list converted to NaN
3  NaN     4  # NaN entry preserved as-is

# to reset the index to be monotonically increasing...
df.explode('var1').reset_index(drop=True)

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

이것은 또한리스트와 스칼라의 혼합 열과 빈리스트와 NaN을 적절히 처리합니다 (이것은 repeat기반 솔루션 의 단점입니다 ).

그러나 지금 explode은 단일 열에서만 작동합니다 .

추신 : 문자열 열을 분해 하려면 먼저 구분 기호를 분할 한 다음을 사용해야 explode합니다. 이 (매우) 관련 답변을 참조하십시오.


8
마지막으로 팬더를위한 explode ()!
Kai

2
드디어! 마음을 날려! 위의 @MaxU의 훌륭한 답변이지만 훨씬 단순화되었습니다.

12

당신은 또한 사용할 수 있습니다 pd.concatpd.melt이에 대한 :

>>> objs = [df, pd.DataFrame(df['samples'].tolist())]
>>> pd.concat(objs, axis=1).drop('samples', axis=1)
   subject  trial_num     0     1     2
0        1          1 -0.49 -1.00  0.44
1        1          2 -0.28  1.48  2.01
2        1          3 -0.52 -1.84  0.02
3        2          1  1.23 -1.36 -1.06
4        2          2  0.54  0.18  0.51
5        2          3 -2.18 -0.13 -1.35
>>> pd.melt(_, var_name='sample_num', value_name='sample', 
...         value_vars=[0, 1, 2], id_vars=['subject', 'trial_num'])
    subject  trial_num sample_num  sample
0         1          1          0   -0.49
1         1          2          0   -0.28
2         1          3          0   -0.52
3         2          1          0    1.23
4         2          2          0    0.54
5         2          3          0   -2.18
6         1          1          1   -1.00
7         1          2          1    1.48
8         1          3          1   -1.84
9         2          1          1   -1.36
10        2          2          1    0.18
11        2          3          1   -0.13
12        1          1          2    0.44
13        1          2          2    2.01
14        1          3          2    0.02
15        2          1          2   -1.06
16        2          2          2    0.51
17        2          3          2   -1.35

마지막으로 필요한 경우 처음 세 열을 기준으로 정렬 할 수 있습니다.


1
이것은 목록의 길이가 무엇인지 사전에 알고 있거나 길이가 모두 같은 경우에만 작동합니다.
Chill2Macht

9

Roman Pekar의 솔루션을 단계별로 통해 더 잘 이해하려고 노력 melt하면서 혼란스러운 스택 및 인덱스 재설정을 피하는 데 사용되는 자체 솔루션을 찾았습니다. 나는 분명히 더 명확한 해결책이라고 말할 수 없다.

items_as_cols = df.apply(lambda x: pd.Series(x['samples']), axis=1)
# Keep original df index as a column so it's retained after melt
items_as_cols['orig_index'] = items_as_cols.index

melted_items = pd.melt(items_as_cols, id_vars='orig_index', 
                       var_name='sample_num', value_name='sample')
melted_items.set_index('orig_index', inplace=True)

df.merge(melted_items, left_index=True, right_index=True)

출력 (분명히 원래 샘플 열을 삭제할 수 있습니다) :

                 samples  subject  trial_num sample_num  sample
0    [1.84, 1.05, -0.66]        1          1          0    1.84
0    [1.84, 1.05, -0.66]        1          1          1    1.05
0    [1.84, 1.05, -0.66]        1          1          2   -0.66
1    [-0.24, -0.9, 0.65]        1          2          0   -0.24
1    [-0.24, -0.9, 0.65]        1          2          1   -0.90
1    [-0.24, -0.9, 0.65]        1          2          2    0.65
2    [1.15, -0.87, -1.1]        1          3          0    1.15
2    [1.15, -0.87, -1.1]        1          3          1   -0.87
2    [1.15, -0.87, -1.1]        1          3          2   -1.10
3   [-0.8, -0.62, -0.68]        2          1          0   -0.80
3   [-0.8, -0.62, -0.68]        2          1          1   -0.62
3   [-0.8, -0.62, -0.68]        2          1          2   -0.68
4    [0.91, -0.47, 1.43]        2          2          0    0.91
4    [0.91, -0.47, 1.43]        2          2          1   -0.47
4    [0.91, -0.47, 1.43]        2          2          2    1.43
5  [-1.14, -0.24, -0.91]        2          3          0   -1.14
5  [-1.14, -0.24, -0.91]        2          3          1   -0.24
5  [-1.14, -0.24, -0.91]        2          3          2   -0.91

6

수동 열 명명을 피하는 Roman Pekar의 답변 버전을 찾는 사람들에게 :

column_to_explode = 'samples'
res = (df
       .set_index([x for x in df.columns if x != column_to_explode])[column_to_explode]
       .apply(pd.Series)
       .stack()
       .reset_index())
res = res.rename(columns={
          res.columns[-2]:'exploded_{}_index'.format(column_to_explode),
          res.columns[-1]: '{}_exploded'.format(column_to_explode)})

4

가장 쉬운 방법은 다음과 같습니다.

  1. samples열을 DataFrame으로 변환
  2. 원본 df와 결합
  3. 녹는

여기에 표시 :

    df.samples.apply(lambda x: pd.Series(x)).join(df).\
melt(['subject','trial_num'],[0,1,2],var_name='sample')

        subject  trial_num sample  value
    0         1          1      0  -0.24
    1         1          2      0   0.14
    2         1          3      0  -0.67
    3         2          1      0  -1.52
    4         2          2      0  -0.00
    5         2          3      0  -1.73
    6         1          1      1  -0.70
    7         1          2      1  -0.70
    8         1          3      1  -0.29
    9         2          1      1  -0.70
    10        2          2      1  -0.72
    11        2          3      1   1.30
    12        1          1      2  -0.55
    13        1          2      2   0.10
    14        1          3      2  -0.44
    15        2          1      2   0.13
    16        2          2      2  -1.44
    17        2          3      2   0.73

각 시도는 동일한 수의 샘플을 가지고 있기 때문에 이것이 효과가 있었을 수도 있습니다 (3). 다른 표본 크기의 시험에는 더 영리한 것이 필요할 수 있습니다.


2

매우 늦은 답변이지만 이것을 추가하고 싶습니다.

sample_numOP의 예에서 열을 처리하는 바닐라 Python을 사용하는 빠른 솔루션 . 천만 개가 넘는 행과 2 천만 개가 넘는 결과가있는 자체 데이터 세트에서는 약 38 초가 걸립니다. 수용 된 솔루션은 그 양의 데이터로 완전히 분해 memory error되어 128GB의 RAM이있는 시스템으로 연결됩니다 .

df = df.reset_index(drop=True)
lstcol = df.lstcol.values
lstcollist = []
indexlist = []
countlist = []
for ii in range(len(lstcol)):
    lstcollist.extend(lstcol[ii])
    indexlist.extend([ii]*len(lstcol[ii]))
    countlist.extend([jj for jj in range(len(lstcol[ii]))])
df = pd.merge(df.drop("lstcol",axis=1),pd.DataFrame({"lstcol":lstcollist,"lstcol_num":countlist},
index=indexlist),left_index=True,right_index=True).reset_index(drop=True)

2

또한 매우 늦었지만 여기에 팬더> = 0.25 버전이없는 경우 Karvy1의 답변이 잘되었습니다. https : //.com/a/52511166/10740287

위의 예에서 다음과 같이 작성할 수 있습니다.

data = [(row.subject, row.trial_num, sample) for row in df.itertuples() for sample in row.samples]
data = pd.DataFrame(data, columns=['subject', 'trial_num', 'samples'])

속도 테스트 :

%timeit data = pd.DataFrame([(row.subject, row.trial_num, sample) for row in df.itertuples() for sample in row.samples], columns=['subject', 'trial_num', 'samples'])

루프 당 1.33ms ± 74.8µs (평균 ± 표준 편차 7 회 실행, 각 1000 루프)

%timeit data = df.set_index(['subject', 'trial_num'])['samples'].apply(pd.Series).stack().reset_index()

루프 당 4.9ms ± 189µs (평균 ± 표준 편차 7 회 실행, 각 100 회 루프)

%timeit data = pd.DataFrame({col:np.repeat(df[col].values, df['samples'].str.len())for col in df.columns.drop('samples')}).assign(**{'samples':np.concatenate(df['samples'].values)})

루프 당 1.38ms ± 25µs (평균 ± 표준 편차 7 회 실행, 각 1000 회 루프)


1
import pandas as pd
df = pd.DataFrame([{'Product': 'Coke', 'Prices': [100,123,101,105,99,94,98]},{'Product': 'Pepsi', 'Prices': [101,104,104,101,99,99,99]}])
print(df)
df = df.assign(Prices=df.Prices.str.split(',')).explode('Prices')
print(df)

팬더> = 0.25 버전에서 이것을 시도하십시오


1
이미 목록 .str.split(',')이기 때문에 필요가 없습니다 Prices.
Oren
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.