Dataframe 셀 내부의 목록을 별도의 행으로 분해하는 방법


93

목록이 포함 된 팬더 셀을 각 값에 대한 행으로 바꾸려고합니다.

그래서 이것을 가져 가십시오.

여기에 이미지 설명 입력

nearest_neighbors각 값이 각 opponent인덱스 내의 행이되도록 열의 값을 압축 해제하고 스택하려면 어떻게하면 좋을까요? 이와 같은 작업을위한 pandas 메서드가 있습니까?


원하는 출력의 예와 지금까지 시도한 내용을 설명해 주시겠습니까? 잘라내어 붙여 넣을 수있는 샘플 데이터를 제공하면 다른 사람이 도움을주는 것이 가장 쉽습니다.
dagrha

pd.DataFrame(df.nearest_neighbors.values.tolist())이 열의 포장을 풀고 다른 열과 pd.merge붙일 때 사용할 수 있습니다 .
hellpanderr 2015 년

@helpanderr 나는 values.tolist()여기서 아무것도 하지 않는다고 생각한다 ; 열은 이미 목록입니다
maxymoo 2015 년


1
관련이 있지만 자세한 내용을 포함합니다. stackoverflow.com/questions/53218931/…
BEN_YO

답변:


54

아래 코드에서 먼저 인덱스를 재설정하여 행 반복을 더 쉽게 만듭니다.

외부 목록의 각 요소가 대상 DataFrame의 행이고 내부 목록의 각 요소가 열 중 하나 인 목록 목록을 만듭니다. 이 중첩 된 목록은 궁극적으로 연결되어 원하는 DataFrame을 생성합니다.

내가 사용 lambda의 각 요소에 대해 행을 만들 목록 반복과 기능을 함께를 nearest_neighbors관련과 짝 name하고 opponent.

마지막으로, 나는이 목록에서 새 DataFrame (원래 열 이름을 사용하여 인덱스 다시 설정 생성 nameopponent).

df = (pd.DataFrame({'name': ['A.J. Price'] * 3, 
                    'opponent': ['76ers', 'blazers', 'bobcats'], 
                    'nearest_neighbors': [['Zach LaVine', 'Jeremy Lin', 'Nate Robinson', 'Isaia']] * 3})
      .set_index(['name', 'opponent']))

>>> df
                                                    nearest_neighbors
name       opponent                                                  
A.J. Price 76ers     [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           blazers   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           bobcats   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]

df.reset_index(inplace=True)
rows = []
_ = df.apply(lambda row: [rows.append([row['name'], row['opponent'], nn]) 
                         for nn in row.nearest_neighbors], axis=1)
df_new = pd.DataFrame(rows, columns=df.columns).set_index(['name', 'opponent'])

>>> df_new
                    nearest_neighbors
name       opponent                  
A.J. Price 76ers          Zach LaVine
           76ers           Jeremy Lin
           76ers        Nate Robinson
           76ers                Isaia
           blazers        Zach LaVine
           blazers         Jeremy Lin
           blazers      Nate Robinson
           blazers              Isaia
           bobcats        Zach LaVine
           bobcats         Jeremy Lin
           bobcats      Nate Robinson
           bobcats              Isaia

2017 년 6 월 수정

다른 방법은 다음과 같습니다.

>>> (pd.melt(df.nearest_neighbors.apply(pd.Series).reset_index(), 
             id_vars=['name', 'opponent'],
             value_name='nearest_neighbors')
     .set_index(['name', 'opponent'])
     .drop('variable', axis=1)
     .dropna()
     .sort_index()
     )

apply(pd.Series)가장 작은 프레임에서는 괜찮지 만 합리적인 크기의 프레임에 대해서는 더 성능이 좋은 솔루션을 재고해야합니다. 코드에서 pandas apply ()를 언제 사용해야합니까?를 참조하십시오 . (더 나은 해결책은 열을 먼저 나열하는 것입니다.)
cs95

2
pandas 0.25 에서는 explode()메서드를 추가 하여 목록과 같은 열을 분해하는 작업이 크게 단순화 되었습니다 . 여기와 동일한 df 설정을 사용하여 예제와 함께 답변 을 추가 했습니다 .
joelostblom

