Pandas: функция применения нескольких индексов между столбцом и индексом

У меня есть мультииндексный фрейм данных, который выглядит так:

In[13]: df
Out[13]:
              Last Trade
Date       Ticker           
1983-03-30 CLM83  1983-05-18
           CLN83  1983-06-17
           CLQ83  1983-07-18
           CLU83  1983-08-19
           CLV83  1983-09-16
           CLX83  1983-10-18
           CLZ83  1983-11-18
1983-04-04 CLM83  1983-05-18
           CLN83  1983-06-17
           CLQ83  1983-07-18
           CLU83  1983-08-19
           CLV83  1983-09-16
           CLX83  1983-10-18
           CLZ83  1983-11-18

С двумя уровнями для индексов (а именно «Дата» и «Тикер»). Я хотел бы применить функцию к столбцу «Последняя сделка», которая позволит мне узнать, сколько месяцев отделяет эту дату «Последняя сделка» от индекса «Дата». Я нашел функцию, которая выполняет расчет:

from calendar import monthrange

def monthdelta(d1, d2):
    delta = 0
    while True:
        mdays = monthrange(d1.year, d1.month)[1]
        d1 += datetime.timedelta(days=mdays)
        if d1 <= d2:
            delta += 1
        else:
            break
    return delta

Я попытался применить следующую функцию h, но она возвращает мне AttributeError: объект «Timestamp» не имеет атрибута «index»:

In[14]: h = lambda x: monthdelta(x.index.get_level_values(0),x)

In[15]: df['Last Trade'] = df['Last Trade'].apply(h)

Как я могу применить функцию, которая будет использовать как столбец, так и значение индекса?

Спасибо за ваши советы,


person Mth Clv    schedule 09.08.2016    source источник


Ответы (2)


Попробуйте это вместо вашей функции:

Опция 1

Вы получаете целое число

def monthdelta(row):
    trade = row['Last Trade'].year*12 + row['Last Trade'].month
    date = row['Date'].year*12 + row['Date'].month
    return trade - date

df.reset_index().apply(monthdelta, axis=1)

Вдохновленный PiRsquared:

df = df.reset_index()
(df['Last Trade'].dt.year*12 + df['Last Trade'].dt.month) -\
(df['Date'].dt.year*12 + df['Date'].dt.month)

Вариант 2

Вы получаете numpy.timedelta64

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

def monthdelta(row):
    return row['Last Trade'] - row['Date']

df.reset_index().apply(monthdelta, axis=1)

Вдохновленный PiRsquared:

df = df.reset_index()
df['Last Trade'] - df['Date']

Вариант 2, конечно, будет быстрее, потому что требует меньше вычислений. Выберите то, что вам нравится!


Чтобы вернуть индекс: df.index = df[['Date', 'Ticker']]

person Kartik    schedule 09.08.2016
comment
Ваше первое решение дает мне AttributeError: ("'Timestamp' object has no attribute 'dt'", u'occurred at index 0'), я подозреваю, потому что apply подает строку за строкой и, таким образом, row['Last Trade'] и row['Date'] не являются скалярными метками времени и больше не являются сериями. Поэтому не нужны dt средства доступа. - person piRSquared; 10.08.2016
comment
Но dtypes следует сохранить в течение .apply. Что означает df.reset_index().dtypes? - person Kartik; 10.08.2016
comment
Посмотрите на тайминги, которые я добавил в свой пост. Вы увидите код, который я использовал для ваших решений. Я удалил .dt из функции. У меня иначе не работает. - person piRSquared; 10.08.2016
comment
В groupby apply передаются фрагменты кадра данных. Типичный apply, который вы использовали, проходит серию для каждой строки. При доступе к датам внутри серии вам больше не нужен (он не будет работать) аксессор dt. - person piRSquared; 10.08.2016
comment
О... Этого я никак не ожидал. Я предполагаю, что способ хранения временных меток в Series отличается от способа их хранения в столбцах DataFrame. Спасибо за эту информацию... :) - person Kartik; 10.08.2016
comment
reset_index быстрее всего работает с большими наборами данных. - person piRSquared; 10.08.2016
comment
Картик спасибо большое! Это было действительно полезно. Хотя это решает основную проблему здесь, я прокомментировал в редактировании piRSquared тот факт, что вычитание dt.month не является идеальной заменой моей функции monthdelta(), поскольку она не учитывает годы. - person Mth Clv; 10.08.2016
comment
Какое изменение вы внесли? - person Mth Clv; 10.08.2016
comment
Работал над этим. Я не знал, что numpy не позволяет конвертировать из 'timedelta64[D]' в 'timedelta64[M]'. Это поставило меня в тупик на несколько. Я включил ссылку на причину в свой ответ. - person Kartik; 10.08.2016

Используйте df.index.to_series().str.get(0), чтобы перейти на первый уровень индекса.

(df['Last Trade'].dt.month - df.index.to_series().str.get(0).dt.month) + \
(df['Last Trade'].dt.year - df.index.to_series().str.get(0).dt.year) * 12

Date        Ticker
1983-03-30  CLM83     2
            CLN83     3
            CLQ83     4
            CLU83     5
            CLV83     6
            CLX83     7
            CLZ83     8
1983-04-04  CLM83     1
            CLN83     2
            CLQ83     3
            CLU83     4
            CLV83     5
            CLX83     6
            CLZ83     7
dtype: int64

Сроки

Дано df

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

pd.concat([df for _ in range(10000)])

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

person piRSquared    schedule 09.08.2016
comment
Разве .reset_index не будет быстрее, чем преобразование в Series, а затем использование класса string? - person Kartik; 10.08.2016
comment
@Kartik Я проверяю. Но я хотел чистый способ сохранить индекс. - person piRSquared; 10.08.2016
comment
Имеет смысл. .reset_index и извлечение месяца требует меньше вычислительных шагов, чем преобразование в Series, затем использование класса str для получения первого уровня индекса, а затем извлечение месяца. .apply - это просто цикл, поэтому он будет масштабироваться с увеличением размера, в отличие от прямого вычитания... Потрясающее сравнение времени! - person Kartik; 10.08.2016
comment
Большое спасибо вам обоим! На этот раз анализ времени оказался для меня очень важным, поскольку я имею дело с очень большой базой данных. Я не знал о аксессорах dt или даже о reset_index(), мне пришлось их искать. Это действительно мощные инструменты. - person Mth Clv; 10.08.2016
comment
У меня осталась только одна проблема, оба ваших подхода не учитывают годы, давайте возьмем пример: разница между 15 января и 14 декабря будет -11, а я бы хотел, чтобы она была 1 - person Mth Clv; 10.08.2016
comment
Есть ли быстрый и эффективный способ учесть это? - person Mth Clv; 10.08.2016
comment
@MthClv Это должно сработать. - person piRSquared; 10.08.2016