最近在做一个数据集的可视化项目,又捡起了pandas和python,这里把实际用到的技巧干货写下来,防止忘记再次去网上各种查和看documentation,同时也分享给大家.
1. 找出nan项
DataFrame.isnull()
返回一个大小和 store_items 一样的布尔型 DataFrame,并用 True 表示具有 NaN 值的元素,用 False 表示非 NaN 值的元素。
2. 统计nan项数量
Series.isnull().sum()
3. 看单个元素是不是nan:因为pandas里面的nan都指向np.nan这样一个object,np.nan本身的type是float。python中is函数的作用是比较object的id
element is np.nan
4. unique() 和 nunique() 用来获取去除了重复值的列(或去重列中的元素数目)
注意:unique是series的方法,如果想在dataframe中实现类似功能,使用df.drop_duplicates(subset=[‘col_name’],keep=‘last’)
keep='last’的意思就是保留重复行里面的最后一行。subset的意思是,按照这一列的重复情况来去重。
顺便一提,df.duplicated(subset=[‘col_name’],keep=‘last’)方法是返回除了你要keep的那一行都为True。要实现和drop_duplicates一样的切片功能可以:df[~df.duplicated(subset=[‘col_name’],keep=‘last’)]
>>> df['generation'].unique()
array(['Generation X', 'Silent', 'G.I. Generation', 'Boomers',
'Millenials', 'Generation Z'], dtype=object)
>>> df['country'].nunique()
101
对每个元素应用函数,function中应该有return
DataFrame/Series.apply(function)
5. #%%是spyder里面代码块的标识
6. Pandas里的assign方法
assign方法可以直接给DataFrame添加一列,或者替换掉名字相同的列;
特别注意在assign里面使用lambda函数时,lambda函数的变量是该DataFrame
DataFrame.assign(column_name=[])
7. Pandas里的map apply applymap方法
区别:
a
0 1
1 2
2 3
def add_num(a,b):
return a+b
b = a['a'].apply(add_num, args=(3,))
a
0 4
1 5
2 6
8. Pandas怎样替换数值
可以使用replace方法,也可以用map,apply等方法;
区别在于处理没有映射到的value时前者返回原value,后者返回NaN,并且速度更快。
记住两者区别,以及用字典映射来替换就可以了。
data.replace({"gender":{'1':'男', '0':'女'}})
data['gender']=data['gender'].map({'1':'男', '0':'女'})
9. Pandas根据条件来进行替换
笔者遇到了一个情况,将b列中的NaN值根据a和d的对应的关系来替换,具体而言就是把b列的NaN转化为d列中的9,因为9和a列里的3是对应关系。
也就是说,将一列中的特定元素根据另外两列的对应关系进行替换
a b c d
0 1 4.0 7.0 1
1 2 5.0 8.0 2
2 3 NaN NaN 9
这一步基于上面替换数值的方法,可以使用replace或者map方法来做,参数为dict。
首先把对应关系转化为字典,再把b列中的NaN变成a列中对应得值,最后根据a列与d列的对应关系,把b列的值转化。
为什么这里要用map,不用replace,因为map没有找到返回的是NaN,而replace返回a列中的原值,我们并不需要。
这样的另一好处是,原index不会改变。
cond = df.b.isnull()
replacement = dict(zip(df.a,df.d))
df.loc[cond,'b'] = df.loc[cond,'a'].map(replacement)
10. Pandas里面分列的方法
如果一列里面的元素是str,那么用str方法来切分
df['name'].str.split(';',expand=True
如果是元组,那么使用apply来得到一个新的dataframe,很神奇
df['a'].apply(pd.Series)
合并列
df['ab'] = df[['a', 'b']].apply(tuple, axis=1)
11. pandas里面进行列循环的方法
一个iterrow,一个是itertuple
iterrow返回index和包含一行数据的series,以列名为index
itertuple返回一个元组,包含行数据;
显然后者比前者快
12. 一个关于条件判断的现象,有如下DataFrame
a = pd.DataFrame()
a = a.assign(a=[1,2,3],b=[4,5,np.nan],c=[7,8,np.nan],d=[1,2,3])
a b c d
0 1 4.0 7.0 1
1 2 5.0 8.0 2
2 3 NaN NaN 3
以下五个条件判断
4. a[‘b’].isnull() & a[‘c’].isnull() 正常运行,在索引2得到True
5. a[‘b’].isnull() & a[‘a’]==3 无法得到预期结果True,在索引2得到了False
6. (a[‘b’].isnull()) & (a[‘a’]==3) 在各条件项上加上括号以后就得到了正确答案
7. a[‘d’]==2 & a[‘a’]==2 报错(ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().)
8. (a[‘d’]==2) & (a[‘a’]==2) 正常运行并得到预期结果。
原因是运算符的优先级问题,&的优先级更高
13. excel删除空行的方法
ctrl+g,选择blank
14. Pandas切片方法
对于DataFrame可以使用.loc方法,也可以使用iloc,
前者可以用列名取值,后者只能用行列数
a b c d
0 1 4.0 7.0 1
1 2 5.0 8.0 2
2 3 NaN NaN 9
#取a、b列的,1-2行
slice = df.loc[1:2,['a','b']]
slice = df.iloc[1:,0:1]
#也这样可以直接取a列
df['a']
df.a
当进行多重切片时不建议叠加使用直接法,会弹出警告,而转而使用loc或者iloc。
15. 方法链
这个用好了可以让代码变得很整洁,一定要用。大概意思就是可以对一个df或者series链式使用方法,需要注意的是,如果想像我这样每个方法提一行,就要给外面加个括号。
df = (df.dropna()
.unique()
.loc[1,'a']
.map(lambda element: element+1 if element <5
else element-1))
16. 匿名函数
上面一条里面的lambda函数就是匿名函数,作用相当于一个不用def的函数,放在map,apply里面相当好用。lambda后面是传入参数的名字。注意lambda里面不能赋值。所以复杂一点的func还是要def一下。
17.指定数据类型
读取csv时指定特定列的数据类型,这样可以避免"01000"这类编号被读取为数字变成"1000"类似的情况
df = pd.read_csv("somefile.csv", dtype = {'column_name' : str})
18. 常见caveat
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
避免方法:
df2 = df[['A']]
df2['A'] = df['A'] / 2
# 变成
df2 = df.loc[:, ['A']]
df2['A'] = df['A'] / 2
19. 大文件读取方法
这里我写了一个函数,使用分块读取并处理来降低内存使用量,chunk_size就是你一次循环中要读取的行数
def read_big_data(file_path, delimiter=',', function=None, chunk_size=1000000):
# 大型数据使用如下,先生成一个iterator
reader = pd.read_csv(file_path, delimiter=delimiter, iterator=True)
# 按照chunk来分块读取并处理
loop = True
chunks = []
while loop:
try:
chunk = reader.get_chunk(chunk_size)
if function is not None:
chunk = function(chunk)
print('current_chunk_processed')
chunks.append(chunk)
except StopIteration:
loop = False
print('Iteration is stopped')
df = pd.concat(chunks, ignore_index=True)
return df
20. 按列条件合并df
new = pd.merge(a,b,how='left',on = '')
a.merge(...)
以下来自处理一个美国人口数据集的经验
21. groupby 和 transform
df.groupby(['A','B']).method()
返回的是带index的serial,index为’A’,'B’对应的值,如果要消除这个index,并将index还原为df中的列,可以使用:
df.groupby(['A','B']).method().reset_index()
如果只是要消除index,不还原为列,则可以:
df.groupby(['A','B']).method().reset_index(drop = True)
需要注意的是,groupby后接的方法,会把df行数会按照不同的"A","B"值来浓缩,使得不同的“A”,“B”对应唯一一行,如果不想浓缩,而与原df行数相同,可以使用:
df.groupby(['A','B']).transform('method')method()
经由transform返回的是一个没有index的serial。
transform可以用来计算同类别数值的占比,比如我想计算人口普查数据各城市的男女比例
'''
CITY NAME GENDER
宜宾 张三 M
宜宾 李四 F
南京 小美 F
南京 小兰 F
'''
# 第一步获取各城市男女两性人数
gender_pop_city = (df.groupby(['CITY','GENDER'])
.NAME
.count()
.reset_index()
.rename(columns= {'NAME':'NUM' })
)
'''
CITY GENDER NUM
宜宾 M 1
宜宾 F 1
南京 F 2
'''
# 第二步获取个城市总人数
pop_city = gender_pop_city.groupby('CITY')\
.NUM\
.transform('sum')
# 第三步,除以
gender_pop_city['RATIP'] = gender_pop_city['NUM']/ pop_city
'''
CITY GENDER NUM RATIO
宜宾 M 1 0.5
宜宾 F 1 0.5
南京 F 2 1
'''
以下来自帮老师整理历史课程资料时使用的技巧
22. 如何在第一行插入列和信息
这里推荐新建DataFrame
# 创建一个和原df列名相同的空df
first_row = pd.DataFrame(columns = data.columns)
# 再把新df第一行的index改名为'总数'或者其他你想要的,并使用apply
first_row.loc['总数'] = data.apply(lambda x:sum(x) if x.dtype == bool else '', axis=0)
# 最后拼接起来
data = pd.concat([first_row,data])
# 在console里面显示所有列
with pd.option_context("display.max_columns", None):
print(data)
·这个地方要注意,python中的lambda函数在任何情况下都要有return value,所以写了if,就必须要跟else来囊括所有情形,否则报错,笔者也纠结了好一会儿才发现。
·另外复习以下,对df使用apply,如果axis=0,则传入参数是每列,axis=1,则传入参数是每行。因此这里lambda函数中的x是df的列,这里只对类型是bool的列求和。
·这里用到知识点还有切片:
df.loc[ ] 只传入一个参数的时候是切index,如果没有对应index,则会append新的一行在df尾部。这里因为df是空的DataFrame,所以新的一行也在第一行。
·concat方法,不能接在df后面,只能pd.concat(),并且被concat的df们要放在一个list里面传入。
23. 找到含有合适字符串的行
使用contains函数
is_phd = data['授课对象'].str.contains('(博士)')
# 此时会有user warning
'''
UserWarning: This pattern is interpreted as a regular expression, and has match groups. To actually get the groups, use str.extract.
'''
原因是contains传入的参数是正则表达式,括号在正则表达式中是有含义的,可以无视。
24. 不同的DataFrame输出到同一xlsx表格不同sheet
with pd.ExcelWriter('result.xlsx',
engine="openpyxl",
mode="a",
if_sheet_exists='replace') as writer:
output.to_excel(writer,
sheet_name='主页')
word_freq.value_counts()\
.reset_index()\
.to_excel(writer,
sheet_name = '关键词统计')
使用ExcelWriter即可,这里推荐用with来配合使用ExcelWriter,比较美观,不然就要writer.save()才能保存你的excel。
另外这里讲一下value.counts方法,很好用,对列使用,获取列中所有元素出现次数。
25. 反选条件
如代码所示,不是硕博专科,那么就是本科了,使用“~”来做“not”,让True和False转换;“&”做“和”的逻辑判断;“|”做“或”
is_master = data['授课对象'].str.contains('(硕士)|(研究生)')
is_phd = data['授课对象'].str.contains('(博士)')
is_associate = data['授课对象'].str.contains('(专科)')
is_bachelor = (~is_master) & (~is_phd) & (~is_associate)
26. Seaborn不显示中文怎么办?
此后如果从非官网看到方法,放出索引
来自https://zhuanlan.zhihu.com/p/337423390
指定字体即可
rc = {'font.sans-serif': 'SimHei',
'axes.unicode_minus': False}
sns.set( rc=rc)
27. 读取指定列,并同时规定列的类型
我有个特别大的csv需要读取,因此只读取部分列,并同时规定data type,
只需要:
d_col = dict({'col_name0':'dtype','col_name1':'dtype'})
df = pd.read_csv('fileName', usecols=d_col, dtype=d_col)
这里用dict可以同时满足usecols和dtype两个参数的输入,在不需要指定data type的时候usecols也可以用list输入。
28.int类型的列里面有空值怎么办
d_col = dict({'HOUSEHOLD_INCOME':'int32','col_name1':'dtype'})
如果HOUSEHOLD_INCOME列中有空值,会报错,因为空值不属于int32的范畴。此时可以使用pandas数据类型“Int32”,此时空值会变成:pd.NA
# 分列散点图
df.plot()
# 密度图,方便查看数据是否符合正太分布
df.plot(kind='kde')
# 通常会使用
cond = (df.col>=1) & (df.col<=2)
# 来获取1-2之间的行
# 但也可以直接用:
cond = df.col.between(1,2)
# 注意这样是不行的:
cond = df.col == 1|2
# 有两种,1, deep 参数为True则将df中包含的python object的内存占用大小也囊括进去,但会花更多时间;返回一个series,index是列名,value是XXX byte
df.memory_usage(deep=True)
'''
YEAR 11247312
SAMPLE 11247312
SERIAL 11247312
CBSERIAL 11247312
...
LAN_POP_RATIO 11247312
INDI 11247312
Length: 175, dtype: int64
'''
# 2. memory_usage参数为'deep'则将df中包含的python object的内存占用大小也囊括进去,但会花更多时间;返回一个None,输出的是print函数
df.info(memory_usage='deep')
'''
Int64Index: 1405914 entries, 3 to 2955667
Columns: 174 entries, YEAR to INDI
dtypes: float64(125), int32(1), int64(45), object(3)
memory usage: 1.8+ GB
'''
32. 减少内存的两个方法
第一:指定更小的数据类型
对于一个100多万行的series,默认整数类型是int64,但如果用不到范围那么大的数字可以改为int32,int16.
df['YEAR'].info()
'''
Int64Index: 1405914 entries, 3 to 2955667
Series name: YEAR
Non-Null Count Dtype
-------------- -----
1405914 non-null int64
dtypes: int64(1)
memory usage: 21.5 MB
'''
df['YEAR'].astype('int16').info()
'''
Int64Index: 1405914 entries, 3 to 2955667
Series name: YEAR
Non-Null Count Dtype
-------------- -----
1405914 non-null int16
dtypes: int16(1)
memory usage: 13.4 MB
'''
第二,如果df中有大量重复文本数据,可以使用category dataType
category本质上相当于将str转化为数字,再用一个codebook把数字和str对应起来,以此节省内存空间。
test.FIP.info(memory_usage='deep')
'''
Int64Index: 1405914 entries, 3 to 2955667
Series name: FIP
Non-Null Count Dtype
-------------- -----
1405914 non-null object
dtypes: object(1)
memory usage: 93.9 MB
'''
test.FIP.astype('category').info(memory_usage='deep')
'''
Int64Index: 1405914 entries, 3 to 2955667
Series name: FIP
Non-Null Count Dtype
-------------- -----
1405914 non-null category
dtypes: category(1)
memory usage: 13.5 MB
'''
如果分类变量是有序的怎么办?我们可以指定顺序。
例如一个产品df里面,有差、良、优三种评分,则可以:
df['quality']=df.quality.astype('category',categories=['差','良','优'],ordered=True)
df.sort_values('quality')
这样如果根据quality进行排序,不再按字母顺序排序,而是会根据我们指定的顺序来排序。(默认升序,ascending=True,也就是从差->优)
33. 给重复项加上后缀
而且后缀是重复次数。
df = pd.DataFrame([['a'], ['a'], ['a'], ['b'], ['b'], ['a'],['c']],
columns=['A'])
'''
加之前
A
0 a
1 a
2 a
3 b
4 b
5 a
6 c
'''
cond = df.A.duplicated(keep=False)
rep_num = df.groupby('A').cumcount()
b = df.A.mask(cond, df.A + rep_num.astype(str))
'''
加之后
0 a0
1 a1
2 a2
3 b0
4 b1
5 a3
6 c
Name: A, dtype: object
'''
34. 按特定列值分开df,按行数平均分开df
下面演示按照df厘米YEAR不同数值分成不同的sub df,储存在一个dict里面,用key来获取。
注意:YEAR列依然在sub df里面
dfs = dict(tuple(data.groupby('YEAR')))
dfs.values()
dfs.items()
平均分使用numpy.array_split(df, n)即可。无法除尽也可以分成大致相同的n份,如果你计算刚好可以平均分配,那可以用np.split(df, n)。array_split的好处就在于想分几份就几份,不会报错。
35. tqdm显示apply,transform进度条
# 安装
conda install -c conda-forge tqdm
# 首先import
from tqdm import tqdm
# 然后初始化
tqdm.pandas(desc=‘anything’)
# 之后把apply替换为progress_apply
data.loc[cond,'NUCLEAR'] = (data[cond]
.groupby('CLUSTER')
.RELATE
.progress_transform(
lambda col: 0 if any((col==5)|(col==6)|(col==9)) else 1))
36. pandas多核运算
对一个两千多万行的df进行transform操作,每秒只能处理大概3600行,tqdm估计要50分钟。。。于是决定找包,dask,rapids,pandarelle 似乎我都配置成功,不行。。。算了还是自己写一个并行吧。
尝试了两种方法:
a. 把要并行运算的df拆分后分开保存,在subprocess里面分别读取:结果不可行,因为io太耗时间。代码如下:
import pandas as pd
import numpy as np
from tqdm import tqdm
import multiprocessing as mp
def func2(message):
df = pd.DataFrame(data=np.ones([10,100000]))
tqdm.pandas(desc=f'job{message}')
df = df.progress_apply(lambda col: col+5)
print('job done, now caching')
df.to_parquet(f'df_temp{message}')
print('caching done')
print(df.columns)
def para2(func):
cpu_count = mp.cpu_count()
with mp.Pool(10) as p:
p.map(func, [i for i in range(10)])
print('para work done, now loading cache')
ret_list = [pd.read_parquet(f'df_temp{i}.csv') for i in range(10)]
print(ret_list)
return pd.concat(ret_list)
para2(func2)
需要注意的地方是:
1 multiprocessing这个pool方法,各个进程之间并不共享内存,而且和主进程之间传data也非常慢。这里是给subprocess一个message,然后按照这个message自己去读取。如果要处理大的dataframe,我认为应该事先把dataframe切割好,保存为名字带编号的parquet,然后告诉sub_process自己去读取。
2 sub_process做完工作以后不要return你的dataframe,原因如上速度太慢,而是选择将dataframe保存,再让主进程一个一个读取,并合并。
3 这种方法进度条只能显示一个进程的,我还在寻找让多个进度条同时存在的方法,如果你知道,希望你能告诉我。
4 在df的保存读取上面,我试了几种数据类型,feather比parquet快,建议用feather。hdf比这两个慢,但有个好处是几个df可以合并到一个hdf里面,按key读取,比较美观。
b.直接传入拆分后的df到subprocess中,这样子速度更快,缺点是内存开销更大(原因未知)。
下面具体讲讲这种方法。
首先是package选择,我们使用multiprocess,而不是multiprocessing。原因在于这个方法会装饰传入func,这样会导致装饰后的func变为local variable,无法被pickle。
def parallelise_func(func,pbar=False):
def wrapper(jobs):
s_time = time.perf_counter()
job_num = jobs[1]
dataFrame = jobs[0]
print(f'job {job_num} passed to the subprocess and is being dealt with')
if pbar is True:
tqdm.pandas()
result = func(dataFrame)
print(f'{job_num}th job done, takes {time.perf_counter()-s_time} seconds')
return result
print('function parallelised')
return wrapper
def test_func(df):
print(f'current memory usage:{psutil.virtual_memory().used/2.**30}')
df = df.apply(lambda col: col+5)
return df
def test(core=8):
print('create ten 10x100000 DataFrames to test for speed \n default core counts = 8')
List_DataFrame = [pd.DataFrame(data=np.ones([10,100000])) for _ in range(10)]
print(f'1 dataframe size:{List_DataFrame[0].memory_usage().sum()/2**30}')
res = parallelise(List_DataFrame, test_func, core, pbar=True)
return res
如代码所示,我见把要传入df.apply()或者transform里的func写好,然后用一个装饰函数parallelise_func把这个func改造为适应为多进程运行的结构。这里因为multiprocessing用的是pickle,而multiprocess用的是dill,我选择multiprocess,不然会报错。
(原因在此处)
(更多描述)
pool就相当于劳动力市场,cpu就是打工人,第一个参数是func是工作流程,第二个参数是一个list,list里面装着df,df就是工作内容。
pool有几个方法,map, imap, 区别见这里
用map就行了。子进程运行完毕以后,使用的内存会释放掉。
from datetime import datetime
def not_during_the_night(func):
def wrapper():
if 7 <= datetime.now().hour < 22:
func()
else:
pass # Hush, the neighbors are asleep
return wrapper
def say_whee():
print("Whee!")
say_whee = not_during_the_night(say_whee)
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def say_whee():
print("Whee!")
今天刚发现了np.where的用法,奉为天人,比apply lambda函数快多了,而且写出来的代码也特别优雅简洁。
具体用法是给np.where(condition, df1, df2)三个参数 ,条件,满足条件赋值,不满足条件赋值。
和pandas where的区别在于,没有pd.where 这样的topdown方法,按照官网说法:
df1.where(m, df2)
# is equivalent to
np.where(m, df1, df2).
这样使得使用限制很多,比如我不想要df1的值呢。所以还是使用numpy.where吧!
但也有一个问题,numpy.where无法对pd.NA进行比较,会报错:TypeError: boolean value of NA is ambiguous
注意:data = data.fillna(np.nan)没有用,只能data = data.replace({pd.NA: np.nan})具体原因不明。
另外要注意的是np.nan是float,而pd.NA是一个pandas自定义类NAType。pd.NA可以放在"Int"(注意I大写了)类col中,保持其他数值为int,一旦引入np.nan那么该列格式会被转化为float。
所以我们在读取csv的时候,空值用哪种格式,和数据类型有关,如果数据类型是整数,那么就是pd,如果是浮点数,那么就是np
所以这也是np.where的一个缺陷吧。
然而并没有这么简单,下面这个小测试显示,其实只有在pd.NA在np.array里面的时候,才会出现TypeError: boolean value of NA is ambiguous。但我在实际使用的时候使用dataframe.col>0,却出现了报错。我只能怀疑在处理大量数据(我这有两千多万行)的时候,转换为Series会转换为array去运算。
#%% 测试 TypeError: boolean value of NA is ambiguous
pd.NA>0
df = pd.DataFrame({'a':[pd.NA, 1, 2, 3]})
ar = np.array([pd.NA, 1, 2, 3])
sr = pd.Series([pd.NA, 1, 2, 3])
print('dataframe with pd.NA ok')
np.where(df > 1, 5, 0)
df>1
print('array with pd.NA not ok')
np.where(ar > 1, 5, 0)
ar>1
print('Series with pd.NA ok')
np.where(sr > 1, 5, 0)
sr>1
最后解决办法:把pd.NA转化为-1.。。。。。。。。。。
主要有count和size两类方法
转载自https://favtutor.com/blogs/pandas-groupby-count
import pandas as pd
import numpy as np
# create a dataframe
data = {
"Students": ["Ray", "John", "Mole", "John", "John", "John", "Ray", "Rick"],
"Subjects": ["Maths", "Economics", "Science", "Maths", np.nan, "Statistics", "Statistics", "Computers"]
}
df = pd.DataFrame(data)
# size
print(df.groupby('Students').size())
#count
print(df.groupby('Students').count())
size和count的区别在于,count不会统计nan项,size会,size返回的数量加总就是原始df总行数。
如果你使用了多个key进行groupby,那么你会得到一个multiindex Series,这个时候配合.unstack()方法,可以把multiindex Series转化为一个dataFrame,level 0是index,level 1是col
书接上回,我此时有了新的需求,但并没有在网上找到答案,于是自己摸索了一下。
假如我们groupby两个col,并因此得到了一个multiindex series,类似于:
CLUSTER LANGAUGE
2005000000021 1 3
2005000000041 0 1
1 1
2005000000061 1 2
2005000000101 1 1
2005000000131 0 2
1 4
2005000000151 1 3
11 1
2005000000161 1 1
2005000000181 1 1
2005000000211 1 2
2005000000221 2 1
2005000000261 1 1
2005000000271 1 1
2005000000301 0 2
1 2
2005000000311 1 1
2005000000331 0 2
63 2
dtype: int64
我想给这个series在每个group内部按照#降序#排序怎么办?
并不能只用sort_value,因为会破坏multiindex结构
fam_lan.sort_values(ascending=False)
Out[27]:
CLUSTER LANGUAGE
2005000000131 1 4
2005000000021 1 3
2005000000151 1 3
2005000000331 0 2
2005000000301 1 2
0 2
2005000000211 1 2
2005000000331 63 2
2005000000131 0 2
2005000000061 1 2
2005000000151 11 1
2005000000161 1 1
2005000000041 0 1
2005000000221 2 1
2005000000261 1 1
2005000000271 1 1
2005000000101 1 1
2005000000311 1 1
2005000000041 1 1
2005000000181 1 1
dtype: int64
像是level 0 index里面的2005000000131,就和自己的两个level 1 index分开了。一个在第一行,一个在第八行。
解决办法:在sort完毕之后,进行sort_index。
fam_lan.sort_values(ascending=False).sort_index(level=0, sort_remaining=False)
Out[29]:
CLUSTER LANGUAGE
2005000000021 1 3
2005000000041 0 1
1 1
2005000000061 1 2
2005000000101 1 1
2005000000131 1 4
0 2
2005000000151 1 3
11 1
2005000000161 1 1
2005000000181 1 1
2005000000211 1 2
2005000000221 2 1
2005000000261 1 1
2005000000271 1 1
2005000000301 1 2
0 2
2005000000311 1 1
2005000000331 0 2
63 2
dtype: int64
观察2005000000131,已经达成了我们想要的效果,4排在1前面。注意,此处sort_index(level=0, sort_remaining=False)一定要sort_remaining=False,否则他会把level 1的index也进行排序就改变了我们之前对value排序的结果。
接下来,如果我想要每个小group里面的最大值怎么搞?
有了我们之前的排序工作就变得很简单了。
fam_lan.sort_values(ascending=False).sort_index(level=0, sort_remaining=False).groupby(level=0).head(1)
Out[32]:
CLUSTER LANGUAGE
2005000000021 1 3
2005000000041 0 1
2005000000061 1 2
2005000000101 1 1
2005000000131 1 4
2005000000151 1 3
2005000000161 1 1
2005000000181 1 1
2005000000211 1 2
2005000000221 2 1
2005000000261 1 1
2005000000271 1 1
2005000000301 1 2
2005000000311 1 1
2005000000331 0 2
dtype: int64
问题解决!
对一个两千多万行的数据作groupby transform,单核运行要50多分钟,折腾了好久并行运行,也要5分钟。但采用向量化编成只要几行代码几秒钟就可以完成!
def infer_immi_fam(data):
# 添加移民家庭信息:条件是任意家庭成员在美国外出生, CLUSTER是家庭唯一编码
immi_fam = (data.groupby('CLUSTER').BPL
.transform(lambda bpl: 1 if any(bpl>99) else 0))
print('immigration status added.')
return immi_fam
''' 向量化!'''
immi_fam = data.CLUSTER[data.BPL>99]
data['IMMIGRATION_FAM'] = np.where(data.CLUSTER.isin(immi_fam),1,0)
return data