панди: Як розділити текст у стовпці на кілька рядків?


135

Я працюю з великим файлом csv, і наступний до останнього стовпця містить рядок тексту, який я хочу розділити за певним роздільником. Мені було цікаво, чи існує простий спосіб зробити це за допомогою панди чи пітона?

CustNum  CustomerName     ItemQty  Item   Seatblocks                 ItemExt
32363    McCartney, Paul      3     F04    2:218:10:4,6                   60
31316    Lennon, John        25     F01    1:13:36:1,12 1:13:37:1,13     300

Я хочу розділити пробіл, (' ')а потім двокрапку (':')в Seatblocksстовпці, але кожна клітина призведе до різної кількості стовпців. У мене є функція переставляти стовпці, щоб Seatblocksстовпець знаходився в кінці аркуша, але я не впевнений, що робити звідти. Я можу це зробити в excel за допомогою вбудованої text-to-columnsфункції та швидкого макросу, але мій набір даних має занадто багато записів для роботи excel.

Зрештою, я хочу взяти записи таких Джона Леннона і створити кілька ліній, з інформацією про кожен набір місць на окремому рядку.


це велике питання стосується FlatMap у пандах, яких наразі не існує
cdarlint

Відповіді:


203

Це розділяє простір блоків сидіння і надає кожному свій ряд.

In [43]: df
Out[43]: 
   CustNum     CustomerName  ItemQty Item                 Seatblocks  ItemExt
0    32363  McCartney, Paul        3  F04               2:218:10:4,6       60
1    31316     Lennon, John       25  F01  1:13:36:1,12 1:13:37:1,13      300

In [44]: s = df['Seatblocks'].str.split(' ').apply(Series, 1).stack()

In [45]: s.index = s.index.droplevel(-1) # to line up with df's index

In [46]: s.name = 'Seatblocks' # needs a name to join

In [47]: s
Out[47]: 
0    2:218:10:4,6
1    1:13:36:1,12
1    1:13:37:1,13
Name: Seatblocks, dtype: object

In [48]: del df['Seatblocks']

In [49]: df.join(s)
Out[49]: 
   CustNum     CustomerName  ItemQty Item  ItemExt    Seatblocks
0    32363  McCartney, Paul        3  F04       60  2:218:10:4,6
1    31316     Lennon, John       25  F01      300  1:13:36:1,12
1    31316     Lennon, John       25  F01      300  1:13:37:1,13

Або надати кожному розділеному двокрапці рядок у власному стовпці:

In [50]: df.join(s.apply(lambda x: Series(x.split(':'))))
Out[50]: 
   CustNum     CustomerName  ItemQty Item  ItemExt  0    1   2     3
0    32363  McCartney, Paul        3  F04       60  2  218  10   4,6
1    31316     Lennon, John       25  F01      300  1   13  36  1,12
1    31316     Lennon, John       25  F01      300  1   13  37  1,13

Це трохи некрасиво, але, можливо, хтось задзвенить гарнішим рішенням.


7
@DanAllan надає індекс серії, коли ви подаєте заявку; вони стануть назвами стовпців
Джефф

4
Хоча це відповідає на питання, варто згадати, що (ймовірно) split () створює список для кожного ряду, який DataFrameдуже швидко збільшує розмір . У моєму випадку запуск коду на таблиці ~ 200М призвело до використання ~ 10G пам'яті (+ своп ...).
Девід Немескі

1
Хоча я не впевнений, що це через те split(), що просто reduce()проходження через колонку працює як шарм. Проблема тоді може полягати в stack()...
Девід Немескі

4
Я отримую помилку NameError: name 'Series' is not definedза це. звідки Seriesпоходить? РЕДАКТИРУЙТЕ: pandas.Seriespandas
ніколи не пам’ятайте

2
Так, @ user5359531. Я from pandas import Seriesдля зручності / стислості.
Дан Аллан

52

На відміну від Дена, я вважаю його відповідь досить елегантною ... але, на жаль, вона також дуже неефективна. Отже, оскільки в питанні було вказано "великий файл csv" , дозвольте запропонувати спробувати рішення оболонки Дена:

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print df['col'].apply(lambda x : pd.Series(x.split(' '))).head()"

... порівняно з цією альтернативою:

time python -c "import pandas as pd;
from scipy import array, concatenate;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(concatenate(df['col'].apply( lambda x : [x.split(' ')]))).head()"

... і це:

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(dict(zip(range(3), [df['col'].apply(lambda x : x.split(' ')[i]) for i in range(3)]))).head()"