@joelostblom 반갑습니다. 현재 사용법과 함께 예제를 추가해 주셔서 감사합니다.
알렉산더

34

사용 apply(pd.Series)하고 stack, 다음 reset_indexto_frame

In [1803]: (df.nearest_neighbors.apply(pd.Series)
              .stack()
              .reset_index(level=2, drop=True)
              .to_frame('nearest_neighbors'))
Out[1803]:
                    nearest_neighbors
name       opponent
A.J. Price 76ers          Zach LaVine
           76ers           Jeremy Lin
           76ers        Nate Robinson
           76ers                Isaia
           blazers        Zach LaVine
           blazers         Jeremy Lin
           blazers      Nate Robinson
           blazers              Isaia
           bobcats        Zach LaVine
           bobcats         Jeremy Lin
           bobcats      Nate Robinson
           bobcats              Isaia

세부

In [1804]: df
Out[1804]:
                                                   nearest_neighbors
name       opponent
A.J. Price 76ers     [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           blazers   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           bobcats   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]

1
솔루션의 우아함을 사랑하십시오! 혹시 다른 접근 방식과 비교하여 벤치마킹 했습니까?
rpyzh

1
의 결과는 df.nearest_neighbors.apply(pd.Series)나에게 매우 놀랍습니다.
Calum You

1
@rpyzh 예, 꽤 우아하지만 한심하게 느립니다.
cs95

34
  • pandas 0.25 에서는 다음과 같은 explode()메서드를 추가 하여 목록과 같은 열을 분해하는 작업이 크게 단순화 되었습니다 .
df = (pd.DataFrame({'name': ['A.J. Price'] * 3, 
                    'opponent': ['76ers', 'blazers', 'bobcats'], 
                    'nearest_neighbors': [['Zach LaVine', 'Jeremy Lin', 'Nate Robinson', 'Isaia']] * 3})
      .set_index(['name', 'opponent']))

df.explode('nearest_neighbors')

밖:

                    nearest_neighbors
name       opponent                  
A.J. Price 76ers          Zach LaVine
           76ers           Jeremy Lin
           76ers        Nate Robinson
           76ers                Isaia
           blazers        Zach LaVine
           blazers         Jeremy Lin
           blazers      Nate Robinson
           blazers              Isaia
           bobcats        Zach LaVine
           bobcats         Jeremy Lin
           bobcats      Nate Robinson
           bobcats              Isaia

2
이것은 단일 열에 대해서만 작동합니다 (0.25부터). 보다 일반적인 솔루션 은 여기여기 를 참조 하십시오 .
cs95

이것은 가장 빠른 솔루션입니다 (실제로 폭발 할 목록이있는 열이 하나만있는 경우 또는 mongodb에서 호출되는 "풀기")
annakeuchenius

16

나는 이것이 정말 좋은 질문이라고 생각합니다. Hive에서는를 사용할 것 EXPLODE입니다. Pandas가 기본적으로이 기능을 포함해야하는 경우가 있다고 생각합니다. 아마도 다음과 같이 중첩 된 생성기 이해로 목록 열을 폭발시킬 것입니다.

pd.DataFrame({
    "name": i[0],
    "opponent": i[1],
    "nearest_neighbor": neighbour
    }
    for i, row in df.iterrows() for neighbour in row.nearest_neighbors
    ).set_index(["name", "opponent"])

이 솔루션을 사용하면 각 행에 대해 목록 항목 수를 다르게 할 수 있다는 점이 마음에 듭니다.
user1718097

이 방법으로 원래 색인을 유지하는 방법이 있습니까?
SummerEla 2018

2
@SummerEla lol 이것은 정말 오래된 대답이었습니다. 나는 내가 지금 어떻게 할 것인지를 보여주기 위해 업데이트했습니다
maxymoo

1
@maxymoo 그래도 여전히 좋은 질문입니다. 업데이트 해주셔서 감사합니다!
SummerEla

나는이 유용하다고과로 설정되어 패키지
오렌

11

빠른 I 발견 방법은 지금까지와 DataFrame 연장되고 .iloc다시 할당 병합 대상 칼럼.

일반적인 입력 (약간 복제 됨)이 주어지면 :

