在 Pandas DataFrame 中,我想根据另一列的值有条件地创建一个新列。在我的应用程序中,DataFrame 通常有几百万行,并且唯一条件值的数量很少,按统一顺序排列。性能极其重要:生成新列的最快方法是什么?
我在下面创建了一个示例案例,并且已经尝试并比较了不同的方法。
在示例中,条件填充表示为
基于列值的字典查找label (此处: 1, 2, 3 之一)。
lookup_dict = {
1: 100, # arbitrary
2: 200, # arbitrary
3: 300, # arbitrary
}
label output
0 3 300
1 2 200
2 3 300
3 3 300
4 2 200
5 2 200
6 1 100
7 1 100
Nlines):pandas.groupby().apply() pandas.groupby().indices.items() pandas.Series.map numpy.select pandas.groupby().apply()pandas.groupby()在 label ,然后使用 apply() 用相同的值填充每个块.def fill_output(r):
''' called by groupby().apply(): all r.label values are the same '''
r.loc[:, 'output'] = lookup_dict[r.iloc[0]['label']]
return r
df = df.groupby('label').apply(fill_output)
>>> method_1_groupby ran in 2.29s (average over 3 iterations)
pandas.groupby().indices.items()apply我使用 groupby().indices.items() 直接访问索引.这最终比方法1快了两倍,这是我使用了很长时间的方法dgb = df.groupby('label')
for label, idx in dgb.indices.items():
df.loc[idx, 'output'] = lookup_dict[label]
method_2_indices ran in 1.21s (average over 3 iterations)
pandas.Series.mapdf['output'] = df.label.map(lookup_dict.get)
map最终比方法 1 慢两倍。for label, value in lookup_dict.items():
df.loc[df.label == label, 'output'] = value
groupby基于的解决方案比这个更快,因为 Pandas 必须与 df.label == label 进行三个比较这里。结果证明我错了:method_4_forloop ran in 0.54s (average over 3 iterations)
numpy.selectselect函数,基于此 StackOverflow answer .conditions = [df.label == k for k in lookup_dict.keys()]
choices = list(lookup_dict.values())
df['output'] = np.select(conditions, choices)
method_5_select ran in 0.29s (average over 3 iterations)
numba方法 6 中的方法。@jit(int64[:](int64[:]), nopython=True)
def hardcoded_conditional_filling(column):
output = np.zeros_like(column)
i = 0
for c in column:
if c == 1:
output[i] = 100
elif c == 2:
output[i] = 200
elif c == 3:
output[i] = 300
i += 1
return output
df['output'] = hardcoded_conditional_filling(df.label.values)
method_6_numba ran in 0.19s (average over 3 iterations)
import pandas as pd
import numpy as np
from timeit import timeit
from numba import jit, int64
lookup_dict = {
1: 100, # arbitrary
2: 200, # arbitrary
3: 300, # arbitrary
}
Nlines = int(1e7)
# Generate
label = np.round(np.random.rand(Nlines)*2+1).astype(np.int64)
df0 = pd.DataFrame(label, columns=['label'])
# Now the goal is to assign the look_up_dict values to a new column 'output'
# based on the value of label
# Method 1
# using groupby().apply()
def method_1_groupby(df):
def fill_output(r):
''' called by groupby().apply(): all r.label values are the same '''
#print(r.iloc[0]['label']) # activate to reveal the #2936 issue in Pandas
r.loc[:, 'output'] = lookup_dict[r.iloc[0]['label']]
return r
df = df.groupby('label').apply(fill_output)
return df
def method_2_indices(df):
dgb = df.groupby('label')
for label, idx in dgb.indices.items():
df.loc[idx, 'output'] = lookup_dict[label]
return df
def method_3_map(df):
df['output'] = df.label.map(lookup_dict.get)
return df
def method_4_forloop(df):
''' naive '''
for label, value in lookup_dict.items():
df.loc[df.label == label, 'output'] = value
return df
def method_5_select(df):
''' Based on answer from
https://stackoverflow.com/a/19913845/5622825
'''
conditions = [df.label == k for k in lookup_dict.keys()]
choices = list(lookup_dict.values())
df['output'] = np.select(conditions, choices)
return df
def method_6_numba(df):
''' This works, but it is hardcoded and i don't really know how
to make it compile with list as runtime constants'''
@jit(int64[:](int64[:]), nopython=True)
def hardcoded_conditional_filling(column):
output = np.zeros_like(column)
i = 0
for c in column:
if c == 1:
output[i] = 100
elif c == 2:
output[i] = 200
elif c == 3:
output[i] = 300
i += 1
return output
df['output'] = hardcoded_conditional_filling(df.label.values)
return df
df1 = method_1_groupby(df0)
df2 = method_2_indices(df0.copy())
df3 = method_3_map(df0.copy())
df4 = method_4_forloop(df0.copy())
df5 = method_5_select(df0.copy())
df6 = method_6_numba(df0.copy())
# make sure we havent modified the input (would bias the results)
assert 'output' not in df0.columns
# Test validity
assert (df1 == df2).all().all()
assert (df1 == df3).all().all()
assert (df1 == df4).all().all()
assert (df1 == df5).all().all()
assert (df1 == df6).all().all()
# Compare performances
Nites = 3
print('Compare performances for {0:.1g} lines'.format(Nlines))
print('-'*30)
for method in [
'method_1_groupby', 'method_2_indices',
'method_3_map', 'method_4_forloop',
'method_5_select', 'method_6_numba']:
print('{0} ran in {1:.2f}s (average over {2} iterations)'.format(
method,
timeit("{0}(df)".format(method), setup="from __main__ import df0, {0}; df=df0.copy()".format(method), number=Nites)/Nites,
Nites))
Compare performances for 1e+07 lines
------------------------------
method_1_groupby ran in 2.29s (average over 3 iterations)
method_2_indices ran in 1.21s (average over 3 iterations)
method_3_map ran in 3.07s (average over 3 iterations)
method_4_forloop ran in 0.54s (average over 3 iterations)
method_5_select ran in 0.29s (average over 3 iterations)
method_6_numba ran in 0.19s (average over 3 iterations)
def method_3b_mapdirect(df):
''' Suggested by https://stackoverflow.com/a/51388828/5622825'''
df['output'] = df.label.map(lookup_dict)
return df
def method_7_take(df):
''' Based on answer from
https://stackoverflow.com/a/19913845/5622825
Exploiting that labels are continuous integers
'''
lookup_arr = np.array(list(lookup_dict.values()))
df['output'] = lookup_arr.take(df['label'] - 1)
return df
method_3_mapdirect ran in 0.23s (average over 3 iterations)
method_7_take ran in 0.11s (average over 3 iterations)
最佳答案
我会考虑 .map (#3) 这样做的惯用方法 - 但不要通过 .get - 单独使用字典,应该会看到非常显着的改进。
df = pd.DataFrame({'label': np.random.randint(, 4, size=1000000, dtype='i8')})
%timeit df['output'] = df.label.map(lookup_dict.get)
261 ms ± 12.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit df['output'] = df.label.map(lookup_dict)
69.6 ms ± 3.08 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
.map 快,但这并不总是正确的,例如如果你有一组字符串。take 进行查找。 ,这应该和 numba 一样快。我认为这基本上是尽可能快的 - 可以在 cython 中编写等价物,但不会更快。%%timeit
lookup_arr = np.array(list(lookup_dict.values()))
df['output'] = lookup_arr.take(df['label'] - 1)
8.68 ms ± 332 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
关于python - 有条件地创建 Pandas 列的最快方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51388201/
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer