Выровнять столбец со значением типа list, дублируя соответственно значение другого столбца в Pandas


Уважаемые эксперты по пандам силы:

Я пытаюсь реализовать функцию для выравнивания столбца фрейма данных, который имеет элемент типа list, я хочу, чтобы для каждой строки фрейма данных, где столбец имеет элемент типа list, все столбцы, кроме указанного столбца, подлежащего выравниванию, были продублированы, в то время как указанный столбец будет иметь одно из значений в списке.

Следующие примеры иллюстрируют мои требования:

input = DataFrame({'A': [1, 2], 'B': [['a', 'b'], 'c']})
     A   B
0    1   [a, b]
1    2   c

expected = DataFrame({'A': [1, 1, 2], 'B': ['a', 'b', 'c']}, index=[0, 0, 1])

     A   B
0    1   a
0    1   b
1    2   c

Я чувствую, что может быть элегантное решение / концепция ради этого, но я борюсь.

Вот моя попытка, которая пока не работает.
def flattenColumn(df, column):
    '''column is a string of the column's name.
    for each value of the column's element (which might be a list), duplicate the rest of columns at the correspdonding row with the (each) value.
    '''
    def duplicate_if_needed(row):
        return concat([concat([row.drop(column, axis = 1), DataFrame({column: each})], axis = 1) for each in row[column][0]])
    return df.groupby(df.index).transform(duplicate_if_needed)

В знак признания помощи alko, вот мое тривиальное обобщение решения для работы с более чем 2 столбцами в фрейме данных:

def flattenColumn(input, column):
    '''
    column is a string of the column's name.
    for each value of the column's element (which might be a list),
    duplicate the rest of columns at the corresponding row with the (each) value.
    '''
    column_flat = pandas.DataFrame(
        [
            [i, c_flattened]
            for i, y in input[column].apply(list).iteritems()
            for c_flattened in y
        ],
        columns=['I', column]
    )
    column_flat = column_flat.set_index('I')
    return (
        input.drop(column, 1)
             .merge(column_flat, left_index=True, right_index=True)
    )
Единственное ограничение на данный момент состоит в том, что порядок столбцов изменился, столбец сплющился бы в самом правом месте, а не в его исходном положении. Это должно быть возможно исправить.
4 5

4 ответа:

Я думаю, что простой способ сгладить список списков был бы чистым кодом python, так как этот тип объекта не очень хорошо подходит для pandas или numpy. Так что вы можете сделать это, например, с

>>> b_flat = pd.DataFrame([[i, x] 
...               for i, y in input['B'].apply(list).iteritems() 
...                    for x in y], columns=list('IB'))
>>> b_flat = b_flat.set_index('I')

Расплющив столбец B, вы можете объединить его обратно:

>>> input[['A']].merge(b_flat, left_index=True, right_index=True)
   A  B
0  1  a
0  1  b
1  2  c

[3 rows x 2 columns]

Если вы хотите, чтобы индекс был воссоздан, как в вашем ожидаемом результате, вы можете добавить .reset_index(drop=True) к последней команде.

Удивительно, что нет более "родного" решения. Поместить ответ от @alko в функцию достаточно просто:

def unnest(df, col, reset_index=False):
    import pandas as pd
    col_flat = pd.DataFrame([[i, x] 
                       for i, y in df[col].apply(list).iteritems() 
                           for x in y], columns=['I', col])
    col_flat = col_flat.set_index('I')
    df = df.drop(col, 1)
    df = df.merge(col_flat, left_index=True, right_index=True)
    if reset_index:
        df = df.reset_index(drop=True)
    return df

Тогда просто

input = pd.DataFrame({'A': [1, 2], 'B': [['a', 'b'], 'c']})
expected = unnest(input, 'B')

Я думаю, было бы неплохо разрешить неинвестирование нескольких столбцов сразу и обработать возможность вложенного столбца с именем I, который бы сломал этот код.

Один лайнер-применение конструктора pd.DataFrame, объединение и присоединение к оригиналу.

my_df = pd.DataFrame({'a': [1, 2, 3], 'b': [2, 3, 4], 'c': [(1, 2), (1, 2), (2, 3)]})
my_df.join(pd.concat(map(lambda x: pd.DataFrame(list(x)), my_df['c']), axis=0))

Немного более простое / более читаемое решение, чем те, которые работали для меня выше.

 out = []
 for n, row in df.iterrows():
    for item in row['B']:
        row['flat_B'] = item
        out += [row.copy()]


flattened_df = pd.DataFrame(out)