[python] 팬더 데이터 프레임 문자열 항목을 분할하여 행 분리

I가 가지고 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)



답변

이런 식으로 어떻습니까 :

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

그런 다음 열의 이름을 바꾸면됩니다.


답변

UPDATE2 : 더 일반적인 벡터화 함수로 normal여러 list열과 여러 열에서 작동합니다.

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

데모:

배수 list 열-모든 list열은 각 행에서 동일한 요소 수를 가져야합니다.

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의 솔루션만큼 빠르고 거의 DF에 적용 할 수 있도록 좀 더 일반화하고 싶었습니다.

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


답변

허용 된 답변보다 빠른 것을 찾기 위해 고통스러운 실험을 한 후에 나는 이것을 작동 시켰습니다. 내가 시도한 데이터 세트에서 약 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


답변

이 일반적인 작업을 위해 작성한 함수는 다음과 같습니다 . 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

이 기능을 사용하면 원래 질문 은 다음과 같이 간단합니다.

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


답변

팬더> = 0.25

시리즈 및 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

이것은ravelrepeat 빈 목록을 완전히 무시하고 NaN을 질식시키는 + 기반 솔루션 보다 심각한 이점 입니다.


답변

비슷한 질문 : 팬더 : 열의 텍스트를 여러 행으로 나누려면 어떻게합니까?

당신은 할 수 있습니다 :

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


답변

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.arange와 함께 사용할 repeat수있는 데이터 프레임 인덱스 위치를 생성하는 데 사용할 것입니다 iloc.

자주하는 질문

왜 사용하지 loc않습니까?

인덱스가 고유하지 않을 수 있으므로 사용 loc하면 쿼리 된 인덱스와 일치하는 모든 행이 반환됩니다.

values속성 을 사용하지 않고 슬라이스하지 않습니까?

를 호출 할 때 values데이터 프레임 전체가 하나의 응집성 “블록”에있는 경우 Pandas는 “블록”인 배열의보기를 반환합니다. 그렇지 않으면 팬더는 새로운 배열을 함께 모아야합니다. 결합 할 때 해당 배열은 균일 한 dtype이어야합니다. 종종 dtype 인 배열을 반환하는 것을 의미 object합니다. iloc슬라이싱 대신 사용 하여values속성 하면 처리 할 필요가 없습니다.

왜 사용 assign합니까?

내가 사용하는 경우 assign나 폭발 해요 것과 같은 열 이름을 사용하여, 나는 기존의 열을 덮어하고 dataframe에서의 위치를 유지합니다.

인덱스 값이 반복되는 이유는 무엇입니까?

iloc반복 된 위치에서 사용함으로써 결과 인덱스는 동일한 반복 패턴을 보여줍니다. 각 요소에 대해 하나의 반복이 목록 또는 문자열입니다.
이것으로 재설정 할 수 있습니다reset_index(drop=True)


문자열

문자열을 너무 일찍 분할하고 싶지 않습니다. 대신 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)})