使用python 进行表格数据的纵表和横表的互转

使用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)






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风暴之零

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值