【问题标题】:Reasonable way to have different versions of None?拥有不同版本的无的合理方法?
【发布时间】:2019-08-05 14:09:20
【问题描述】:

在 Python3 中工作。

假设您有一百万只甲虫,您的任务是对它们的斑点大小进行分类。所以你会做一个表格,每行是一只甲虫,行中的数字代表斑点的大小;

 [[.3, 1.2, 0.5],
  [.6, .7],
  [1.4, .9, .5, .7],
  [.2, .3, .1, .7, .1]]

此外,您决定将其存储在一个 numpy 数组中,并用 None 填充列表(numpy 会将其转换为 np.nan)。

 [[.3, 1.2, 0.5, None, None],
  [.6, .7, None, None, None],
  [1.4, .9, .5, .7, None],
  [.2, .3, .1, .7, .1]]

但是有一个问题,表示为 None 的值可以是 None 出于 3 个原因之一;

  1. 甲虫没有很多斑点;这个数量不存在。

  2. 甲虫不会静止不动,您也无法测量位置。

  3. 你还没来得及测量那只甲虫,所以没有赋值。

我的问题实际上并不涉及甲虫,但原理是一样的。 我想要 3 个不同的 None 值,所以我可以保持这些缺失值的原因不同。我目前的解决方案是使用一个大到物理上不可能的值,但这不是一个非常安全的解决方案。

假设您不能使用负数 - 实际上我测量的数量可能是负数。

数据量大,读取速度很重要。

编辑; cmets 正确地指出,说速度很重要,但不说什么操作有点毫无意义。主成分分析可能将用于变量去相关,用于聚类算法的欧几里德距离平方计算(但该变量中的数据稀疏)可能进行一些插值。最终是一个递归神经网络,但这将来自一个库,所以我只需要将数据转换为输入形式。所以也许没有什么比线性代数更糟糕的了,如果我小心的话,它应该都可以放入 RAM 中。

什么是好的策略?

【问题讨论】:

  • 如果你想要不同的值,不要使用None。使用其他类型。这不是None 的用途。
  • 如果您使用 numpy 数组,为什么使用 np.nannp.inf-np.inf 不是解决方案?
  • 也许添加一个包含点数的列?这显示了有多少其他列具有有效值,并且对于其他情况可能存在特殊值,例如 -1 和 -2。
  • Masked Arrays 对您的问题有意义吗?它们是表示无效/缺失数据的一种方式。我不知道会有多大的性能影响。
  • Create an Enum 的值是None 的不同风格。

标签: python python-3.x numpy nonetype


【解决方案1】:

最简单的方法是使用字符串:'not counted'、'unknown' 和 'N/A'。但是,如果您想在 numpy 中快速处理,具有混合数字/对象的数组不是您的朋友。

我的建议是添加几个与您的数据形状相同的数组,由 0 和 1 组成。因此数组 missing = 1,其中缺少点,否则为 0,依此类推,与数组 not_measured 相同等。

然后您可以在任何地方使用 NaN,然后​​使用 np.where(missing == 1) 等屏蔽您的数据,以便轻松找到您需要的特定 NaN。

