Индекс доступа в pandas.Series.apply

Допустим, у меня есть серия MultiIndex s:

>>> s
     values
a b
1 2  0.1 
3 6  0.3
4 4  0.7

и я хочу применить функцию, которая использует индекс строки:

def f(x):
   # conditions or computations using the indexes
   if x.index[0] and ...: 
   other = sum(x.index) + ...
   return something

Как я могу сделать s.apply(f) для такой функции? Каков рекомендуемый способ проведения такого рода операций? Я ожидаю получить новую серию со значениями, полученными в результате этой функции, применяемой к каждой строке и к тому же MultiIndex.

Ответ 1

Я не верю, что apply имеет доступ к индексу; он рассматривает каждую строку как numpy-объект, а не серию, как вы можете видеть:

In [27]: s.apply(lambda x: type(x))
Out[27]: 
a  b
1  2    <type 'numpy.float64'>
3  6    <type 'numpy.float64'>
4  4    <type 'numpy.float64'>

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

Series(s.reset_index().apply(f, axis=1).values, index=s.index)

Другие подходы могут использовать s.get_level_values, который, по моему мнению, часто становится немного уродливым, или s.iterrows(), который, вероятно, будет медленнее - возможно, в зависимости от того, что именно делает f.

Ответ 2

Сделайте это фреймом, верните скаляры, если хотите (поэтому результат представляет собой серию)

Настройка

In [11]: s = Series([1,2,3],dtype='float64',index=['a','b','c'])

In [12]: s
Out[12]: 
a    1
b    2
c    3
dtype: float64

Функция печати

In [13]: def f(x):
    print type(x), x
    return x
   ....: 

In [14]: pd.DataFrame(s).apply(f)
<class 'pandas.core.series.Series'> a    1
b    2
c    3
Name: 0, dtype: float64
<class 'pandas.core.series.Series'> a    1
b    2
c    3
Name: 0, dtype: float64
Out[14]: 
   0
a  1
b  2
c  3

Так как вы можете вернуть что-либо здесь, просто верните скаляры (обратитесь к индексу с помощью атрибута name)

In [15]: pd.DataFrame(s).apply(lambda x: 5 if x.name == 'a' else x[0] ,1)
Out[15]: 
a    5
b    2
c    3
dtype: float64

Ответ 3

Преобразуйте в DataFrame и примените вдоль строки. Вы можете получить доступ к индексу как x.name. x также является Series теперь с 1 значением

s.to_frame(0).apply(f, axis=1)[0]

Ответ 4

Скорее вы можете использовать where, а не apply здесь:

In [11]: s = pd.Series([1., 2., 3.], index=['a' ,'b', 'c'])

In [12]: s.where(s.index != 'a', 5)
Out[12]: 
a    5
b    2
c    3
dtype: float64

Также вы можете использовать логику/функции типа numpy для любой из частей:

In [13]: (2 * s + 1).where((s.index == 'b') | (s.index == 'c'), -s)
Out[13]: 
a   -1
b    5
c    7
dtype: float64

In [14]: (2 * s + 1).where(s.index != 'a', -s)
Out[14]: 
a   -1
b    5
c    7
dtype: float64

Я рекомендую тестировать скорость (поскольку эффективность от применения будет зависеть от функции). Хотя, я считаю, что apply более читабельны...

Ответ 5

Вы можете получить доступ ко всей строке в качестве аргумента внутри fucntion, если вы используете DataFrame.apply() вместо Series.apply().

def f1(row):
    if row['I'] < 0.5:
        return 0
    else:
        return 1

def f2(row):
    if row['N1']==1:
        return 0
    else:
        return 1

import pandas as pd
import numpy as np
df4 = pd.DataFrame(np.random.rand(6,1), columns=list('I'))
df4['N1']=df4.apply(f1, axis=1)
df4['N2']=df4.apply(f2, axis=1)