Cách bùng nổ danh sách bên trong ô Dataframe thành các hàng riêng biệt


93

Tôi đang tìm cách biến một ô gấu trúc chứa danh sách thành các hàng cho mỗi giá trị đó.

Vì vậy, hãy lấy cái này:

nhập mô tả hình ảnh ở đây

Nếu tôi muốn giải nén và xếp chồng các giá trị trong nearest_neighborscột sao cho mỗi giá trị sẽ là một hàng trong mỗi opponentchỉ mục, tốt nhất tôi nên làm như thế nào về điều này? Có phương pháp nào dành cho gấu trúc dành cho các hoạt động như thế này không?


Bạn có thể cho một ví dụ về đầu ra mong muốn của bạn và những gì bạn đã thử cho đến nay? Người khác sẽ giúp bạn dễ dàng nhất nếu bạn cung cấp một số dữ liệu mẫu có thể được cắt và dán.
dagrha

Bạn có thể sử dụng pd.DataFrame(df.nearest_neighbors.values.tolist())để giải nén cột này và sau đó pd.mergedán nó với những cột khác.
hellpanderr

@helpanderr Tôi không nghĩ là values.tolist()làm bất cứ điều gì ở đây; cột đã là một danh sách
maxymoo


1
Có liên quan nhưng chứa nhiều chi tiết hơn stackoverflow.com/questions/53218931/…
BEN_YO

Câu trả lời:


54

Trong đoạn mã dưới đây, trước tiên tôi đặt lại chỉ mục để làm cho việc lặp hàng dễ dàng hơn.

Tôi tạo một danh sách các danh sách trong đó mỗi phần tử của danh sách bên ngoài là một hàng của DataFrame đích và mỗi phần tử của danh sách bên trong là một trong các cột. Danh sách lồng nhau này cuối cùng sẽ được nối để tạo DataFrame mong muốn.

Tôi sử dụng một lambdahàm cùng với việc lặp lại danh sách để tạo một hàng cho mỗi phần tử của nearest_neighborscặp có liên quan nameopponent.

