Як розгорнути список всередині комірки Dataframe в окремі рядки


93

Я прагну перетворити клітинку панд, що містить список, у рядки для кожного з цих значень.

Отже, візьмімо це:

введіть тут опис зображення

Якщо я хотів би розпакувати та скласти значення у nearest_neighborsстовпці так, щоб кожне значення було рядком у кожному opponentіндексі, як би я найкраще це зробив? Чи існують методи панд, призначені для таких операцій?


Не могли б ви навести приклад бажаного результату та те, що ви пробували до цього часу? Іншим найпростіше допомогти, якщо ви надаєте деякі зразкові дані, які також можна вирізати та вставити.
dagrha

Ви можете pd.DataFrame(df.nearest_neighbors.values.tolist())розпакувати цей стовпець, а потім pd.mergeсклеїти його з іншими.
hellpanderr

@helpanderr я не думаю values.tolist(), що тут щось робиться; стовпець вже є списком
maxymoo


1
Пов'язані , але містять більш детальну stackoverflow.com/questions/53218931 / ...
BEN_YO

Відповіді:


54

У наведеному нижче коді я спочатку скидаю індекс, щоб полегшити ітерацію рядків.

Я створюю список списків, де кожен елемент зовнішнього списку є рядком цільового DataFrame, а кожен елемент внутрішнього списку є одним із стовпців. Зрештою цей вкладений список буде об’єднаний для створення бажаного DataFrame.

Я використовую lambdaфункцію разом з ітерацією списку, щоб створити рядок для кожного елемента в nearest_neighborsпарі з відповідними nameта opponent.

Нарешті, я створюю новий DataFrame із цього списку (використовуючи оригінальні назви стовпців та повертаючи індекс до nameта opponent).

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

Альтернативний спосіб полягає в наступному:

>>> (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
Розширення стовпця, подібного до списку, було значно спрощено в пандах 0,25 із додаванням explode()методу. Я додав відповідь із прикладом, використовуючи ту саму установку df, що і тут.
joelostblom

@joelostblom Приємно чути. Дякуємо, що додали приклад із поточним використанням.
Олександр

34

Використовуйте apply(pd.Series)та stack, потім reset_indexіto_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
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

2
@SummerEla lol це була справді стара відповідь, я оновив, щоб показати, як я це зробив би зараз
maxymoo

1
@maxymoo Це все ще прекрасне питання. Дякуємо за оновлення!
SummerEla

Я знайшов це корисним і перетворив його на пакет
Орен

11

Швидкий метод , який я знайшов до сих пір розширює 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 05.03.18

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] The df.iloc[ilocations, cols].copy()помилка , якщо не представлений з індексом стовпчика.
jdungan

Ще раз спасибі за пропозицію iloc. Детальне пояснення того, як це працює, я написав тут: medium.com/@johnadungan/… . Сподіваюся, це допоможе кожному, хто має подібний виклик.
jdungan

7

Приємніше альтернативне рішення із застосуванням (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 правильно, але ви завжди можете транспонувати DataFrame, а потім застосувати pd.Series - набагато простіше, ніж більшість інших пропозицій
Філіп Шварц

7

Подібно до функціональності EXPLODE 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
Коли я запускаю це, я отримую таку помилку: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

Ось потенційна оптимізація для більших фреймів даних. Це працює швидше, коли в полі "вибух" є кілька рівних значень. (Чим більший фрейм даних порівнюється з унікальним підрахунком значень у полі, тим кращим буде цей код.)

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на автоматичне згладжування всіх списків-стовпців:

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: Забагато рівнів: Індекс має лише 2 рівні, а не 3, коли я пробую свій приклад
vinsent paramanantham

1
Ви повинні змінити "рівень" у reset_index відповідно до вашого прикладу
suleep kumar
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.