目录:
****1. Numpy-diag 矩阵变换
- stack()/unstack()
- pd.pivot_table()
- pd.melt()
- groupby聚类算法
- mapping小技巧
-
numpy.vectorize()**
前言
最近遇到很多需要迭代和归并数据的情况,一直以来的做法,都是循环主要的键,去进行后续操作。这是最典型的Python 操作,然而还是上次提到的效率问题。记得之前朋友和我讲过Py的历史,甚至反思了下自己的定位。
我们写好的py脚本,会通过解释器翻译成机器可以识别并执行的信息。解释器包括:编译器和虚拟机(与Java类似)。之前听朋友讲过,如果py脚本自身已经优化到极致,就可以考虑Cython和Numba来进一步加快速的,如果是大数据,可以考虑py-spark这种伪分布式,可以通过Modin库合理分配cpu多核运算。
pip --default-timeout=100 install modin[ray]
pip -i https://pypi.tuna.tsinghua.edu.cn/simple --default-timeout=100 install pyspark
以下就记录一下最近遇到的一些情况,以及优化方式。
1. Numpy-diag 矩阵变换
numpy.diag简单说就是对角线,线性代数矩阵里面对角线作为一个list。
>>>array_1=np.array([1,0,0,0],[0,2,0,0],[0,0,3,0],[0,0,0,4])
np.diag(array_1)
out:
[1,2,3,4]
>>>array_2=np.array([2,3,4])
np.diag(array_2)
out:
array([2,0,0],[0,3,0],[0,0,4])
这种方法放在pandas的dataframe里也可以用。比如,我们有一列数据,每4个为一组,这样就可以按照这种方式,进行数据结构的转换。不用再去循环运算。
2. stack()/unstack()
这种方法可以本身是用于数据转置,stack()为列转行,unstack()为行传列,如:
A B C
1 2 3
2 6 4
stack()后变成:
1 2
2 6
3 4
因此,我们可以用于数据增维。比如,一般py代码在增加维度(改变某一个键值,复制第一行数据),都会首先copy第一行数据,进行增加复制,这样避免不了遍历所有需要复制的对象,因此效率过低。所以我们可以通过这种方法,一步到位。
#普通遍历方式
>>>df_new=pd.DataFrame(columns=['name','Age','class'])
for i in range(len(df['Age'])):
df_temp=df.loc[0,:].copy()
df_temp['cal']=['A','B','C']
df_new=df_new.append(df_temp)
>>>print(df_new)
此种方法思路较为传统:1. 对整个dataframe进行遍历,复制每一行。2. 将对应的List插入这个temp df。3. 最后用之前建好的新的空dataframe df_new去装载这些新扩充好的df_temp。
也就是加入之前有n行,扩充后变成3*n行数据。
此种方法时间复杂度较高,如果需要扩充的数据为万行以上就会特别慢。上次讲到pandas的内置函数,他本质就是C语言的映射,内在的遍历是直接执行C代码,因此我们一定要有限考虑内置函数。
如上述算法可以改成:
>>>temp_str='A,B,C'
df['cal']=temp_str
df_1=df['cal'].str.split(',',expand=True)
df_1=df_1.stack()
df_1=df_1.reset_index(level=1, drop=True)
df_2=pd.DataFrame(df_1,columns=['cal'])
df_final=df.drop(['cal'], axis=1).join(df_2)
print(df_final)
如此一来,速度可以快了6倍左右。有几个小时tip需要注意:
1.join的时候默认匹配原有的index,所以新建的list才会完美衔接原有df,自动复制Index相同的rows。
2.unstack()可以进行反向操作.
下面附上笔者自己写的一段代码,可以改一改,进行增row和增columns的操作:
##添加n列:将period放在列上
class add_row_col():
def __init__(self, dataframe, new_col_horizontal, new_col_vertical, counts=None):
self.d = dataframe
self.h = new_col_horizontal
self.n = new_col_vertical
self.c = counts
# 创建一个p1---p?的字符串,逗号间隔。
def create_str(self):
P_list = list()
for i in range(1, self.c + 1):
N = str(str(self.h)) + str(i)
P_list.append(N)
PStr = ','.join(P_list)
return PStr
# 创建P1-P12的list,增加每个FA的维度。(12*N行)
def create_matrix(self):
P_list = list()
for i in range(1, self.c + 1):
N = str(str(self.h)) + str(i)
P_list.append(N)
return [a for a in P_list]
# 增加维度!(复制N行)
def multi_dimension(self):
str_list = self.create_str()
df_1 = self.d
df_1[str(self.n)] = str(str_list)
df_1[str(self.n)] = df_1[str(self.n)].apply(lambda y: y.replace('[', '').replace(']', ''))
df_temp = df_1[str(self.n)].str.split(',', expand=True)
df_temp = df_temp.stack()
df_temp = df_temp.reset_index(level=1, drop=True)
df_temp = pd.DataFrame(df_temp, columns=[str(self.n)])
df_2 = df_1.drop([str(self.n)], axis=1).join(df_temp)
df_2[str(self.n)] = df_2[str(self.n)].apply(lambda x: x.replace("'", ""))
df_2.reset_index(drop=True, inplace=True)
return df_2
# 增加维度!(复制N列)
def create_col(self):
df = self.d.copy()
matrix = self.create_matrix()
for g in matrix:
df[g] = 0
return df
#引用的时候:
>>>df_2=add_row_col(df_1,'P','Period_list',counts=12).multi_dimension() #纵行增值放在一列
>>>df_2=add_row_col(df_1, 'P', 'Period_list', counts=12).create_col() #横向增值放在多列
3. pd.pivot_table() (行转列)
这个东西太方便了,和同事都赞不绝口。仿佛就是再用excel的 pivot。自由组合想要的键值,给予最直观的展示。
详细参数解释:pd.pivot_table()
简单说是行列转换,或者我们想看到例如某个城市,某个年份的某数据时,会用到,即多个constraint限制同一个value。
举个例子看就清楚了:
>>>df_2 = pd.pivot_table(df_1, index='City', columns='Time', values='Birth_Rate')
###city固定在左边,Time作为横向的时间轴,中间的值为value,表示某个城市,某一个时间的出生率。
>>>df_2 = pd.pivot_table(df_1, index=['City','District'], columns=['Time','Time2'], values=['Birth_Rate','Death_Rate'])
###当然我们的标准也可以变得更多维度,将参数替换成list也可。
4. pd.melt() (列转行)
刚才提到的pivot_table是行转列,而melt()则是列转行。
详细参数解释:pd.melt()
举个例子来看:
[In]:A B C
1 2 3
4 5 6
>>>df_2=pd.melt(df_1, id_vars='A', value_vars='C', var_name='C_name', value_name='Value_name')
[Out]:A C_name Value_name
1 C 3
4 C 6
5. groupby聚类算法
groupby一般可以用来按照特性聚合某一类数据。官方给的解释很晦涩,
详细解释:groupby()
其实简单理解,就是filter。但是和filter不太一样,filter是单一的一个步骤,而groupby更像一个放大镜,我的目的是通过镜子看到更细致的东西,而不只是打开放大镜那么简单。这样groupby就可以和你任何想进行的后续操作结合。先来看一个简单的filter 和后续操作:
##先筛选这一类数据
df_2=df_1[df_1['A']==20]
##再做计算
df_2['C']=df_2['B'].apply(lambda x: x.mean())
"""
但是如果你的数据有数以千万条,或者你要筛选的条件不只一个,比如可以写成如下:
"""
df_base_input_1=df_base_input[(df_base_input['Retail_or_Non_Retail']=='RETAIL')
&(~df_base_input['LE'].isin(['740','712','714','738']))
&(df_base_input['DeprnAccount'].isin(['683000.0000','684000.0000']))
&(df_base_input['Cost']!=0)]
"""
此时,速度就会稍微慢一些,因为你把一步可以做完的是拆成多步,且还同时占用了更多的空间。除非筛选这一步需要保存,否则用groupby更佳。
"""
##比如以下操作,同样是求平均数,一步到位。
>>>df_temp['SUMMPANY']=df_sum.groupby(['ENTITY_CODE','PERIOD_TIME'])['Actual_area'].mean()
##或者如此:
>>>df_temp['SUMMPANY']=df_sum.groupby(['ENTITY_CODE','PERIOD_TIME']).apply(lambda x: x['ACTUAL_AREA'].sum())
6. mapping小技巧
传统的mapping方法,可以合并多表,在进行多列操作,不过这样占用空间很大。可以用到的合并方式很多,比如:
pd.merge()
pd.concat()
join()
append()
但是,这里说的mapping小技巧,是自己发现的,有不对的地方,欢迎大神指正。
>>>df_map=df_1[df_1['A'].isin['dog','cat','mat','frog']].groupby('age')['B'].mean()
"""
这个df_map便是一个小的查询表单:
>age B
>5岁 2.334
>6岁 3.456
>7岁 5.662
>8岁 7.028
之后我们在引用的时候可以直接抓取需要的年龄对应的那个平均数.
>此处的df_map[str(x['Age'])]便是我们要抓取的用于计算的平均数。
>>>df_1['calulation']=df_1.apply(lambda x: x['BB']/df_map[str(x['Age'])],axis=1)
7. numpy.vectorize()
这个矢量化也很好用,专门处理数组运算,有点类似于pandas里的apply lambda。
详细解释:numpy.vectorize()
简单的用法就是把编辑好的function 放在里面,再去运行。很多时候一个dataframe里运算负荷太大,如果只抽取某一数组更为方便。如[a,b,c,d,e]与A的运算。
def my_function(a, b):
count=0
for i in range(0, 5):
if count<3:
a+=i
else:
b+=i
count+=1
>>>np.vectorize(my_function)([1,2,3,4],5)
这个count也是很神奇,leetcode初级题中经常出现。
未完待续......如有错误,欢迎大神指正,一起进步学习~~~