Другий просто утримується від виділення 100 000 серій, і цього достатньо, щоб зробити це приблизно в 10 разів швидше. Але третє рішення, яке дещо іронічно витрачає багато дзвінків на str.split () (воно називається один раз на стовпець у рядку, тому втричі більше, ніж для інших двох рішень), приблизно в 40 разів швидше, ніж перше, оскільки він навіть уникає примірника 100 000 списків. І так, це, звичайно, трохи некрасиво ...

EDIT: ця відповідь підказує, як використовувати "to_list ()" та уникнути необхідності лямбда. Результат - щось подібне

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(df.col.str.split().tolist()).head()"

що навіть ефективніше, ніж третє рішення, і, звичайно, набагато елегантніше.

EDIT: ще простіше

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print pd.DataFrame(list(df.col.str.split())).head()"

працює також і майже так само ефективно.

EDIT: ще простіше ! І обробляє NaNs (але менш ефективні):

time python -c "import pandas as pd;
df = pd.DataFrame(['a b c']*100000, columns=['col']);
print df.col.str.split(expand=True).head()"

У мене виникають невеликі проблеми з кількістю пам'яті, яку споживає цей метод, і мені цікаво, чи можете ви дати мені невелику пораду. У мене є DataFrame, який містить близько 8000 рядків, у кожному з яких є рядок, що містить 9216 пробілів з 8-бітовими цілими числами. Це приблизно 75 Мб, але коли я застосую останнє рішення дослівно, Python з'їдає 2 ГБ моєї пам'яті. Чи можете ви вказати мене в бік джерела, який би сказав мені, чому це так, і що я можу зробити, щоб обійти його? Дякую.
замок-браво

1
У вас багато списків і дуже малих рядків, що є більш-менш гіршим випадком використання пам'яті в python (а проміжний крок ".split (). Tolist ()" створює чисті об'єкти python). Можливо, я б зробив на вашому місці, щоб скинути DataFrame у файл, а потім відкрити його як csv з read_csv (..., sep = ''). Але зупинитися на темі: перше рішення (разом з третім, яке, однак, має бути дуже повільним), може бути тим, що пропонує вам найменше використання пам'яті серед 4, оскільки у вас порівняно невелика кількість відносно довгих рядків.
Pietro Battiston

Ей, П'єтро, я спробував вашу пропозицію зберегти у файл і перезавантажити, він спрацював досить добре. У мене виникли проблеми, коли я намагався це зробити в об’єкті StringIO, і тут було розміщено гарне рішення моєї проблеми .
замок-браво

3
Ваша остання пропозиція tolist()ідеальна. У моєму випадку я хотів лише одну частину даних у списку і зміг безпосередньо додати один стовпець до мого існуючого df, використовуючи .ix:df['newCol'] = pd.DataFrame(df.col.str.split().tolist()).ix[:,2]
фантастичний

А-а-а, я спершу мав проблеми з тим, щоб це було на роботі - те, про obect of type 'float' has no len()що було збентеження, поки я не зрозумів, що деякі з моїх рядів були NaNв них, на відміну від str.
dwanderson

14
import pandas as pd
import numpy as np

df = pd.DataFrame({'ItemQty': {0: 3, 1: 25}, 
                   'Seatblocks': {0: '2:218:10:4,6', 1: '1:13:36:1,12 1:13:37:1,13'}, 
                   'ItemExt': {0: 60, 1: 300}, 
                   'CustomerName': {0: 'McCartney, Paul', 1: 'Lennon, John'}, 
                   'CustNum': {0: 32363, 1: 31316}, 
                   'Item': {0: 'F04', 1: 'F01'}}, 
                    columns=['CustNum','CustomerName','ItemQty','Item','Seatblocks','ItemExt'])

print (df)
   CustNum     CustomerName  ItemQty Item                 Seatblocks  ItemExt
0    32363  McCartney, Paul        3  F04               2:218:10:4,6       60
1    31316     Lennon, John       25  F01  1:13:36:1,12 1:13:37:1,13      300

Ще одне подібне рішення з ланцюжком - це використання reset_indexта rename:

print (df.drop('Seatblocks', axis=1)
             .join
             (
             df.Seatblocks
             .str
             .split(expand=True)
             .stack()
             .reset_index(drop=True, level=1)
             .rename('Seatblocks')           
             ))

   CustNum     CustomerName  ItemQty Item  ItemExt    Seatblocks
0    32363  McCartney, Paul        3  F04       60  2:218:10:4,6
1    31316     Lennon, John       25  F01      300  1:13:36:1,12
1    31316     Lennon, John       25  F01      300  1:13:37:1,13

Якщо в стовпці НЕ NaN значень, найшвидшим рішенням є використання listрозуміння з DataFrameконструктором:

df = pd.DataFrame(['a b c']*100000, columns=['col'])

In [141]: %timeit (pd.DataFrame(dict(zip(range(3), [df['col'].apply(lambda x : x.split(' ')[i]) for i in range(3)]))))
1 loop, best of 3: 211 ms per loop

