如何在熊猫中做到这一点:
我在单个文本列上有一个函数 extract_text_features
,返回多个输出列。具体来说,该函数返回 6 个值。
该函数有效,但似乎没有任何正确的返回类型(pandas DataFrame/numpy array/Python 列表)以便输出可以正确分配 df.ix[: ,10:16] = df.textcol.map(extract_text_features)
所以我认为我需要根据 this 退回到使用 df.iterrows()
进行迭代?
更新:使用 df.iterrows()
进行迭代至少慢 20 倍,因此我放弃并将函数拆分为六个不同的 .map(lambda ...)
调用。
更新 2:在可用性 df.apply
得到改进或 df.assign()
为 added in v0.16 之前,这个问题在 v0.11.0 附近被问到。因此,许多问题和答案都不太相关。
df.ix[: ,10:16]
。我认为您必须将您的特征merge
放入数据集中。
apply
的更高效解决方案 check this one below 的人
我通常使用 zip
执行此操作:
>>> df = pd.DataFrame([[i] for i in range(10)], columns=['num'])
>>> df
num
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
>>> def powers(x):
>>> return x, x**2, x**3, x**4, x**5, x**6
>>> df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \
>>> zip(*df['num'].map(powers))
>>> df
num p1 p2 p3 p4 p5 p6
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
2 2 2 4 8 16 32 64
3 3 3 9 27 81 243 729
4 4 4 16 64 256 1024 4096
5 5 5 25 125 625 3125 15625
6 6 6 36 216 1296 7776 46656
7 7 7 49 343 2401 16807 117649
8 8 8 64 512 4096 32768 262144
9 9 9 81 729 6561 59049 531441
在 2020 年,我将 apply()
与参数 result_type='expand'
一起使用
applied_df = df.apply(lambda row: fn(row.text), axis='columns', result_type='expand')
df = pd.concat([df, applied_df], axis='columns')
pd.Series
,这对于性能问题总是很好
df.apply
的函数返回 dict
,则列将根据键命名。
appiled_df =
替换为 df[["col1", "col2", ...]] =
,可以将其简化为一行。这也将给出命名列。
result_type='expand'
。例如 df[new_cols] = df.apply(extract_text_features, axis=1, result_type='expand')
就可以了。尽管您需要知道新列的名称。
根据 user1827356 的回答,您可以使用 df.merge
一次性完成作业:
df.merge(df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1})),
left_index=True, right_index=True)
textcol feature1 feature2
0 0.772692 1.772692 -0.227308
1 0.857210 1.857210 -0.142790
2 0.065639 1.065639 -0.934361
3 0.819160 1.819160 -0.180840
4 0.088212 1.088212 -0.911788
编辑:请注意巨大的内存消耗和低速:https://ys-l.github.io/posts/2015/08/28/how-not-to-use-pandas-apply/!
这是我过去所做的
df = pd.DataFrame({'textcol' : np.random.rand(5)})
df
textcol
0 0.626524
1 0.119967
2 0.803650
3 0.100880
4 0.017859
df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1}))
feature1 feature2
0 1.626524 -0.373476
1 1.119967 -0.880033
2 1.803650 -0.196350
3 1.100880 -0.899120
4 1.017859 -0.982141
编辑完整性
pd.concat([df, df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1}))], axis=1)
textcol feature1 feature2
0 0.626524 1.626524 -0.373476
1 0.119967 1.119967 -0.880033
2 0.803650 1.803650 -0.196350
3 0.100880 1.100880 -0.899120
4 0.017859 1.017859 -0.982141
df[['col1', 'col2']] = df['col3'].apply(lambda x: pd.Series('val1', 'val2'))
之外指定列,则不需要使用字典或合并
对于 95% 的用例,这是实现此目的的正确且最简单的方法:
>>> df = pd.DataFrame(zip(*[range(10)]), columns=['num'])
>>> df
num
0 0
1 1
2 2
3 3
4 4
5 5
>>> def example(x):
... x['p1'] = x['num']**2
... x['p2'] = x['num']**3
... x['p3'] = x['num']**4
... return x
>>> df = df.apply(example, axis=1)
>>> df
num p1 p2 p3
0 0 0 0 0
1 1 1 1 1
2 2 4 8 16
3 3 9 27 81
4 4 16 64 256
pd.Series({k:v})
并像 Ewan 的回答那样序列化列分配不是更好吗?
只需使用 result_type="expand"
df = pd.DataFrame(np.random.randint(0,10,(10,2)), columns=["random", "a"])
df[["sq_a","cube_a"]] = df.apply(lambda x: [x.a**2, x.a**3], axis=1, result_type="expand")
对我来说,这很有效:
输入df
df = pd.DataFrame({'col x': [1,2,3]})
col x
0 1
1 2
2 3
功能
def f(x):
return pd.Series([x*x, x*x*x])
创建 2 个新列:
df[['square x', 'cube x']] = df['col x'].apply(f)
输出:
col x square x cube x
0 1 1 1
1 2 4 8
2 3 9 27
总结:如果您只想创建几列,请使用 df[['new_col1','new_col2']] = df[['data1','data2']].apply( function_of_your_choosing(x), axis=1)
对于此解决方案,您创建的新列数必须等于您用作 .apply() 函数输入的列数。如果您想做其他事情,请查看其他答案。
详细信息假设您有两列数据框。第一列是一个人 10 岁时的身高;第二个是20岁时的身高。
假设您需要计算每个人身高的平均值和每个人的身高总和。这是每行两个值。
您可以通过以下即将应用的功能来做到这一点:
def mean_and_sum(x):
"""
Calculates the mean and sum of two heights.
Parameters:
:x -- the values in the row this function is applied to. Could also work on a list or a tuple.
"""
sum=x[0]+x[1]
mean=sum/2
return [mean,sum]
你可以像这样使用这个函数:
df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)
(要清楚:此应用函数从子集数据帧中的每一行中获取值并返回一个列表。)
但是,如果您这样做:
df['Mean_&_Sum'] = df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)
您将创建 1 个包含 [mean,sum] 列表的新列,您可能希望避免这样做,因为这需要另一个 Lambda/Apply。
相反,您希望将每个值分解为自己的列。为此,您可以一次创建两列:
df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']]
.apply(mean_and_sum(x),axis=1)
df["mean"], df["sum"] = df[['height_at_age_10','height_at_age_20']] .apply(mean_and_sum(x),axis=1)
return pd.Series([mean,sum])
我已经看过几种方法,这里显示的方法(返回熊猫系列)似乎不是最有效的。
如果我们从一个较大的随机数据数据框开始:
# Setup a dataframe of random numbers and create a
df = pd.DataFrame(np.random.randn(10000,3),columns=list('ABC'))
df['D'] = df.apply(lambda r: ':'.join(map(str, (r.A, r.B, r.C))), axis=1)
columns = 'new_a', 'new_b', 'new_c'
此处显示的示例:
# Create the dataframe by returning a series
def method_b(v):
return pd.Series({k: v for k, v in zip(columns, v.split(':'))})
%timeit -n10 -r3 df.D.apply(method_b)
10 个循环,3 个循环中的最佳:每个循环 2.77 秒
另一种方法:
# Create a dataframe from a series of tuples
def method_a(v):
return v.split(':')
%timeit -n10 -r3 pd.DataFrame(df.D.apply(method_a).tolist(), columns=columns)
10 个循环,3 个循环中的最佳:每个循环 8.85 毫秒
据我估计,采用一系列元组然后将其转换为 DataFrame 会更有效。如果我的工作出现错误,我很想听听人们的想法。
对于大量数据,公认的解决方案将非常缓慢。投票数最多的解决方案有点难以阅读,而且数字数据也很慢。如果每个新列都可以独立于其他列进行计算,我将直接分配它们而不使用 apply
。
带有假字符数据的示例
在 DataFrame 中创建 100,000 个字符串
df = pd.DataFrame(np.random.choice(['he jumped', 'she ran', 'they hiked'],
size=100000, replace=True),
columns=['words'])
df.head()
words
0 she ran
1 she ran
2 they hiked
3 they hiked
4 they hiked
假设我们想提取一些文本特征,就像在原始问题中所做的那样。例如,让我们提取第一个字符,计算字母“e”的出现次数并将短语大写。
df['first'] = df['words'].str[0]
df['count_e'] = df['words'].str.count('e')
df['cap'] = df['words'].str.capitalize()
df.head()
words first count_e cap
0 she ran s 1 She ran
1 she ran s 1 She ran
2 they hiked t 2 They hiked
3 they hiked t 2 They hiked
4 they hiked t 2 They hiked
计时
%%timeit
df['first'] = df['words'].str[0]
df['count_e'] = df['words'].str.count('e')
df['cap'] = df['words'].str.capitalize()
127 ms ± 585 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
def extract_text_features(x):
return x[0], x.count('e'), x.capitalize()
%timeit df['first'], df['count_e'], df['cap'] = zip(*df['words'].apply(extract_text_features))
101 ms ± 2.96 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
令人惊讶的是,您可以通过循环遍历每个值来获得更好的性能
%%timeit
a,b,c = [], [], []
for s in df['words']:
a.append(s[0]), b.append(s.count('e')), c.append(s.capitalize())
df['first'] = a
df['count_e'] = b
df['cap'] = c
79.1 ms ± 294 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
另一个伪造数字数据的例子
创建 100 万个随机数并从上面测试 powers
函数。
df = pd.DataFrame(np.random.rand(1000000), columns=['num'])
def powers(x):
return x, x**2, x**3, x**4, x**5, x**6
%%timeit
df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \
zip(*df['num'].map(powers))
1.35 s ± 83.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
分配每一列的速度提高了 25 倍,并且非常易读:
%%timeit
df['p1'] = df['num'] ** 1
df['p2'] = df['num'] ** 2
df['p3'] = df['num'] ** 3
df['p4'] = df['num'] ** 4
df['p5'] = df['num'] ** 5
df['p6'] = df['num'] ** 6
51.6 ms ± 1.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
我对 more details here 做出了类似的回应,说明为什么 apply
通常不可行。
在另外两个类似的问题中发布了相同的答案。我更喜欢这样做的方式是将函数的返回值包装成一个系列:
def f(x):
return pd.Series([x**2, x**3])
然后使用 apply 如下创建单独的列:
df[['x**2','x**3']] = df.apply(lambda row: f(row['x']), axis=1)
def extract_text_features(feature):
...
...
return pd.Series((feature1, feature2))
df[['NewFeature1', 'NewFeature1']] = df[['feature']].apply(extract_text_features, axis=1)
在这里,具有单个特征的数据框被转换为两个新特征。也试试这个。
您可以返回整行而不是值:
df = df.apply(extract_text_features,axis = 1)
函数返回行的位置
def extract_text_features(row):
row['new_col1'] = value1
row['new_col2'] = value2
return row
extract_text_features
应用于 df 的每一列,只应用于文本列 df.textcol
我有一个更复杂的情况,数据集有一个嵌套结构:
import json
data = '{"TextID":{"0":"0038f0569e","1":"003eb6998d","2":"006da49ea0"},"Summary":{"0":{"Crisis_Level":["c"],"Type":["d"],"Special_Date":["a"]},"1":{"Crisis_Level":["d"],"Type":["a","d"],"Special_Date":["a"]},"2":{"Crisis_Level":["d"],"Type":["a"],"Special_Date":["a"]}}}'
df = pd.DataFrame.from_dict(json.loads(data))
print(df)
输出:
TextID Summary
0 0038f0569e {'Crisis_Level': ['c'], 'Type': ['d'], 'Specia...
1 003eb6998d {'Crisis_Level': ['d'], 'Type': ['a', 'd'], 'S...
2 006da49ea0 {'Crisis_Level': ['d'], 'Type': ['a'], 'Specia...
Summary
列包含 dict 对象,因此我将 apply
与 from_dict
和 stack
一起使用来提取 dict 的每一行:
df2 = df.apply(
lambda x: pd.DataFrame.from_dict(x[1], orient='index').stack(), axis=1)
print(df2)
输出:
Crisis_Level Special_Date Type
0 0 0 1
0 c a d NaN
1 d a a d
2 d a a NaN
看起来不错,但缺少 TextID
列。为了恢复 TextID
列,我尝试了三种方法:
修改 apply 以返回多列: df_tmp = df.copy() df_tmp[['TextID', 'Summary']] = df.apply( lambda x: pd.Series([x[0], pd.DataFrame.from_dict( x[1], orient='index').stack()]), axis=1) print(df_tmp) 输出:TextID 摘要 0 0038f0569e Crisis_Level 0 c Type 0 d Spec... 1 003eb6998d Crisis_Level 0 d Type 0 a ... 2 006da49ea0 Crisis_Level 0 d Type 0 a Spec...但这不是我想要的,摘要结构是扁平的。使用 pd.concat:df_tmp2 = pd.concat([df['TextID'], df2], axis=1) print(df_tmp2) 输出:TextID (Crisis_Level, 0) (Special_Date, 0) (Type, 0) (Type , 1) 0 0038f0569e cad NaN 1 003eb6998d daad 2 006da49ea0 daa NaN 看起来不错,MultiIndex 列结构被保留为元组。但检查列类型:df_tmp2.columns 输出:Index(['TextID', ('Crisis_Level', 0), ('Special_Date', 0), ('Type', 0), ('Type', 1)], dtype='object') 就像一个普通的 Index 类,而不是 MultiIndex 类。使用 set_index:将所有要保留的列转换为行索引,经过一些复杂的应用函数,然后 reset_index 以获取列: df_tmp3 = df.set_index('TextID') df_tmp3 = df_tmp3.apply( lambda x: pd.DataFrame. from_dict(x[0], orient='index').stack(), axis=1) df_tmp3 = df_tmp3.reset_index(level=0) print(df_tmp3) 输出:TextID Crisis_Level Special_Date Type 0 0 0 1 0 0038f0569e cad NaN 1 003eb6998d daad 2 006da49ea0 daa NaN 检查列类型 df_tmp3.columns 输出:MultiIndex(levels=[['Crisis_Level', 'Special_Date', 'Type', 'TextID'], [0, 1, '']],代码=[[3, 0, 1, 2, 2], [2, 0, 0, 0, 1]])
因此,如果您的 apply
函数将返回 MultiIndex
列,并且您想保留它,您可能想尝试第三种方法。
temp = list(zip(*df['num'].map(powers))); for i, c in enumerate(columns): df[c] = temp[c]
for i, c in enumerate(columns): df[c] = temp[i]
。多亏了这一点,我才真正明白了enumerate
的目的:Dzip(*df['col'].map(function))
可能是要走的路。