我有什么:
我想做什么:
我已经有以下代码,它可以正常工作。但是,分析表明此代码是我的代码中的重要瓶颈之一,所以我想尽可能优化它,我也有理由相信应该是可能的:
df["NewColumn1"] = df.apply(lambda row: compute_new_column1_value(row), axis=1)
df["NewColumn2"] = df.apply(lambda row: compute_new_column2_value(row), axis=1)
# a few more lines of code like the above
我将此解决方案基于this one之类的问题的答案(这是一个类似于我的问题,但特别是关于添加一个新列,而我的问题是添加许多新列)。我想这些df.apply()
调用中的每一个都是通过所有行的循环在内部实现的,我怀疑应该可以使用只循环所有循环一次的解决方案来优化它(而不是每次一次)我要添加的专栏。)
在其他答案中,我看到了对assign()函数的引用,它确实支持一次添加多个列。我尝试以下列方式使用它:
# WARNING: this does NOT work
df = df.assign(
NewColumn1=lambda row: compute_new_column1_value(row),
NewColumn2=lambda row: compute_new_column2_value(row),
# more lines like the two above
)
这不起作用的原因是因为lambda实际上根本没有接收到数据帧的行,所以它们似乎只是立即得到整个数据帧。然后期望每个lambda一次返回一个完整的列/系列/数组值。所以,我的问题在于我必须最终在lambda中的所有循环中实现手动循环,这显然会对性能更糟。
我可以从概念上考虑两个解决方案,但到目前为止还无法找到如何实际实现它们:
类似于df.assign()
(支持一次添加多个列),但能够将行传递到lambda而不是完整的数据帧
一种向compute_new_columnX_value()
函数进行向量化的方法,以便它们可以像df.assign()
期望的那样用作lambda。
到目前为止我的第二个解决方案的问题是基于行的版本我的一些函数看起来如下,我很难找到如何正确地向量化它们:
def compute_new_column1_value(row):
if row["SomeExistingColumn"] in some_dictionary:
return some_dictionary[row["SomeExistingColumn"]]
else:
return some_default_value
答案 0 :(得分:2)
您是否尝试将列初始化为nan
,逐行迭代数据,并使用loc
分配值?
import numpy as np
import pandas as pd
df = pd.DataFrame(np.random.randint(0, 20, (10, 5)))
df[5] = np.nan
df[6] = np.nan
for i, row in df.iterrows():
df.loc[i, 5] = row[1] + row[4]
df.loc[i, 6] = row[3] * 2
print(df)
产量
0 1 2 3 4
0 17 4 3 11 10
1 16 1 14 11 16
2 4 18 12 19 7
3 11 3 7 10 5
4 11 0 10 1 17
5 5 17 10 3 8
6 0 0 7 3 6
7 7 18 18 13 8
8 16 4 12 11 16
9 13 9 15 8 19
0 1 2 3 4 5 6
0 17 4 3 11 10 14.0 22.0
1 16 1 14 11 16 17.0 22.0
2 4 18 12 19 7 25.0 38.0
3 11 3 7 10 5 8.0 20.0
4 11 0 10 1 17 17.0 2.0
5 5 17 10 3 8 25.0 6.0
6 0 0 7 3 6 6.0 6.0
7 7 18 18 13 8 26.0 26.0
8 16 4 12 11 16 20.0 22.0
9 13 9 15 8 19 28.0 16.0
答案 1 :(得分:1)
不是试图将行标签放入.assign(),而是可以 在将.assign()链接到它之前,将一个布尔掩码应用于数据框。下面的示例可以很容易地扩展到多个布尔条件和多个lambdas,有或没有额外的for循环或if语句。
import pandas as pd
# Create data frame
idx = np.arange(0, 10)
rnd = pd.Series(np.random.randint(10, 20, 10))
alpha_idx = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
df = pd.DataFrame({'idx': idx, 'A': rnd, 'B': 100})
df.index = alpha_idx
# First assign() dependent on a boolean mask
df_tmp = df[df['A'] < 15].assign(AmulB = lambda x: (x.A.mul(x.B)),
A_B = lambda x: x.B - x.A)
# Second assign() dependent on a boolean mask
df_tmp2 = df[df['A'] >= 15].assign(AmulB = lambda x: (x.A.div(x.B)),
A_B = lambda x: x.B + x.A)
# Create a new df with different lambdas combined
df_lambdas = df_tmp.append(df_tmp2)
# Sort values
df_lambdas.sort_values('idx', axis=0, inplace=True)
print(df_lambdas)
A B idx
a 19 100 0
b 17 100 1
c 16 100 2
d 13 100 3
e 15 100 4
f 10 100 5
g 16 100 6
h 15 100 7
i 13 100 8
j 10 100 9
A B idx A_B AmulB
a 19 100 0 119 0.19
b 17 100 1 117 0.17
c 16 100 2 116 0.16
d 13 100 3 87 1300.00
e 15 100 4 115 0.15
f 10 100 5 90 1000.00
g 16 100 6 116 0.16
h 15 100 7 115 0.15
i 13 100 8 87 1300.00
j 10 100 9 90 1000.00
答案 2 :(得分:1)
如果您只有50个条件要检查,那么迭代条件并填充块中的单元格而不是逐行遍历整个帧可能更好。顺便说一句.assign()不仅接受lambda函数,而且代码也可以比我之前的建议更具可读性。下面是一个修改版本,也填充了额外的列。如果这个数据框有10,000,000行,我只想在A列中对10组数字范围应用不同的操作,这将是填充额外列的一种非常巧妙的方法。
import pandas as pd
import numpy as np
# Create data frame
rnd = np.random.randint(1, 10, 10)
rnd2 = np.random.randint(100, 1000, 10)
df = pd.DataFrame(
{'A': rnd, 'B': rnd2, 'C': np.nan, 'D': np.nan, 'E': np.nan })
# Define different ways of filling the extra cells
def f1():
return df['A'].mul(df['B'])
def f2():
return np.log10(df['A'])
def f3():
return df['B'] - df['A']
def f4():
return df['A'].div(df['B'])
def f5():
return np.sqrt(df['B'])
def f6():
return df['A'] + df['B']
# First assign() dependent on a boolean mask
df[df['A'] < 50] = df[df['A'] < 15].assign(C = f1(), D = f2(), E = f3())
# Second assign() dependent on a boolean mask
df[df['A'] >= 50] = df[df['A'] >= 50].assign(C = f4(), D = f5(), E = f6())
print(df)
A B C D E
0 4.0 845.0 3380.0 0.602060 841
1 3.0 967.0 2901.0 0.477121 964
2 3.0 468.0 1404.0 0.477121 465
3 2.0 548.0 1096.0 0.301030 546
4 3.0 393.0 1179.0 0.477121 390
5 7.0 741.0 5187.0 0.845098 734
6 1.0 269.0 269.0 0.000000 268
7 4.0 731.0 2924.0 0.602060 727
8 4.0 193.0 772.0 0.602060 189
9 3.0 306.0 918.0 0.477121 303
答案 3 :(得分:1)
我真的被这个问题所吸引,所以这是涉及外部词典的另一个例子:
import pandas as pd
import numpy as np
# Create data frame and external dictionaries
rnd = pd.Series(np.random.randint(10, 100, 10))
names = 'Rafael Roger Grigor Alexander Dominic Marin David Jack Stan Pablo'
name = names.split(' ')
surnames = 'Nadal Federer Dimitrov Zverev Thiem Cilic Goffin Sock Wawrinka Busta'
surname = surnames.split()
countries_str = ('Spain Switzerland Bulgaria Germany Austria Croatia Belgium USA Switzerland Spain')
country = countries_str.split(' ')
player = dict(zip(name, surname))
player_country = dict(zip(name, country))
df = pd.DataFrame(
{'A': rnd, 'B': 100, 'Name': name, 'Points': np.nan, 'Surname': np.nan, 'Country': np.nan})
df = df[['A', 'B', 'Name', 'Surname', 'Country', 'Points']]
df.loc[9, 'Name'] = 'Dennis'
print(df)
# Functions to fill the empty columns
def f1():
return df['A'].mul(df['B'])
def f2():
return np.random.randint(1, 10)
def f3():
return player[key]
def f4():
return player_country[key]
def f5():
return 'Unknown'
def f6():
return 0
# .assign() dependent on a boolean mask
for key, value in player.items():
df[df['Name'] == key] = df[df['Name'] == key].assign(
Surname = f3(), Country = f4(), Points = f1())
df[df['Name']=='Dennis'] = df[df['Name'] == 'Dennis'].assign(
Surname = f5(), Country = f5(), Points = f6())
df = df.sort_values('Points', ascending=False)
print(df)
A B Name Surname Country Points
1 97.0 100.0 Roger Federer Switzerland 9700.0
4 93.0 100.0 Dominic Thiem Austria 9300.0
8 92.0 100.0 Stan Wawrinka Switzerland 9200.0
5 86.0 100.0 Marin Cilic Croatia 8600.0
6 67.0 100.0 David Goffin Belgium 6700.0
7 61.0 100.0 Jack Sock USA 6100.0
0 35.0 100.0 Rafael Nadal Spain 3500.0
2 34.0 100.0 Grigor Dimitrov Bulgaria 3400.0
3 25.0 100.0 Alexander Zverev Germany 2500.0
9 48.0 100.0 Dennis Unknown Unknown 0.0
答案 4 :(得分:0)
由于我在评论中提供的原因,到目前为止提供的答案并没有为我的具体案例提供加速。到目前为止,我能找到的最佳解决方案主要基于this answer to another question。它没有给我一个大的加速(大约10%),但它是迄今为止我能做到的最好的。如果它们存在,我仍然对更快的解决方案非常感兴趣!
事实证明,就像assign
函数一样,apply
实际上也可以提供lambda,它一次返回多列的一系列值,而不是只返回一个lambda的值标量。所以,到目前为止我实施的最快的实现如下:
# first initialize all the new columns with standard values for entire df at once
# this turns out to be very important. Skipping this comes at a high computational cost
for new_column in ["NewColumn1", "NewColumn2", "etc."]:
df[new_column] = np.nan
df = df.apply(compute_all_new_columns, axis=1)
然后,不是将所有那些单独的lambda用于所有不同的新列,而是将它们全部实现在同一个函数中:
def compute_all_new_columns(row):
if row["SomeExistingColumn"] in some_dictionary:
row["NewColumn1"] = some_dictionary[row["SomeExistingColumn"]]
else:
row["NewColumn1"] = some_default_value
if some_other_condition:
row["NewColumn2"] = whatever
else:
row["NewColumn2"] = row["SomeExistingColumn"] * whatever
# assign values to other new columns here
结果数据框包含它先前执行的所有列,以及compute_all_new_columns
函数逐行插入的所有新列的值。保留原始顺序。这个解决方案不包含任何基于python的循环(速度很慢),并且只有一个循环通过“幕后”的行''由pandas apply
函数提供给我们
答案 5 :(得分:0)
此代码解决了我的答案的缺点,该答案基于使用外部字典填充数据框中的其他列。由于字典可以很容易地转换为数据帧,因此该示例基于从另一个数据帧的列中绘制数据。在这里,我回答的问题是,2017年十大网球运动员的名字在美国是多么受欢迎。这是通过从2016年美国社会保障管理局登记册中搜索32,868个婴儿名字并将性别和婴儿数量列添加到ATP数据框来实现的。我没有计时,但结果很快就会回来,我希望这比使用词典更适合你。由于后者中的密钥不能重复,因此您不会获得下面给出的两个性别的婴儿名称的行重复。
# Show first few lines and info
print('Baby Name Data Head:')
print(df_names.head())
print('Baby Name Data Info:')
print(df_names.info())
print('ATP Top Ten: \n', df_ATP)
# Add the baby inormation to the ATP data frame
df_ATP = pd.merge(df_ATP, df_names, on=['Name', 'Name'], how='left')
print('Modified ATP Top Ten: \n', df_ATP)
Baby Name Data Head:
Name Sex Number_of_Births
0 Emma F 19414
1 Olivia F 19246
2 Ava F 16237
3 Sophia F 16070
4 Isabella F 14722
Baby Name Data Info:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 32868 entries, 0 to 32867
Data columns (total 3 columns):
Name 32868 non-null category
Sex 32868 non-null category
Number_of_Births 32868 non-null int64
dtypes: category(2), int64(1)
memory usage: 1.8 MB
ATP Top Ten:
A B Name Surname Country Points
0 96 100 Roger Federer Switzerland 9600.0
1 80 100 Grigor Dimitrov Bulgaria 8000.0
2 72 100 Dominic Thiem Austria 7200.0
3 65 100 Pablo Busta Spain 6500.0
4 58 100 Stan Wawrinka Switzerland 5800.0
5 56 100 Jack Sock USA 5600.0
6 44 100 Marin Cilic Croatia 4400.0
7 43 100 David Goffin Belgium 4300.0
8 25 100 Alexander Zverev Germany 2500.0
9 14 100 Rafael Nadal Spain 1400.0
Modified ATP Top Ten:
A B Name Surname Country Points Sex Number_of_Births
0 96 100 Roger Federer Switzerland 9600.0 M 407
1 80 100 Grigor Dimitrov Bulgaria 8000.0 M 7
2 72 100 Dominic Thiem Austria 7200.0 F 11
3 72 100 Dominic Thiem Austria 7200.0 M 5394
4 65 100 Pablo Busta Spain 6500.0 M 787
5 58 100 Stan Wawrinka Switzerland 5800.0 M 15
6 56 100 Jack Sock USA 5600.0 F 11
7 56 100 Jack Sock USA 5600.0 M 8367
8 44 100 Marin Cilic Croatia 4400.0 F 165
9 44 100 Marin Cilic Croatia 4400.0 M 10
10 43 100 David Goffin Belgium 4300.0 F 14
11 43 100 David Goffin Belgium 4300.0 M 11028
12 25 100 Alexander Zverev Germany 2500.0 F 18
13 25 100 Alexander Zverev Germany 2500.0 M 13321
14 14 100 Rafael Nadal Spain 1400.0 M 1232
答案 6 :(得分:0)
由于您检查了代码很慢,因为您正在检查非常大的字典的键,我想我会发布下面的代码。这里的技巧是使用set intersection,它产生一个集合,该集合仅包含在感兴趣的数据框架和参考字典中找到的密钥。下面,我正在快速推导出一个包含2017年前10名ATP网球运动员名字的数据框,使用30294长的婴儿名字词典。由此产生的集合交集只有10个元素,因为它已经找到了字典中所有前10名玩家的名字。因此,允许填充额外列的for循环非常短。
import pandas as pd
# Prepare a large dictionary from the file containing US baby names from 2016
# The number of keys is 30294
df_bn = pd.read_csv('C:\yob2016.txt', header=None)
names = list(df_bn[0])
sex = list(df_bn[1])
bn_dict = dict(zip(names, sex))
# Create a tennis player data frame
names = 'Rafael Roger Grigor Alexander Dominic Marin David Jack Stan Pablo'
name = names.split(' ')
surnames = 'Nadal Federer Dimitrov Zverev Thiem Cilic Goffin Sock Wawrinka Busta'
surname = surnames.split()
df = pd.DataFrame({'Name': name, 'Surname': surname})
# Set intersection and filling the new data frame column
bn_set = set(bn_dict.keys())
tennis_player_set = set(df['Name'])
set_intersection = bn_set.intersection(tennis_player_set)
df['Sex'] = ''
for key in set_intersection:
df.loc[df['Name'] == key, 'Sex'] = bn_dict[key]
print(df)
Name Surname Sex
0 Rafael Nadal M
1 Roger Federer M
2 Grigor Dimitrov M
3 Alexander Zverev M
4 Dominic Thiem M
5 Marin Cilic M
6 David Goffin M
7 Jack Sock M
8 Stan Wawrinka M
9 Pablo Busta M