Cuối cùng, tôi tạo một DataFrame mới từ danh sách này (sử dụng tên cột ban đầu và thiết lập chỉ mục trở lại 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

CHỈNH SỬA THÁNG 6 NĂM 2017

Một phương pháp thay thế như sau:

>>> (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)tốt với những khung nhỏ nhất, nhưng đối với bất kỳ khung có kích thước hợp lý nào, bạn nên xem xét lại một giải pháp hiệu quả hơn. Xem Khi nào tôi nên sử dụng pandas apply () trong mã của mình? (Một giải pháp tốt hơn là để listify cột đầu tiên.)
cs95

2
Việc giải phóng một cột giống như danh sách đã được đơn giản hóa đáng kể trong pandas 0,25 với việc bổ sung explode()phương thức. Tôi đã thêm một câu trả lời với một ví dụ bằng cách sử dụng cùng một thiết lập df như ở đây.
joelostblom 19/07/19

@joelostblom Rất vui khi nghe. Cảm ơn bạn đã thêm ví dụ với cách sử dụng hiện tại.
Alexander

35
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')

Ngoài:

                    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
Lưu ý rằng điều này chỉ hoạt động cho một cột duy nhất (tính đến 0,25). Xem tại đâytại đây để biết thêm các giải pháp chung.
cs95

đây là giải pháp nhanh nhất dễ dàng nhất (thực sự nếu bạn chỉ có một cột với danh sách để phát nổ hoặc "để giải phóng" như nó sẽ được gọi trong mongodb)
annakeuchenius

34

Sử dụng apply(pd.Series)stack, sau đó 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

Chi tiết

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
Yêu thích sự sang trọng của giải pháp của bạn! Bạn có tình cờ đánh giá nó so với các cách tiếp cận khác không?
rpyzh

1
Kết quả df.nearest_neighbors.apply(pd.Series)là rất đáng kinh ngạc đối với tôi;
Calum You

1
@rpyzh Vâng, nó khá thanh lịch, nhưng chậm một cách thảm hại.
cs95

16

Tôi nghĩ đây là một câu hỏi thực sự hay, trong Hive mà bạn sẽ sử dụng EXPLODE, tôi nghĩ rằng có một trường hợp được thực hiện rằng Pandas nên bao gồm chức năng này theo mặc định. Tôi có thể sẽ làm nổ cột danh sách với cách hiểu trình tạo lồng nhau như thế này:

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

Tôi thích cách giải pháp này cho phép số lượng mục danh sách khác nhau cho mỗi hàng.
user1718097

Có cách nào để giữ chỉ mục gốc bằng phương pháp này không?
SummerEla

2
@SummerEla lol đây là một câu trả lời thực sự cũ, tôi đã cập nhật để hiển thị như thế nào tôi sẽ làm ngay bây giờ
maxymoo

1
@maxymoo Tuy nhiên, đây vẫn là một câu hỏi hay. Cảm ơn vì đã cập nhật!
SummerEla

Tôi thấy điều này rất hữu ích và biến nó thành một gói
Oren

11

Các nhanh nhất phương pháp tôi đã tìm thấy cho đến nay được mở rộng DataFrame với .ilocvà gán lại phẳng cột mục tiêu.

Với đầu vào thông thường (sao chép một chút):

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

Đưa ra các lựa chọn thay thế được đề xuất sau:

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

Tôi thấy đó extend_iloc()nhanh nhất :

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

đánh giá tốt
javadba

2
Cảm ơn vì điều này, nó thực sự đã giúp tôi. Tôi đã sử dụng giải pháp expand_iloc và thấy rằng cols = [c for c in df.columns if c != col_target] : cols = [i for i,c in enumerate(df.columns) if c != col_target] Các df.iloc[ilocations, cols].copy()lỗi nếu không được trình bày với chỉ mục cột.
jdungan

Cảm ơn một lần nữa cho gợi ý iloc. Tôi đã viết một giải thích chi tiết về cách nó hoạt động tại đây: medium.com/@johnadungan/… . Hy vọng nó sẽ giúp bất cứ ai có một thách thức tương tự.
jdungan

7

Giải pháp thay thế tốt hơn với áp dụng (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)

Cái này mở rộng cột chứ không phải hàng.
Oleg

@Oleg đúng, nhưng bạn luôn có thể hoán vị DataFrame và sau đó áp dụng pd.Series -way đơn giản hơn hầu hết các đề xuất khác
Philipp Schwarz

7

Tương tự với chức năng EXPLODE của Hive:

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
Khi tôi chạy điều này, tôi gặp lỗi sau:NameError: global name 'copy' is not defined
frmsaul

4

Vì vậy, tất cả các câu trả lời này đều tốt nhưng tôi muốn một cái gì đó ^ thực sự đơn giản ^ vì vậy đây là đóng góp của tôi:

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

Vậy là xong .. chỉ cần sử dụng cái này khi bạn muốn một loạt phim mới, nơi các danh sách được 'bùng nổ'. Đây là một ví dụ trong đó chúng tôi thực hiện value_counts () trên các lựa chọn taco :)

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

Đây là một cách tối ưu hóa tiềm năng cho các khung dữ liệu lớn hơn. Điều này chạy nhanh hơn khi có một số giá trị bằng nhau trong trường "phát nổ". (Khung dữ liệu càng lớn so với số giá trị duy nhất trong trường, mã này sẽ hoạt động tốt hơn.)

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

Mở rộng .iloccâu trả lời của Oleg để tự động san phẳng tất cả các cột danh sách:

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

Điều này giả định rằng mỗi cột danh sách có độ dài danh sách bằng nhau.


1

Thay vì sử dụng apply (pd.Series), bạn có thể làm phẳng cột. Điều này cải thiện hiệu suất.

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: Quá nhiều cấp độ: Chỉ số chỉ có 2 cấp độ chứ không phải 3 cấp khi tôi thử ví dụ của mình
vinsent paramanantham

1
Bạn phải thay đổi "cấp độ" trong reset_index theo ví dụ của bạn
suleep kumar
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.