将Pandas Dataframe或Panel转换为3D numpy数组
设置:
pdf = pd.DataFrame(np.random.rand(4,5), columns = list('abcde'))
pdf['a'][2:]=pdf['a'][0]
pdf['a'][:2]=pdf['a'][1]
pdf.set_index(['a','b'])
输出:
c d e
a b
0.439502 0.115087 0.832546 0.760513 0.776555
0.609107 0.247642 0.031650 0.727773
0.995370 0.299640 0.053523 0.565753 0.857235
0.392132 0.832560 0.774653 0.213692
每组数据都是根据索引 ID a
来分组的,而 b
则表示与 a
相关的时间索引。有没有办法让 pandas 生成一个 numpy 的三维数组,以反映 a
的分组情况?目前它读取的数据是二维的,所以 pdf.shape
输出的是 (4, 5)
。我希望得到的数组是这样的形式:
array([[[-1.38655912, -0.90145951, -0.95106951, 0.76570984],
[-0.21004144, -2.66498267, -0.29255182, 1.43411576],
[-0.21004144, -2.66498267, -0.29255182, 1.43411576]],
[[ 0.0768149 , -0.7566995 , -2.57770951, 0.70834656],
[-0.99097395, -0.81592084, -1.21075386, 0.12361382]]])
有没有 pandas 自带的方法可以做到这一点?请注意,实际数据中每个 a
分组的行数是变化的,所以我不能简单地转置或重塑 pdf.values
。如果没有自带的方法,针对成千上万行和几百列的数据,构建这些数组的最佳方法是什么?
4 个回答
以前我们用过的 .as_matrix
和 .values()
这些方法已经不再推荐使用了。现在,pandas 的文档建议我们使用 .to_numpy()
这个方法。
'警告: 我们建议使用 DataFrame.to_numpy() 代替。'
as_matrix
这个方法已经不再推荐使用了。这里我们假设第一个关键字是 a
,那么 a
中的组可能有不同的长度,这个方法可以解决所有相关的问题。
import pandas as pd
import numpy as np
from typing import List
def make_cube(df: pd.DataFrame, idx_cols: List[str]) -> np.ndarray:
"""Make an array cube from a Dataframe
Args:
df: Dataframe
idx_cols: columns defining the dimensions of the cube
Returns:
multi-dimensional array
"""
assert len(set(idx_cols) & set(df.columns)) == len(idx_cols), 'idx_cols must be subset of columns'
df = df.set_index(keys=idx_cols) # don't overwrite a parameter, thus copy!
idx_dims = [len(level) + 1 for level in df.index.levels]
idx_dims.append(len(df.columns))
cube = np.empty(idx_dims)
cube.fill(np.nan)
cube[tuple(np.array(df.index.to_list()).T)] = df.values
return cube
测试:
pdf = pd.DataFrame(np.random.rand(4,5), columns = list('abcde'))
pdf['a'][2:]=pdf['a'][0]
pdf['a'][:2]=pdf['a'][1]
# a, b must be integer
pdf1 = (pdf.assign(a=lambda df: df.groupby(['a']).ngroup())
.assign(b=lambda df: df.groupby(['a'])['b'].cumcount())
)
make_cube(pdf1, ['a', 'b']).shape
给出: (2, 2, 3)
pdf = pd.DataFrame(np.random.rand(5,5), columns = list('abcde'))
pdf['a'][2:]=pdf['a'][0]
pdf['a'][:2]=pdf['a'][1]
pdf1 = (pdf.assign(a=lambda df: df.groupby(['a']).ngroup())
.assign(b=lambda df: df.groupby(['a'])['b'].cumcount())
)
make_cube(pdf1, ['a', 'b']).shape
给出 s (2, 3, 3) 。
我刚遇到一个非常相似的问题,解决方法是这样的:
a3d = np.array(list(pdf.groupby('a').apply(pd.DataFrame.as_matrix)))
输出结果:
array([[[ 0.47780308, 0.93422319, 0.00526572, 0.41645868, 0.82089215],
[ 0.47780308, 0.15372096, 0.20948369, 0.76354447, 0.27743855]],
[[ 0.75146799, 0.39133973, 0.25182206, 0.78088926, 0.30276705],
[ 0.75146799, 0.42182369, 0.01166461, 0.00936464, 0.53208731]]])
确认它是三维的,a3d.shape 显示为 (2, 2, 5)。
最后,如果想把新创建的维度放在最后一位(而不是第一位),可以使用:
a3d = np.dstack(list(pdf.groupby('a').apply(pd.DataFrame.as_matrix)))
这样就变成了 (2, 5, 2) 的形状
对于数据不规则的情况(正如评论中提到的),如果你想继续使用numpy的解决方案,可以尝试以下方法。但要注意,处理缺失数据的最佳策略因情况而异。在这个例子中,我们简单地为缺失的行添加了零。
不规则形状的示例设置:
pdf = pd.DataFrame(np.random.rand(5,5), columns = list('abcde'))
pdf['a'][2:]=pdf['a'][0]
pdf['a'][:2]=pdf['a'][1]
pdf.set_index(['a','b'])
数据框:
c d e
a b
0.460013 0.577535 0.299304 0.617103 0.378887
0.167907 0.244972 0.615077 0.311497
0.318823 0.640575 0.768187 0.652760 0.822311
0.424744 0.958405 0.659617 0.998765
0.077048 0.407182 0.758903 0.273737
一种可能的解决方案:
n_max = pdf.groupby('a').size().max()
a3d = np.array(list(pdf.groupby('a').apply(pd.DataFrame.as_matrix)
.apply(lambda x: np.pad(x, ((0, n_max-len(x)), (0, 0)), 'constant'))))
a3d.shape 显示为 (2, 3, 5)
panel.values
这段代码会直接返回一个numpy数组。这个数组的类型会是能接受的最高级别,因为所有的数据都被压缩成一个三维的numpy数组。这个数组是一个全新的数组,而不是pandas数据的一个视图(无论它的数据类型是什么)。