In [142]: %timeit (pd.DataFrame(df.col.str.split().tolist()))
10 loops, best of 3: 87.8 ms per loop

In [143]: %timeit (pd.DataFrame(list(df.col.str.split())))
10 loops, best of 3: 86.1 ms per loop

In [144]: %timeit (df.col.str.split(expand=True))
10 loops, best of 3: 156 ms per loop

In [145]: %timeit (pd.DataFrame([ x.split() for x in df['col'].tolist()]))
10 loops, best of 3: 54.1 ms per loop

Але якщо стовпець містить NaNлише str.splitпараметр, expand=Trueякий повертає DataFrame( документація ), і він пояснює, чому це повільніше:

df = pd.DataFrame(['a b c']*10, columns=['col'])
df.loc[0] = np.nan
print (df.head())
     col
0    NaN
1  a b c
2  a b c
3  a b c
4  a b c

print (df.col.str.split(expand=True))
     0     1     2
0  NaN  None  None
1    a     b     c
2    a     b     c
3    a     b     c
4    a     b     c
5    a     b     c
6    a     b     c
7    a     b     c
8    a     b     c
9    a     b     c

Може бути, варто згадати , що вам обов'язково потрібно expand=Trueопцію працювати з pandas.DataFramesпри використанні .str.split(), наприклад.
holzkohlengrill

@holzkohlengrill - дякую за коментар, додаю його до відповіді.
jezrael

@jezrael, мені потрібно багато часу, щоб виконати цей код, це очікувано. Як саме це зробити швидше? ЯКЩО я ставлю його в циклі for, як: for x in df [Seablocks] [: 100], щоб зробити це лише на підмножині, а потім об'єднатись у ці підмножини, це буде працювати?
bernando_vialli

2

Інший підхід був би таким:

temp = df['Seatblocks'].str.split(' ')
data = data.reindex(data.index.repeat(temp.apply(len)))
data['new_Seatblocks'] = np.hstack(temp)

1

Можна також використовувати groupby () без необхідності приєднання та складання ().

Використовуйте наведені вище приклади даних:

import pandas as pd
import numpy as np


df = pd.DataFrame({'ItemQty': {0: 3, 1: 25}, 
                   'Seatblocks': {0: '2:218:10:4,6', 1: '1:13:36:1,12 1:13:37:1,13'}, 
                   'ItemExt': {0: 60, 1: 300}, 
                   'CustomerName': {0: 'McCartney, Paul', 1: 'Lennon, John'}, 
                   'CustNum': {0: 32363, 1: 31316}, 
                   'Item': {0: 'F04', 1: 'F01'}}, 
                    columns=['CustNum','CustomerName','ItemQty','Item','Seatblocks','ItemExt']) 
print(df)

   CustNum     CustomerName  ItemQty Item                 Seatblocks  ItemExt
0  32363    McCartney, Paul  3        F04  2:218:10:4,6               60     
1  31316    Lennon, John     25       F01  1:13:36:1,12 1:13:37:1,13  300  


#first define a function: given a Series of string, split each element into a new series
def split_series(ser,sep):
    return pd.Series(ser.str.cat(sep=sep).split(sep=sep)) 
#test the function, 
split_series(pd.Series(['a b','c']),sep=' ')
0    a
1    b
2    c
dtype: object

df2=(df.groupby(df.columns.drop('Seatblocks').tolist()) #group by all but one column
          ['Seatblocks'] #select the column to be split
          .apply(split_series,sep=' ') # split 'Seatblocks' in each group
         .reset_index(drop=True,level=-1).reset_index()) #remove extra index created

print(df2)
   CustNum     CustomerName  ItemQty Item  ItemExt    Seatblocks
0    31316     Lennon, John       25  F01      300  1:13:36:1,12
1    31316     Lennon, John       25  F01      300  1:13:37:1,13
2    32363  McCartney, Paul        3  F04       60  2:218:10:4,6

Заздалегідь спасибі. Як я міг би використати вищезазначений код, розділивши два стовпчики підрядним чином. Наприклад: 0 31316 Lennon, John 25 F01 300 1: 13: 36: 1,12 1: 13: 37: 1,13 A, B .. Результат повинен бути: 0 31316 Lennon, John 25 F01 300 1:13:36:1,12 Aта наступний рядок 0 31316 Lennon, John 25 F01 300 1:13:37:1,13 B
Krithi.S

@ Krithi.S, я намагаюся зрозуміти питання. Ви маєте на увазі, що два стовпці повинні мати однакову кількість членів після розбиття? Які очікувані результати для 0 31316 Lennon, John 25 F01 300 1: 13: 36: 1,12 1: 13: 37: 1,13 A, B, C?
Ben2018

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