在DataFrame索引上应用函数

131

如何在Pandas DataFrame索引上应用函数的最佳方式? 目前我正在使用这种冗长的方法:

pd.DataFrame({"Month": df.reset_index().Date.apply(foo)})

Date是索引名称,foo是我正在应用的函数名称。


7
df.index.map(foo) 能正常工作吗? - HYRY
1
它“可以工作”,但返回的是一个numpy数组而不是Pandas Series。 - Alex Rothberg
1
你的最终目标是什么?你可以将数组传递给DataFrame构造函数。或者像这样做:pd.Series(df.index).apply(foo) - Roman Pekar
这完全取决于函数是什么... - Andy Hayden
1
如果您只想修改现有DataFrame的索引,可以使用以下代码:df.index = df.index.map(foo) - Ben
4个回答

142

正如评论中HYRY所建议的那样,Series.map是这里使用的方式。只需要将索引设置为结果系列。

简单示例:

df = pd.DataFrame({'d': [1, 2, 3]}, index=['FOO', 'BAR', 'BAZ'])
df
        d
FOO     1
BAR     2
BAZ     3

df.index = df.index.map(str.lower)
df
        d
foo     1
bar     2
baz     3

索引 != 系列

正如@OP所指出的那样,df.index.map(str.lower)调用返回一个numpy数组。这是因为数据帧的索引基于numpy数组,而不是Series。

唯一将索引转换为Series的方法是从中创建一个Series。

pd.Series(df.index.map(str.lower))

注意事项

Index类现在是StringAccessorMixin的子类,这意味着你可以按照以下方式执行上述操作:

df.index.str.lower()

这仍然会生成一个索引对象,而不是一个数据系列。


1
使用多索引,如果您想在函数中同时使用两个项目,例如 x[0]x[1],则可以使用切片。 - Elliott
3
df.index.map(str.lower) 的简化写法。 - Zero
1
@JohnGalt 感谢您指出这一点。 这不仅更简短,而且更快,因为str.lower是一个已编译的Cython函数,而我编写的lambda函数不是。 - firelynx
如果我想要应用的函数需要一些参数,那么该如何修改呢?例如,我有一个浮点索引,我想将每个值舍入到小数点后2位。 - Luca Clissa

27
你可以使用其 to_series() 方法将索引转换为系列,然后根据需要使用 applymap
ret = df.index.map(foo)                # Returns pd.Index
ret = df.index.to_series().map(foo)    # Returns pd.Series
ret = df.index.to_series().apply(foo)  # Returns pd.Series

以上所有内容都可以直接分配给df的新列或现有列:

df["column"] = ret

仅供完整性:pd.Index.mappd.Series.mappd.Series.apply都是逐元素操作。我经常使用map来应用由dictspd.Series表示的查找。 apply更通用,因为您可以传递任何函数以及其他argskwargs。有关applymap之间的区别,请参见此SO线程。我不知道为什么省略了pd.Index.apply


感谢您的详细回复,第三个选项帮我解决了一大难题。 - dimButTries
2
我发现第三个例子很有用,因为返回的Series中保留了索引。 - kristianp

13

假设您想通过将函数“foo”应用于索引来在当前数据帧中创建一个列。您可以编写...

df['Month'] = df.index.map(foo)

如果您只想生成该系列,可以执行以下操作...

pd.Series({x: foo(x) for x in foo.index})

2
在pandas/numpy生态系统中使用for循环是不被推荐的。它非常浪费内存,并且在处理大型数据集时容易崩溃。 - firelynx

6
很多答案都会返回索引作为一个数组,这样就会丢失关于索引名称的信息等(虽然你可以使用pd.Series(index.map(myfunc), name=index.name))。这也不能用于多重索引。
我处理这个问题的方式是使用“rename”函数:
mix = pd.MultiIndex.from_tuples([[1, 'hi'], [2, 'there'], [3, 'dude']], names=['num', 'name'])
data = np.random.randn(3)
df = pd.Series(data, index=mix)
print(df)
num  name 
1    hi       1.249914
2    there   -0.414358
3    dude     0.987852
dtype: float64

# Define a few dictionaries to denote the mapping
rename_dict = {i: i*100 for i in df.index.get_level_values('num')}
rename_dict.update({i: i+'_yeah!' for i in df.index.get_level_values('name')})
df = df.rename(index=rename_dict)
print(df)
num  name       
100  hi_yeah!       1.249914
200  there_yeah!   -0.414358
300  dude_yeah!     0.987852
dtype: float64

唯一需要注意的是,你的索引在不同的多重索引级别之间需要具有唯一标签,但也许比我更聪明的人知道如何规避这个问题。对于我的目的,这在95%的情况下都有效。


rename 函数有一个 level 参数(现在是这样吗?)。因此,这样可以消除歧义:df.rename(index=rename_dict0, level=0).rename(index=rename_dict1, level=1) - Antony Hatchkins

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接