df = (pd.DataFrame({'name': ['A.J. Price'] * 3, 
                    'opponent': ['76ers', 'blazers', 'bobcats'], 
                    'nearest_neighbors': [['Zach LaVine', 'Jeremy Lin', 'Nate Robinson', 'Isaia']] * 3})
      .set_index(['name', 'opponent']))
df = pd.concat([df]*10)

df
Out[3]: 
                                                   nearest_neighbors
name       opponent                                                 
A.J. Price 76ers     [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           blazers   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           bobcats   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           76ers     [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
           blazers   [Zach LaVine, Jeremy Lin, Nate Robinson, Isaia]
...

다음과 같은 제안 된 대안이 주어집니다.

col_target = 'nearest_neighbors'

def extend_iloc():
    # Flatten columns of lists
    col_flat = [item for sublist in df[col_target] for item in sublist] 
    # Row numbers to repeat 
    lens = df[col_target].apply(len)
    vals = range(df.shape[0])
    ilocations = np.repeat(vals, lens)
    # Replicate rows and add flattened column of lists
    cols = [i for i,c in enumerate(df.columns) if c != col_target]
    new_df = df.iloc[ilocations, cols].copy()
    new_df[col_target] = col_flat
    return new_df

def melt():
    return (pd.melt(df[col_target].apply(pd.Series).reset_index(), 
             id_vars=['name', 'opponent'],
             value_name=col_target)
            .set_index(['name', 'opponent'])
            .drop('variable', axis=1)
            .dropna()
            .sort_index())

def stack_unstack():
    return (df[col_target].apply(pd.Series)
            .stack()
            .reset_index(level=2, drop=True)
            .to_frame(col_target))

나는 그것이 가장 빠르다extend_iloc() 는 것을 안다 .

%timeit extend_iloc()
3.11 ms ± 544 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit melt()
22.5 ms ± 1.25 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit stack_unstack()
11.5 ms ± 410 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

좋은 평가
javadba

2
감사합니다. 정말 도움이되었습니다. 나는 extend_iloc 솔루션을 사용하고 발견 cols = [c for c in df.columns if c != col_target] 해야한다 : 오류를 열 인덱스가 표시되지 않은 경우. cols = [i for i,c in enumerate(df.columns) if c != col_target]df.iloc[ilocations, cols].copy()
jdungan

iloc 제안에 다시 한번 감사드립니다. 여기에 어떻게 작동하는지에 대한 자세한 설명을 썼습니다 : medium.com/@johnadungan/… . 비슷한 도전을 가진 사람에게 도움이되기를 바랍니다.
jdungan

7

apply (pd.Series)로 더 좋은 대안 솔루션 :

df = pd.DataFrame({'listcol':[[1,2,3],[4,5,6]]})

# expand df.listcol into its own dataframe
tags = df['listcol'].apply(pd.Series)

# rename each variable is listcol
tags = tags.rename(columns = lambda x : 'listcol_' + str(x))

# join the tags dataframe back to the original dataframe
df = pd.concat([df[:], tags[:]], axis=1)

이것은 행이 아닌 열을 확장합니다.
Oleg

@Oleg 맞습니다.하지만 항상 DataFrame을 전치 한 다음 pd.Series를 적용 할 수 있습니다. 대부분의 다른 제안보다 더 간단합니다
Philipp Schwarz

7

Hive의 EXPLODE 기능과 유사합니다.

import copy

def pandas_explode(df, column_to_explode):
    """
    Similar to Hive's EXPLODE function, take a column with iterable elements, and flatten the iterable to one element 
    per observation in the output table

    :param df: A dataframe to explod
    :type df: pandas.DataFrame
    :param column_to_explode: 
    :type column_to_explode: str
    :return: An exploded data frame
    :rtype: pandas.DataFrame
    """

    # Create a list of new observations
    new_observations = list()

    # Iterate through existing observations
    for row in df.to_dict(orient='records'):

        # Take out the exploding iterable
        explode_values = row[column_to_explode]
        del row[column_to_explode]

        # Create a new observation for every entry in the exploding iterable & add all of the other columns
        for explode_value in explode_values:

            # Deep copy existing observation
            new_observation = copy.deepcopy(row)

            # Add one (newly flattened) value from exploding iterable
            new_observation[column_to_explode] = explode_value

            # Add to the list of new observations
            new_observations.append(new_observation)

    # Create a DataFrame
    return_df = pandas.DataFrame(new_observations)

    # Return
    return return_df

1
실행하면 다음과 같은 오류가 발생합니다.NameError: global name 'copy' is not defined
frmsaul

4

따라서이 모든 답변은 훌륭하지만 ^ 정말 간단한 ^ 무언가를 원했기 때문에 여기에 내 기여가 있습니다.

def explode(series):
    return pd.Series([x for _list in series for x in _list])                               

그게 다야 .. 목록이 '폭발'되는 새 시리즈를 원할 때 이것을 사용하십시오. 다음은 타코 선택에 대해 value_counts ()를 수행하는 예입니다. :)

In [1]: my_df = pd.DataFrame(pd.Series([['a','b','c'],['b','c'],['c']]), columns=['tacos'])      
In [2]: my_df.head()                                                                               
Out[2]: 
   tacos
0  [a, b, c]
1     [b, c]
2        [c]

In [3]: explode(my_df['tacos']).value_counts()                                                     
Out[3]: 
c    3
b    2
a    1

2

다음은 더 큰 데이터 프레임에 대한 잠재적 인 최적화입니다. "exploding"필드에 동일한 값이 여러 개있을 때 더 빨리 실행됩니다. (데이터 프레임이 필드의 고유 값 수에 비해 클수록이 코드의 성능이 향상됩니다.)

def lateral_explode(dataframe, fieldname): 
    temp_fieldname = fieldname + '_made_tuple_' 
    dataframe[temp_fieldname] = dataframe[fieldname].apply(tuple)       
    list_of_dataframes = []
    for values in dataframe[temp_fieldname].unique().tolist(): 
        list_of_dataframes.append(pd.DataFrame({
            temp_fieldname: [values] * len(values), 
            fieldname: list(values), 
        }))
    dataframe = dataframe[list(set(dataframe.columns) - set([fieldname]))]\ 
        .merge(pd.concat(list_of_dataframes), how='left', on=temp_fieldname) 
    del dataframe[temp_fieldname]

    return dataframe

1

.iloc모든 목록 열을 자동으로 평면화하도록 Oleg의 대답을 확장합니다 .

def extend_iloc(df):
    cols_to_flatten = [colname for colname in df.columns if 
    isinstance(df.iloc[0][colname], list)]
    # Row numbers to repeat 
    lens = df[cols_to_flatten[0]].apply(len)
    vals = range(df.shape[0])
    ilocations = np.repeat(vals, lens)
    # Replicate rows and add flattened column of lists
    with_idxs = [(i, c) for (i, c) in enumerate(df.columns) if c not in cols_to_flatten]
    col_idxs = list(zip(*with_idxs)[0])
    new_df = df.iloc[ilocations, col_idxs].copy()

    # Flatten columns of lists
    for col_target in cols_to_flatten:
        col_flat = [item for sublist in df[col_target] for item in sublist]
        new_df[col_target] = col_flat

    return new_df

이것은 각 목록 열의 목록 길이가 같다고 가정합니다.


1

apply (pd.Series)를 사용하는 대신 열을 평평하게 할 수 있습니다. 이것은 성능을 향상시킵니다.

df = (pd.DataFrame({'name': ['A.J. Price'] * 3, 
                'opponent': ['76ers', 'blazers', 'bobcats'], 
                'nearest_neighbors': [['Zach LaVine', 'Jeremy Lin', 'Nate Robinson', 'Isaia']] * 3})
  .set_index(['name', 'opponent']))



%timeit (pd.DataFrame(df['nearest_neighbors'].values.tolist(), index = df.index)
           .stack()
           .reset_index(level = 2, drop=True).to_frame('nearest_neighbors'))

1.87 ms ± 9.74 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)


%timeit (df.nearest_neighbors.apply(pd.Series)
          .stack()
          .reset_index(level=2, drop=True)
          .to_frame('nearest_neighbors'))

2.73 ms ± 16.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

IndexError : Too many levels : Index has only 2 levels, not 3, when I try my example
vinsent paramanantham

1
예에 따라 reset_index에서 "레벨"을 변경해야합니다
suleep kumar
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.