【问题标题】:Creating a Pandas DataFrame from a NumPy masked array?从 NumPy 掩码数组创建 Pandas DataFrame?
【发布时间】:2021-07-26 15:00:53
【问题描述】:

我正在尝试从 NumPy 掩码数组创建 Pandas DataFrame,据我所知,这是受支持的操作。这是源数组的示例:

a = ma.array([(1, 2.2), (42, 5.5)],
             dtype=[('a',int),('b',float)],
             mask=[(True,False),(False,True)])

输出为:

masked_array(data=[(--, 2.2), (42, --)],
             mask=[( True, False), (False,  True)],
       fill_value=(999999, 1.e+20),
            dtype=[('a', '<i8'), ('b', '<f8')])

尝试用pd.DataFrame(a) 创建一个DataFrame 返回:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-40-a4c5236a3cd4> in <module>
----> 1 pd.DataFrame(a)

/usr/local/anaconda/lib/python3.8/site-packages/pandas/core/frame.py in __init__(self, data, index, columns, dtype, copy)
    636             # a masked array
    637             else:
--> 638                 data = sanitize_masked_array(data)
    639                 mgr = ndarray_to_mgr(
    640                     data,

/usr/local/anaconda/lib/python3.8/site-packages/pandas/core/construction.py in sanitize_masked_array(data)
    452     """
    453     mask = ma.getmaskarray(data)
--> 454     if mask.any():
    455         data, fill_value = maybe_upcast(data, copy=True)
    456         data.soften_mask()  # set hardmask False if it was True

/usr/local/anaconda/lib/python3.8/site-packages/numpy/core/_methods.py in _any(a, axis, dtype, out, keepdims, where)
     54     # Parsing keyword arguments is currently fairly slow, so avoid it for now
     55     if where is True:
---> 56         return umr_any(a, axis, dtype, out, keepdims)
     57     return umr_any(a, axis, dtype, out, keepdims, where=where)
     58 

TypeError: cannot perform reduce with flexible type

确实支持这个操作吗?目前使用 Pandas 1.3.3 和 NumPy 1.20.3。

更新

支持吗? 根据Pandas documentation here

或者,您可以将 numpy.MaskedArray 作为数据参数传递给 DataFrame 构造函数,其被屏蔽的条目将被视为缺失。

上面的代码是我问的问题“我会得到什么?”如果我将 NumPy 掩码数组传递给 Pandas,但这是我希望的结果。以上是我能想到的最简单的例子。

我确实希望 Pandas 中的每个系列/列都是单一类型。

更新 2

任何对此感兴趣的人都应该看到这个熊猫GitHub issue;值得注意的是,Pandas 已“弃用对 MaskedRecords 的支持”。

【问题讨论】:

  • 通常,在对子类“一无所知”的代码中使用掩码数组将导致仅使用数组的data 部分,而忽略掩码。使用掩码数组的np.ma 函数或方法。 “据我所知是受支持的操作”的来源是什么?
  • 这样的问题应该包括预期的结果。这样可以减少歧义并减少后续问题或修改的需要。

标签: pandas numpy missing-data


【解决方案1】:

如果数组具有简单的 dtype,则数据框创建工作(如文档所述):

In [320]: a = np.ma.array([(1, 2.2), (42, 5.5)],
     ...:    mask=[(True,False),(False,True)])
In [321]: a
Out[321]: 
masked_array(
  data=[[--, 2.2],
        [42.0, --]],
  mask=[[ True, False],
        [False,  True]],
  fill_value=1e+20)
In [322]: import pandas as pd
In [323]: pd.DataFrame(a)
Out[323]: 
      0    1
0   NaN  2.2
1  42.0  NaN

这个a是(2,2),结果是2行2列

使用复合dtype,形状为1d:

In [326]: a = np.ma.array([(1, 2.2), (42, 5.5)],
     ...:              dtype=[('a',int),('b',float)],
     ...:              mask=[(True,False),(False,True)])
In [327]: a.shape
Out[327]: (2,)

错误是对掩码进行测试的结果。 flexible type 指您的化合物dtype

In [330]: a.mask.any()
Traceback (most recent call last):
  File "<ipython-input-330-8dc32ee3f59d>", line 1, in <module>
    a.mask.any()
  File "/usr/local/lib/python3.8/dist-packages/numpy/core/_methods.py", line 57, in _any
    return umr_any(a, axis, dtype, out, keepdims)
TypeError: cannot perform reduce with flexible type

已记录的 pandas 功能显然不适用于结构化数组。如果不研究 pandas 代码,我目前无法准确说出它想要做什么,但很明显,代码编写时并未考虑到结构化数组。

未屏蔽的部分确实可以使用所需的列 dtypes:

In [332]: pd.DataFrame(a.data)
Out[332]: 
    a    b
0   1  2.2
1  42  5.5

使用默认的fill

In [344]: a.filled()
Out[344]: 
array([(999999, 2.2e+00), (    42, 1.0e+20)],
      dtype=[('a', '<i8'), ('b', '<f8')])
In [345]: pd.DataFrame(a.filled())
Out[345]: 
        a             b
0  999999  2.200000e+00
1      42  1.000000e+20

我必须更多地查看ma docs/code,看看是否可以对这两个字段应用不同的填充。填充 nan 不适用于 int 字段。 numpy 没有 pandas' int none。我对 pandas 功能的了解还不够,无法知道生成的 dtype 是否仍然是 int,或者它已更改为 object。

无论如何,您正在通过这项任务突破np.mapandas 的界限。

编辑

默认的fill_value是一个元组,每个字段一个:

In [350]: a.fill_value
Out[350]: (999999, 1.e+20)

所以我们可以用不同的方式填充字段,并以此为基础制作一个框架:

In [351]: a.filled((-1, np.nan))
Out[351]: array([(-1, 2.2), (42, nan)], dtype=[('a', '<i8'), ('b', '<f8')])
In [352]: pd.DataFrame(a.filled((-1, np.nan)))
Out[352]: 
    a    b
0  -1  2.2
1  42  NaN

看起来我可以使用 pandas dtype 及其关联的 fill_value 创建一个结构化数组:

In [363]: a = np.ma.array([(1, 2.2), (42, 5.5)],
     ...:              dtype=[('a',pd.Int64Dtype),('b',float)],
     ...:              mask=[(True,False),(False,True)],
                       fill_value=(pd.NA,np.nan))
In [364]: a
Out[364]: 
masked_array(data=[(--, 2.2), (42, --)],
             mask=[( True, False), (False,  True)],
       fill_value=(<NA>, nan),
            dtype=[('a', 'O'), ('b', '<f8')])

In [366]: pd.DataFrame(a.filled())
Out[366]: 
      a    b
0  <NA>  2.2
1    42  NaN

【讨论】:

  • 看起来是这样!谢谢你的澄清。 np.ma.MaskedArray.filled 确实允许字段/类型之间的不同填充。似乎一种解决方案是手动填充每一列,转换为DataFrame,然后根据 pandas-land 中的这些值根据需要屏蔽/分配NaN。一些额外的步骤,但可行。干杯。
  • 看起来我们可以使用 pandas dtypes 并在结构化数组中填充值 - 请参阅我的编辑。
  • 我不会想到在 NumPy 数组中使用 Pandas 类型 - 很高兴看到它有效。很好的解决方案,感谢您的帮助。
【解决方案2】:

问题是您希望得到什么? pandas 转换你的数据会很模糊。

如果要获取原始数据:

>>> pd.DataFrame(a.data)
    a    b
0   1  2.2
1  42  5.5

如果您想认为掩码值无效:

>>> pd.DataFrame(a.filled(np.nan))

但是,为此您应该在掩码数组中包含所有类型的浮点数

【讨论】:

  • 理想情况下,对于浮点类型,掩码值为 NaN,而对于 int 值,则使用 new(er) Int64 可为空的 Pandas 类型。我不想要一个未屏蔽的数组。
  • 在 pandas 中,列(系列)可以有自己的dtype。这不适用于 Series 的元素(除非您使用 object dtype)。
猜你喜欢
  • 1970-01-01
  • 2016-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-05
相关资源
最近更新 更多