【讨论】:

    【解决方案2】:

    如果你只想要一个没有任何已知值的对象,也不是None,只需创建一个新对象:

    NOT_APPLICABLE = object()
    NOT_MEASURED = object()
    UNKNOWN = object()
    

    现在您可以像使用 None 一样使用这些值:

    [1.4, .9, .5, .7, UNKNOWN]
    
    ...
    
    if value is UNKNOWN:
        # do something
    

    等等

    如果您需要一个可以表示为 float 的值(例如,在 numpy 数组中),您可以使用尾数中编码的“额外”数据创建一个 NaN 值。但是,这样做可能并不安全,因为无法保证这些位通过对值的各种操作得到保留。

    【讨论】:

    • 但我认为这会让 Numpy 感到困惑。它可以将 None 转换为 NaN 并将其保留为浮点数组,但如果那里有随机 Python 对象将无法工作。
    • 我真的不知道numpy 在这种情况下做了什么。我相信它可以在数组中存储任意对象,它只是切换到不同类型的数组。但我不知道你会怎么做这种事情。
    • OP 确实说它是一个 numpy 数组(应该可能已经标记了它)。我认为这里的性能也是一个重要问题,这就是为什么他/她不简单地使用字符串(在 numpy 速度方面与对象相同)
    • @DanielPryden 是的,对不起,我把它埋了一点。用 None 填充的原因是为了转换为 numpy 数组。这样做是为了提高各种后续操作的速度。不过,这似乎是一个很好的解决方案,我可能必须弄清楚如何以与 numpy 兼容的方式来做到这一点。
    • 这将需要一个 dtype=object 数组,这将消除 numpy 的速度优势。
    【解决方案3】:

    这是一个避免减速带的解决方案(免责声明:HACK!),例如 object dtype 或单独的掩码:

    nan 的 fp 表示周围似乎有相当多的“死区”:

    >>> nan_as_int = np.array(np.nan).view(int)[()]
    >>> nan_as_int
    9221120237041090560
    
    >>> custom_nan = np.arange(nan_as_int, nan_as_int+10).view(float)
    >>> custom_nan
    array([nan, nan, nan, nan, nan, nan, nan, nan, nan, nan])
    

    我们创建了十个不同的nans。请注意,这与使用float("nan") 创建多个实例不同。这些实例都将映射到 numpy 中的相同值,因此一旦放入非对象数组中就无法区分。

    尽管我们的十个nans 具有不同的表示形式,但在浮点级别上它们很难区分(因为根据定义nan != nan,即使是唯一的nan)。所以我们需要一个小帮手:

    >>> def which_nan(a):
    ...     some_nan = np.isnan(a)
    ...     return np.where(some_nan, np.subtract(a.view(int), nan_as_int, where=some_nan), -1)
    

    例子:

    >>> exmpl = np.array([0.1, 1.2, custom_nan[3], custom_nan[0]])
    >>> exmpl
    array([0.1, 1.2, nan, nan])
    >>> which_nan(exmpl)
    array([-1, -1,  3,  0], dtype=int64)
    

    也许令人惊讶的是,这似乎至少在一些基本的 numpy 操作中仍然存在:

    >>> which_nan(np.sin(exmpl))
    array([-1, -1,  3,  0], dtype=int64)
    

    【讨论】:

      【解决方案4】:

      在下面问题的评论中,我问为什么不使用np.inf-np.infnp.nan,作者回复说这是他需要的。

      所以我添加帖子,因为人们更经常查看回复,而不是 cmets。

      【讨论】:

      • 虽然我认为我在一般情况下是一个更好的答案,但对于这种特定情况,这可能更好,+1
      • 我不确定。作者写道“数据很大,读取速度很重要”。所以添加 svereal 0,1 数组不是一个好主意。我瘦加一个不同标记的数组(1,2,3,...)比几个数组好。
      • 是的,正如我所说,您的解决方案更适合这种特定情况,但在某些情况下,np.inf 也可能是实际值,那么您需要另一种解决方案
      • 这为我的需求提供了足够的灵活性,并且它可能不会与未来的实现发生冲突。对于需要更多版本的人来说,@PaulPanzer 解决方案可能是解决方案,但在我的无知中,我可能会说它不安全。
      【解决方案5】:

      建议为每个案例创建三个不同的 object 实例。

      由于您希望这些对象具有NaN 的属性,您可以尝试创建三个不同的NaN 实例。

      NOT_APPLICABLE = float("nan")
      NOT_MEASURED = float("nan")
      UNKNOWN = float("nan")
      

      这是一个黑客的极限,所以使用风险自负,但我不相信任何 Python 实现会优化 NaN 以始终重用相同的对象。尽管如此,您仍然可以在运行前添加一个标记条件来检查它。

      if NOT_APPLICABLE is NOT_MEASURED or NOT_MEASURED is UNKNOWN or UNKNOWN is NOT_APPLICABLE :
          raise ValueError # or try something else
      

      如果可行,这样做的好处是允许您比较 NaN id 以检查其含义。

      row = [1.0, 2.4, UNKNOWN]
      
      ...
      
      if value is UNKNOWN:
          ...
      

      同时,它保留了 numpy 可能对其数组进行的任何优化。

      披露:这是一个 hacky 建议,我很想听听其他人的意见。

      【讨论】:

      • 接受这个,因为哨兵改进了很多。
      猜你喜欢
      • 1970-01-01
      • 2012-09-27
      • 1970-01-01
      • 2019-10-12
      • 2018-12-29
      • 1970-01-01
      • 2020-04-23
      • 2018-12-23
      • 1970-01-01
      相关资源
      最近更新 更多