Получить последний столбец после операции .str.split() в столбце в pandas DataFrame

У меня есть столбец в pandas DataFrame, который я хотел бы разделить на одно пространство. Расщепление достаточно просто с DataFrame.str.split(' '), но я не могу создать новый столбец из последней записи. Когда я .str.split() в столбце, я получаю список массивов, и я не знаю, как это сделать, чтобы получить новый столбец для моего DataFrame.

Вот пример. Каждая запись в столбце содержит "стоимость данных символа", и я хотел бы отделить цену (и в конечном итоге удалить "p"... или "c" в половине случаев).

import pandas as pd
temp = pd.DataFrame({'ticker' : ['spx 5/25/2001 p500', 'spx 5/25/2001 p600', 'spx 5/25/2001 p700']})
temp2 = temp.ticker.str.split(' ')

что дает

0    ['spx', '5/25/2001', 'p500']
1    ['spx', '5/25/2001', 'p600']
2    ['spx', '5/25/2001', 'p700']

Но temp2[0] просто дает один массив записей в списке и temp2[:][-1] терпит неудачу. Как преобразовать последнюю запись в каждый массив в новый столбец? Спасибо!

Ответ 1

Вы можете использовать метод tolist в качестве посредника:

In [99]: import pandas as pd

In [100]: d1 = pd.DataFrame({'ticker' : ['spx 5/25/2001 p500', 'spx 5/25/2001 p600', 'spx 5/25/2001 p700']})

In [101]: d1.ticker.str.split().tolist()
Out[101]: 
[['spx', '5/25/2001', 'p500'],
 ['spx', '5/25/2001', 'p600'],
 ['spx', '5/25/2001', 'p700']]

Из которого вы можете создать новый DataFrame:

In [102]: d2 = pd.DataFrame(d1.ticker.str.split().tolist(), 
   .....:                   columns="symbol date price".split())

In [103]: d2
Out[103]: 
  symbol       date price
0    spx  5/25/2001  p500
1    spx  5/25/2001  p600
2    spx  5/25/2001  p700

Для хорошей меры вы можете исправить цену:

In [104]: d2["price"] = d2["price"].str.replace("p","").astype(float)

In [105]: d2
Out[105]: 
  symbol       date  price
0    spx  5/25/2001    500
1    spx  5/25/2001    600
2    spx  5/25/2001    700

PS: но если вам действительно нужен последний столбец, apply будет достаточно:

In [113]: temp2.apply(lambda x: x[2])
Out[113]: 
0    p500
1    p600
2    p700
Name: ticker

Ответ 2

Сделайте это:

In [43]: temp2.str[-1]
Out[43]: 
0    p500
1    p600
2    p700
Name: ticker

Ответ 4

Использование Pandas 0.20.3:

In [10]: import pandas as pd
    ...: temp = pd.DataFrame({'ticker' : ['spx 5/25/2001 p500', 'spx 5/25/2001 p600', 'spx 5/25/2001 p700']})
    ...:

In [11]: temp2 = temp.ticker.str.split(' ', expand=True)  # the expand=True return a DataFrame

In [12]: temp2
Out[12]:
     0          1     2
0  spx  5/25/2001  p500
1  spx  5/25/2001  p600
2  spx  5/25/2001  p700

In [13]: temp3 = temp.join(temp2[2])

In [14]: temp3
Out[14]:
               ticker     2
0  spx 5/25/2001 p500  p500
1  spx 5/25/2001 p600  p600
2  spx 5/25/2001 p700  p700

Ответ 5

Series.str.rpartition

Это довольно эффективно.

temp.ticker.str.rpartition(expand=False).str[-1]

0    p500
1    p600
2    p700
Name: ticker, dtype: object

Если вы передадите параметр expand=True (который является значением по умолчанию), результатом будет DataFrame, где каждый столбец принадлежит своему собственному разделению:

temp.ticker.str.rpartition()
# temp.ticker.str.rpartition(expand=True)

               0  1     2
0  spx 5/25/2001     p500
1  spx 5/25/2001     p600
2  spx 5/25/2001     p700

Отсюда легко получить последний столбец.

temp.ticker.str.rpartition().iloc[:,-1]

0    p500
1    p600
2    p700
Name: 2, dtype: object

Series.str.rsplit(n=1)

Еще одно предложение - str.rsplit. Так как мы разделяем с конца, мы можем попросить split разделение только один раз (потому что перед окончательным разделением нам ничего не нужно).

temp.ticker.str.rsplit(n=1).str[-1]

0    p500
1    p600
2    p700
Name: ticker, dtype: object

Или же,

temp.ticker.str.rsplit(n=1, expand=True).iloc[:,-1]

0    p500
1    p600
2    p700
Name: 1, dtype: object

Это оказывается более эффективным, чем str.split.


Понимание списка

Для производительности вы можете использовать списки здесь:

[s.rpartition(' ')[-1] for s in temp.ticker]
# ['p500', 'p600', 'p700']

pd.Series([s.rpartition(' ')[-1] for s in temp.ticker])

0    p500
1    p600
2    p700
dtype: object

Вы также можете использовать str.rsplit в списке комп.

pd.Series([s.rsplit(None, 1)[-1] for s in temp.ticker])

0    p500
1    p600
2    p700
dtype: object

Я рекомендую списочные понимания, потому что они имеют меньшие издержки, чем строковые функции панд (которые также выполняют итерации по данным - строковые операции не могут быть действительно "векторизованы" легко). Для получения дополнительной информации вы можете взглянуть на циклы для панд - Когда мне все равно? ,


Спектакль

df_ = temp.copy()
df = pd.concat([df_] * 10000, ignore_index=True)

%timeit temp.ticker.str.rpartition(expand=False).str[-1]
%timeit temp.ticker.str.rpartition().iloc[:,-1]
%timeit temp.ticker.str.rsplit(n=1).str[-1]
%timeit pd.Series([s.rpartition(' ')[-1] for s in temp.ticker])
%timeit pd.Series([s.rsplit(None, 1)[-1] for s in temp.ticker])

801 µs ± 30.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
1.5 ms ± 18.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
780 µs ± 23.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
327 µs ± 5.59 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
318 µs ± 13 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Методы обработки списков работают быстрее, но большая часть замедления происходит из-за накладных расходов при преобразовании в Series. Операция с необработанным списком выполняется намного быстрее:

%timeit [s.rpartition(' ')[-1] for s in temp.ticker]
50.5 µs ± 1.29 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)