Разделить ячейку на несколько строк в кадре данных pandas

У меня есть фрейм данных, содержащий данные о заказах, каждый заказ имеет несколько пакетов, которые хранятся в виде разделенных запятыми столбцов [ package & package_code ]

Я хочу разделить данные пакетов и создать строку для каждого пакета, включая детали его заказа

Вот пример входного фрейма данных:

import pandas as pd
df = pd.DataFrame({"order_id":[1,3,7],"order_date":["20/5/2018","22/5/2018","23/5/2018"], "package":["p1,p2,p3","p4","p5,p6"],"package_code":["#111,#222,#333","#444","#555,#666"]})

Input Dataframe

И это то, что я пытаюсь достичь в качестве результата: Output

Как я могу сделать это с пандами?

Ответ 1

Здесь один из способов использования numpy.repeat и itertools.chain. Концептуально это именно то, что вы хотите сделать: повторите некоторые значения, добавьте другие. Рекомендуется для небольшого количества столбцов, в противном случае методы на основе stack могут улучшиться.

import numpy as np
from itertools import chain

# return list from series of comma-separated strings
def chainer(s):
    return list(chain.from_iterable(s.str.split(',')))

# calculate lengths of splits
lens = df['package'].str.split(',').map(len)

# create new dataframe, repeating or chaining as appropriate
res = pd.DataFrame({'order_id': np.repeat(df['order_id'], lens),
                    'order_date': np.repeat(df['order_date'], lens),
                    'package': chainer(df['package']),
                    'package_code': chainer(df['package_code'])})

print(res)

   order_id order_date package package_code
0         1  20/5/2018      p1         #111
0         1  20/5/2018      p2         #222
0         1  20/5/2018      p3         #333
1         3  22/5/2018      p4         #444
2         7  23/5/2018      p5         #555
2         7  23/5/2018      p6         #666

Ответ 2

Это должно работать для любого количества столбцов, подобных этому. Суть в том, что маска стека складывается с помощью str.split.

(df.set_index(['order_date', 'order_id'])
   .stack()
   .str.split(',', expand=True)
   .stack()
   .unstack(-2)
   .reset_index(-1, drop=True)
   .reset_index()
)

  order_date  order_id package package_code
0  20/5/2018         1      p1         #111
1  20/5/2018         1      p2         #222
2  20/5/2018         1      p3         #333
3  22/5/2018         3      p4         #444
4  23/5/2018         7      p5         #555
5  23/5/2018         7      p6         #666

Существует еще одна альтернатива для исполнителей, включающая chain, но вам нужно будет явно цепочки и повторять каждый столбец (небольшая проблема с большим количеством столбцов). Выберите все, что лучше подходит для описания вашей проблемы, так как нет единого ответа.


подробности

Во-первых, установите столбцы, которые не должны касаться индекса.

df.set_index(['order_date', 'order_id'])

                      package    package_code
order_date order_id                          
20/5/2018  1         p1,p2,p3  #111,#222,#333
22/5/2018  3               p4            #444
23/5/2018  7            p5,p6       #555,#666

Затем stack строки.

_.stack()

order_date  order_id              
20/5/2018   1         package               p1,p2,p3
                      package_code    #111,#222,#333
22/5/2018   3         package                     p4
                      package_code              #444
23/5/2018   7         package                  p5,p6
                      package_code         #555,#666
dtype: object

У нас есть серия сейчас. Поэтому вызовите str.split в запятую.

_.str.split(',', expand=True)

                                     0     1     2
order_date order_id                               
20/5/2018  1        package         p1    p2    p3
                    package_code  #111  #222  #333
22/5/2018  3        package         p4  None  None
                    package_code  #444  None  None
23/5/2018  7        package         p5    p6  None
                    package_code  #555  #666  None

Нам нужно избавиться от значений NULL, поэтому снова вызовите stack.

_.stack()

order_date  order_id                 
20/5/2018   1         package       0      p1
                                    1      p2
                                    2      p3
                      package_code  0    #111
                                    1    #222
                                    2    #333
22/5/2018   3         package       0      p4
                      package_code  0    #444
23/5/2018   7         package       0      p5
                                    1      p6
                      package_code  0    #555
                                    1    #666
dtype: object

Мы почти там. Теперь мы хотим, чтобы второй последний уровень индекса стал нашим столбцом, поэтому unstack using unstack(-2) (unstack на втором последнем уровне)

_.unstack(-2)

                      package package_code
order_date order_id                       
20/5/2018  1        0      p1         #111
                    1      p2         #222
                    2      p3         #333
22/5/2018  3        0      p4         #444
23/5/2018  7        0      p5         #555
                    1      p6         #666

Избавьтесь от лишнего последнего уровня, используя reset_index:

_.reset_index(-1, drop=True)

                    package package_code
order_date order_id                     
20/5/2018  1             p1         #111
           1             p2         #222
           1             p3         #333
22/5/2018  3             p4         #444
23/5/2018  7             p5         #555
           7             p6         #666

И наконец,

_.reset_index()

  order_date  order_id package package_code
0  20/5/2018         1      p1         #111
1  20/5/2018         1      p2         #222
2  20/5/2018         1      p3         #333
3  22/5/2018         3      p4         #444
4  23/5/2018         7      p5         #555
5  23/5/2018         7      p6         #666

Ответ 3

Близко к холодному методу :-)

df.set_index(['order_date','order_id']).apply(lambda x : x.str.split(',')).stack().apply(pd.Series).stack().unstack(level=2).reset_index(level=[0,1])
Out[538]: 
  order_date  order_id package package_code
0  20/5/2018         1      p1         #111
1  20/5/2018         1      p2         #222
2  20/5/2018         1      p3         #333
0  22/5/2018         3      p4         #444
0  23/5/2018         7      p5         #555
1  23/5/2018         7      p6         #666