将 Pandas DataFrame 转换为 3D numpy 数组的“枢轴”操作

6

给定以下结构的 DataFrame:

Date     | Site  | Measurement Type | Value
-----------------------------------------------
1/1/2020 | A     | Temperature      | 32.3
1/2/2020 | B     | Humidity         | 70%

我想创建一个3D“数据透视表”,其中第一维表示站点,第二维表示日期,第三维表示测量类型,每个元素存储值。

例如,如果我有5个站点在一周内每天测量温度和湿度,期望的输出将是一个形状为(5,7,2)的数组。

Pandas似乎仅支持创建2D数据透视表,但是我满意只将未标记的3D numpy数组作为输出。想知道是否存在现成的简便方法,在我自己实现之前先了解一下。


5
这听起来像是使用MultiIndex进行基本的数据透视。因此,您发布期望输出非常重要,因为您可以用多种方式解决这个问题。 - Erfan
@Erfan 请查看编辑-是否澄清了所需的输出结构? - LoLa
@LoLa 如果我理解正确的话,他们指的是一个实际的数据框,就像预期输出的样子。 - anky
1
在我看来,你正在把这件事情搞得比它本来应该的更加困难。3D数组很难阅读。Pandas通过MultiIndex解决了这个问题,你可以使用它来实现多维度。尝试一下这段代码:df.pivot_table(index='Date', columns=['Site', 'Measurement Type'], values='Value', aggfunc=lambda x: x) - Erfan
1
@Erfan 我需要3D结构,因为我正在将数据转换为Tensorflow中的RNN训练。请参见此教程中所需结构的图表。 我同意,为了可读性,MultiIndex更优越。 - LoLa
显示剩余2条评论
3个回答

4

你可以使用 df.pivot_table 来执行。我在你的样本数据中增加了一行,包含了两个测量类型(Measurement Type)。如果有缺失值,会用np.nan表示。

sample `df`

       Date Site Measurement_Type Value
0  1/1/2020    A      Temperature  32.3
1  1/1/2020    A         Humidity   60%
2  1/2/2020    B         Humidity   70%

尝试以下方法:
iix = pd.MultiIndex.from_product([np.unique(df.Date), np.unique(df.Measurement_Type)])
df_pivot = (df.pivot_table('Value', 'Site', ['Date', 'Measurement_Type'], aggfunc='first')
              .reindex(iix, axis=1))
arr = np.array(df_pivot.groupby(level=0, axis=1).agg(lambda x: [*x.values])
                       .to_numpy().tolist())

print(arr)

Out[1447]:
array([[['60%', '32.3'],
        [nan, nan]],

       [[nan, nan],
        ['70%', nan]]], dtype=object)

方法2:使用不同的列和numpy的reshape函数,使用pivot_table进行处理
iix_n = pd.MultiIndex.from_product([np.unique(df.Site), np.unique(df.Date)])
arr = (df.pivot_table('Value', ['Site', 'Date'], 'Measurement_Type', aggfunc='first')
         .reindex(iix_n).to_numpy()
         .reshape(df.Site.nunique(),df.Date.nunique(),-1))

Out[1501]:
array([[['60%', '32.3'],
        [nan, nan]],

       [[nan, nan],
        ['70%', nan]]], dtype=object)

这正是我所需要的,虽然我希望有一种更简单的方法。谢谢。我喜欢它如何处理缺失的数据。 - LoLa
本地情况下,“pivot_table”不支持3D数组输出,因此我们需要进行更多的处理才能实现它。很高兴我能帮到你 :) - Andy L.
1
@LoLa:我使用pivot_tablereshape添加了另一种方法。它比第一个方法更简单。如果你想要一个更简单的解决方案,请尝试一下。请注意不同的透视列和不同的reindex - Andy L.
1
整洁,我喜欢这个。生成的数组将在每个轴上进行排序(基于原始索引),对吗? - LoLa
是的,它是的 :) - Andy L.

1

我写了一个小脚本来衡量不同 @Andy L. 方法的性能。

第二种方法似乎稍微快一些:

def pivot_table_3d_1(df, col1, col2, col3, value, aggfunc='first') :
    iix = pd.MultiIndex.from_product([np.unique(df[col2]), np.unique(df[col3])])
    df_pivot = (df.pivot_table(value, col1, [col2,col3], aggfunc=aggfunc)
                  .reindex(iix, axis=1))
    arr = np.array(df_pivot.groupby(level=0, axis=1).agg(lambda x: [*x.values])
                           .to_numpy().tolist())

    return arr

def pivot_table_3d_2(df, col1, col2, col3, value, aggfunc='first') :
    iix_n = pd.MultiIndex.from_product([np.unique(df[col1]), np.unique(df[col2])])
    arr = (df.pivot_table(value, [col1, col2], col3, aggfunc=aggfunc)
         .reindex(iix_n).to_numpy()
         .reshape(df[col1].nunique(),df[col2].nunique(),-1))

    return arr

## TESTING
N1 = 100
N2 = 200
N3 = 300
df = pd.DataFrame({'col1': np.random.randint(0, N1, N1*N2*N3),
                   'col2': np.random.randint(0, N2, N1*N2*N3),
                   'col3': np.random.randint(0, N3, N1*N2*N3),
                   'value': np.random.normal(0,1,N1*N2*N3)})

%timeit pivot_table_3d(df, col1='col1', col2='col2', col3='col3', value='value')
# 10.2 s ± 39.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit pivot_table_3d_2(df, col1='col1', col2='col2', col3='col3', value='value')
#9.47 s ± 108 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

感谢使用 timeit :) +1 - Andy L.

1
我认为你需要的是类似于 "panel" 的东西。你也可以使用一个三维的 numpy 数组。例如,使用 panel:
p_dim = {}

# desired columns
cols = ['Site', 'Measurement Type']

for date in df.Date:
    sub_df = df[df.Date.isin([date])].reset_index(drop=True)
    p_dim[date] = sub_df[[c for c in sub_df.columns if c in cols]]

panel = pd.Panel(p_dim)

现在,您可以使用panel['1/1/2020']访问与日期相关的各种数据,假设您的Date列是str类型。要查看所有可用的键,您可以使用panel.keys()

1
我正在尝试避免使用panel,因为根据pandas文档的说法,它已经被弃用了:https://pandas.pydata.org/pandas-docs/version/0.23.4/generated/pandas.Panel.html - LoLa
你的解决方案很容易适应我所需的,如果确实没有等价的 pivot_table() 可用,我可能会做类似的事情。 - LoLa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接