使用python进行表格数据的行列互转有一些现成的API,但是在细节上往往还待进一步优化,本文对常用的纵表和横表的互转进行了进一步的封装,提升了易用性。
纵表在聚合、分组统计方面有优势
横表在展示、去重(key列已经去重了)方面有优势。
纵表转横表
1、使用pivot_table()和折叠函数方法
场景
1、优点:可以选择横向排列的列,通过columns参数。
2、对于索引和列不能唯一确定值的数据,该函数只能选择第一个数,因为pd.pivot_table函数的参数aggfunc=“first”。
例如下面的表是不能用该方法的
3、该方法性能相对较差,数万行的记录是秒级
用法
pandas主要使用pivot_table()函数,通过透视表实现纵表转横表,同时还有pivot有类似功能(该函数不能处理重复数据)。
如果直接使用pivot_table()函数会导致每个值都作为一列。会导致列变得非常长。本文通过折叠列,让修改后得结果更容易阅读。
如果原始表如下:
直接透视后得表如下:
折叠后的表,减少了列,缩短了表得宽度
1、pivot_table()函数介绍
pandas.pivot_table(data, values=None, index=None, columns=None, aggfunc='mean', fill_value=None, margins=False, dropna=True, margins_name='All')
其中各参数含义如下:
data: 需要进行透视的 DataFrame;
values: 需要汇总的数据;
index: 用于分组的列(即行标)名或其他列级别;
columns: 在结果DataFrame中放置在列上的列级别名称;
aggfunc: 对取值进行聚合的函数,默认为 mean()。
fill_value:替换 NaN 的值;
margins: 是否增加行/列的小计和汇总;
dropna: 是否删除包含缺失值的行或列;
margins_name: 小计/汇总列的名称。
2、修改排列方式,对列进行折叠
from pandasrw import load,dump
import pandas as pd
import numpy as np
#去掉一维数组中的np.nan
def drop_nan(row):
arr_1d = row[~np.isnan(row)]
return arr_1d
# 调整元素顺序对原来二维数组中随机分步的nan移到最右边
# 1、预先分配二维数组A 2、删除输入数组的每一行的nan 3、将删除nan后的一维数组赋值给A的每一行
def nan_to_right(arr_2d,n):
s=arr_2d.shape
m=s[0]
A= np.full((m, n), np.nan, dtype=object, order='C')
i=0
for row in arr_2d:
row_1d=row[~np.isnan(row)]
k=row_1d.shape[0]
A[i,0:k]=row_1d
i+=1
return A
#纵表转横表
#1、通过数据透视表将纵表转为横表,aggfunc="first"取value的原始值而不是做均值、最大、最小等统计。2、调整元素顺序对原来二维数组中随机分步的nan移到最右边 3、将数据改为所需要的格式
def long_to_wide_pivot(df,index,columns,values,n):
df_wide= pd.pivot_table(df, index=index, columns=columns, values=values,aggfunc="first")
np_wide=df_wide.values
np_wide_res=nan_to_right(np_wide,n)
#将df中的index合并入结果
idx = df_wide.index
np_idx = idx.to_numpy().reshape(-1,1)
wide=np.concatenate((np_idx,np_wide_res),axis=1)
#转为dataframe
df_wide_res=pd.DataFrame(wide)
df_wide_res = df_wide_res.dropna(axis=1, how='all')
return df_wide_res
if __name__ == '__main__':
path1=r"xx"
path2 = r"xx"
df=load(path1)
df_base=df[["col1","col2","col3","col4"]]
df_b=df[["col1","col2"]]
result = long_to_wide_pivot(df_b, index="col1", columns="col2", values="col3",n=100)
dump(result,path2)
二、 使用numpy 进行转换
对要转化的数组A,首先选择分组列并将分组列移到第一列,然后按分组列对数组分组为[A1,A2…];
对与每一个分组后的数组A1…,将它除去第一列(分组列)后的数组,用reshape(1,-1)成横向排列(数据内存是C布局的数组,展开成一维后正好是按行横排的);
对于展开后的一位数组,赋值给提前分配好的结果数组。
1、单进程版本
使用场景
1、可以解决重复数据问题。对于索引和列不能唯一确定值的数据,该函数可以排列成行。
2、性能较高,10万行记录仅需0.5秒以内
3、本函数本身不能选择横向排列的列,只能提前通过
4、数组的数据内存布局是C的不能是F的(默认是C的)
用法
1、注意赋值数组还需要设计
计算结果矩阵的大小,高度为len(groups),长度为(m-1)*n,因为第一列不会改为横排所以为m-1,但是第一列需要写入分组名称,所以+1
result = np.empty((len(groups), (m - 1) * n + 1), dtype=object)
2、切片后赋值后的维度需要仔细考虑
result[i:i + 1, 1:p] = arr1d
不能用result[i] 会导致是二维的
3、与透视表的方法的性能需要对比
4、以下可以改为多进程
import numpy as np
import pandas as pd
from pandasrw import load,dump,view
from collections import Counter
def move_column_to_first(arr,col_index):
"""
将数组的指定列移动到第一列的位置
参数:
arr:numpy数组,要操作的数组
col_index:int,要移动的列的索引
返回值:
numpy数组,移动列后的数组
"""
columns = np.arange(arr.shape[1])
columns[0], columns[col_index] = columns[col_index], columns[0]
return arr[:, columns]
def long_to_width(arr,col_index):
arr2d = move_column_to_first(arr,col_index)
m=arr2d.shape[1]
#统计索引0列的每个元素出现的次数和次数中最大的值
counter = Counter(arr2d[:,0])
#出现最多的字段出现的次数
n=counter.most_common(1)[0][1]
#不重复出现的字段
groups = np.unique(arr2d[:, 0])
# 计算结果矩阵的大小,高度为len(groups),长度为(m-1)*n,因为第一列不会改为横排所以为m-1,但是第一列需要写入分组名称,所以+1
result = np.empty((len(groups), (m-1)*n+1), dtype=object)
# 对每个分组形成的数组进行转换
for i, group in enumerate(groups):
group_data = arr2d[arr2d[:, 0] == group]
result[i, 0] = group
arr1d = group_data[:, 1:].reshape(1, -1)
p =arr1d.shape[1]+1
print(group,p)
try:
result[i:i + 1, 1:p] = arr1d
except:
result[i:i + 1, 1:p+1] = arr1d
return result
if __name__ == '__main__':
path=r""
path1 = r""
df=load(path,engine="pandas")
arr=df.values
np1=long_to_width(arr,2)
df1=pd.DataFrame(np1)
df1=df1.iloc[:,0:100]
dump(df1,path1)
二、多进程版本
通过对分组列,将np数组分为多个块,每个块一个进程。
注意: 由于多进程开销和结果合并开销,多进程版本在数据小于100万行的时候反而会比单进程的慢。
from joblib import Parallel, delayed
import numpy as np
import pandas as pd
from pandasrw import load, dump, view
from collections import Counter
def move_column_to_first(arr, col_index):
"""
将数组的指定列移动到第一列的位置
参数:
arr:numpy数组,要操作的数组
col_index:int,要移动的列的索引
返回值:
numpy数组,移动列后的数组
"""
columns = np.arange(arr.shape[1])
columns[0], columns[col_index] = columns[col_index], columns[0]
return arr[:, columns]
def long_to_width(arr, col_index):
arr2d = move_column_to_first(arr, col_index)
m = arr2d.shape[1]
# 统计索引0列的每个元素出现的次数和次数中最大的值
counter = Counter(arr2d[:, 0])
# 出现最多的字段出现的次数
n = counter.most_common(1)[0][1]
# 不重复出现的字段
groups = np.unique(arr2d[:, 0])
# 计算结果矩阵的大小,高度为len(groups),长度为(m-1)*n,因为第一列不会改为横排所以为m-1,但是第一列需要写入分组名称,所以+1
result = np.empty((len(groups), (m - 1) * n + 1), dtype=object)
# 对每个分组形成的数组进行转换
for i, group in enumerate(groups):
group_data = arr2d[arr2d[:, 0] == group]
result[i, 0] = group
arr1d = group_data[:, 1:].reshape(1, -1)
p = arr1d.shape[1] + 1
result[i, 1:p] = arr1d
return result
def long_to_width_no_move(arr2d):
m = arr2d.shape[1]
# 统计索引0列的每个元素出现的次数和次数中最大的值
counter = Counter(arr2d[:, 0])
# 出现最多的字段出现的次数
n = counter.most_common(1)[0][1]
# 不重复出现的字段
groups = np.unique(arr2d[:, 0])
# 计算结果矩阵的大小,高度为len(groups),长度为(m-1)*n,因为第一列不会改为横排所以为m-1,但是第一列需要写入分组名称,所以+1
result = np.empty((len(groups), (m - 1) * n + 1), dtype=object)
# 对每个分组形成的数组进行转换
for i, group in enumerate(groups):
group_data = arr2d[arr2d[:, 0] == group]
result[i, 0] = group
arr1d = group_data[:, 1:].reshape(1, -1)
p = arr1d.shape[1] + 1
result[i, 1:p] = arr1d
return result
def long_to_width_pal(arr):
arr2d = move_column_to_first(arr, 2)
groups = np.unique(arr2d[:, 0])
split_groups = np.array_split(groups,2)
split_arr = [arr2d[np.where(np.isin(arr2d[:, 0], np.array(group)))] for group in split_groups]
res = Parallel(n_jobs=3)([delayed(long_to_width_no_move)(x) for x in split_arr ] )
max_cols = max(r.shape[1] for r in res)
ls=[]
for r in res:
if r.shape[1] < max_cols:
b_arr=np.empty((r.shape[0], max_cols - r.shape[1]), dtype=object)
r = np.hstack([r, b_arr])
ls.append(r)
data=np.concatenate(ls)
df=pd.DataFrame(data)
return df
if __name__ == '__main__':
path=r""
path1 = r""
df=load(path,engine="pandas")
arr=df.values
df=long_to_width_pal(arr)
dump(df,path1)