【问题标题】:Assigning rank 2 numpy array to pandas DataFrame column behaves inconsistently将 rank 2 numpy 数组分配给 pandas DataFrame 列的行为不一致
【发布时间】:2019-02-04 09:55:11
【问题描述】:

我注意到分配给pandasDataFrame 列(使用.loc 索引器)的行为会有所不同,具体取决于DataFrame 中存在的其他列以及分配的确切形式。用三个例子DataFrames:

df1 = pandas.DataFrame({
    'col1': [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
})
#         col1
# 0  [1, 2, 3]
# 1  [4, 5, 6]
# 2  [7, 8, 9]
df2 = pandas.DataFrame({
    'col1': [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
    'col2': [[10, 20, 30], [40, 50, 60], [70, 80, 90]]
})
#         col1          col2
# 0  [1, 2, 3]  [10, 20, 30]
# 1  [4, 5, 6]  [40, 50, 60]
# 2  [7, 8, 9]  [70, 80, 90]
df3 = pandas.DataFrame({
    'col1': [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
    'col2': [1, 2, 3]
})
#         col1  col2
# 0  [1, 2, 3]     1
# 1  [4, 5, 6]     2
# 2  [7, 8, 9]     3
x = numpy.array([[111, 222, 333],
                 [444, 555, 666],
                 [777, 888, 999]])

我发现了以下内容:

  1. df1:

    1. df1.col1 = x

      结果:

      df1
      #    col1
      # 0   111
      # 1   444
      # 2   777
      
    2. df1.loc[:, 'col1'] = x

      结果:

      df1
      #    col1
      # 0   111
      # 1   444
      # 2   777
      
    3. df1.loc[0:2, 'col1'] = x

      结果:

      # […]
      # ValueError: could not broadcast input array from shape (3,3) into shape (3)
      
  2. df2:

    1. df2.col1 = x

      结果:

      df2
      #    col1          col2
      # 0   111  [10, 20, 30]
      # 1   444  [40, 50, 60]
      # 2   777  [70, 80, 90]
      
    2. df2.loc[:, 'col1'] = x

      结果:

      df2
      #    col1          col2
      # 0   111  [10, 20, 30]
      # 1   444  [40, 50, 60]
      # 2   777  [70, 80, 90]
      
    3. df2.loc[0:2, 'col1'] = x

      结果:

      # […]
      # ValueError: could not broadcast input array from shape (3,3) into shape (3)
      
  3. df3:

    1. df3.col1 = x

      结果:

      df3
      #    col1  col2
      # 0   111     1
      # 1   444     2
      # 2   777     3
      
    2. df3.loc[:, 'col1'] = x

      结果:

      # ValueError: Must have equal len keys and value when setting with an ndarray
      
    3. df3.loc[0:2, 'col1'] = x

      结果:

      # ValueError: Must have equal len keys and value when setting with an ndarray
      

因此,如果 DataFrame 中的其他列之一没有 dtype objectdf.loc 的行为似乎有所不同。

我的问题是:

  • 为什么其他列的存在会对这种分配产生影响?
  • 为什么不同版本的作业不等效?特别是,为什么在ValueError 不导致DataFrame 列被numpy 数组的第一列 的值填充的情况下的结果?李>

注意:我不想讨论以这种方式将列分配给numpy 数组是否有意义。我只想知道行为上的差异,以及这是否算作错误。

【问题讨论】:

  • 抱歉,我不打算深入探讨这个问题,但我会说,根据我的经验,pandas 并不总是表现良好,如果你将数组喜欢放在DataFrame.
  • @JohnE 我认为你有错误的印象。如果这种事情被记录为不被推荐/支持,那么我完全可以接受它可能显示的任何不稳定行为。不过,如果不是,我认为应该小心,以使 API 以尽可能一致和可预测的方式运行(最不惊讶等等)。因此,如果熊猫文档在任何时候都说“不要这样做”,我会立即接受带有链接的答案。我的观点是,应该可以直接找出是否值得追求这样的事情。
  • @Socob,您可能对this meta discussion 感兴趣。
  • Socob:我删除了我的评论,因为 jpp 在这里和元讨论中更好地解释了它。我不认为你做错了什么,这可能没有很好的文档记录(熊猫以没有很好的文档而闻名)。它更像是熊猫用户随着时间的推移学习的民间传说(通过阅读此处的问答)。 @jpp 这是一个很好的元讨论,我不知道你对我的回答有何看法,但我确实试图让它不仅仅是通用的,而是特定于 OP 试图做的事情。我与您一样,对最佳方法到底是什么感到困惑!
  • @JohnE,我很喜欢你的回答,我已经投了赞成票 :)

标签: python pandas numpy dataframe


【解决方案1】:

为什么其他列的存在会对这种情况产生影响 任务?

答案很简单,因为 Pandas 会检查数据框中的混合类型。您可以使用源代码中使用的相同方法自行检查:

print(df1._is_mixed_type)  # False
print(df2._is_mixed_type)  # False
print(df3._is_mixed_type)  # True

使用的逻辑因_is_mixed_type 的值而异。具体来说,当 _is_mixed_typeTrue 对于您提供的输入时,_setitem_with_indexer 中的以下测试失败:

if len(labels) != value.shape[1]:
    raise ValueError('Must have equal len keys and value '
                     'when setting with an ndarray')

换句话说,数组中的列比数据框中要分配的列多。

这是一个错误吗?在我看来,在 Pandas 数据框中使用列表或数组都充满危险。1添加了ValueError 检查以解决更重要的问题 (GH 7551)。


为什么不同版本的作业不等价?

通过df3['col1'] = x 分配有效的原因是因为col1 是现有系列。尝试df3['col3'] = x,您的代码将因ValueError 而失败。

深入挖掘,df[] 是语法糖的 datframe 的 __setitem__ 方法通过 key = com._apply_if_callable(key, self)'col1' 标签转换为系列(如果存在):

def _apply_if_callable(maybe_callable, obj, **kwargs):
    """
    Evaluate possibly callable input using obj and kwargs if it is callable,
    otherwise return as it is
    """
    if callable(maybe_callable):
        return maybe_callable(obj, **kwargs)
    return maybe_callable

然后该逻辑可以回避_setitem_with_indexer 中的检查逻辑。您可以推断出这一点,因为当我们为现有系列提供标签时,我们会跳转到 _setitem_array 而不是 _set_item

def __setitem__(self, key, value):

    key = com._apply_if_callable(key, self)

    if isinstance(key, (Series, np.ndarray, list, Index)):
        self._setitem_array(key, value)
    elif isinstance(key, DataFrame):
        self._setitem_frame(key, value)
    else:
        self._set_item(key, value)

以上都是实现细节;你不应该基于这些底层方法来构建你的 Pandas 语法,因为它们可能会在未来发生变化。


1 我想说它应该被禁用默认并且只能通过设置启用。这是一种非常低效的存储和操作数据的方式。有时它提供了短期的便利,但代价是代码混淆了。

【讨论】:

  • 有趣。不过,您还没有解决问题的第二部分。特别是,您的解释似乎不适用于df3.col1 = x 这一行。
  • @Socob,查看更新。 TL; DR:在系列中使用列表/数组风险自负
  • df.loc[0:len(df), 'col1'] 又做了与df.loc[:, 'col1'] 不同的事情? (因为df1.loc[0:2, 'col1'] = xdf2.loc[0:2, 'col1'] = x都抛出ValueError,而df1.loc[:, 'col1'] = xdf2.loc[:, 'col1'] = x都没有。)
  • @Socob,如果我有时间,我会研究行索引。说实话,这很可能是白费力气。这些都没有记录,因此可能会在不通知的情况下更改新版本。特别是因为它不推荐使用。
  • @Socob,它几乎记录在(或应该记录在)我关于 SO 的所有答案中,涉及pd.Series 对象中的列表。但我不确定这是否重要:)。至于if it doesn't error, expect it to work,我认为要很好地理解Pandas,您必须了解NumPy 以及object dtype 有据可查的缺陷这一事实。它在文档here 中提到,以If a DataFrame or Panel contains homogeneously-typed data... 开头的段落。
【解决方案2】:

首先,让我尝试一个不那么技术性和不那么严格的@jpp 解释版本。一般来说,当您尝试将 numpy 数组插入 pandas 数据帧时,pandas 期望它们具有相同的等级和维度(例如,两者都是 4x2,尽管如果 numpy 数组的等级低于 pandas 也可以没关系,例如,如果 pandas 尺寸是 4x2 而 numpy 尺寸是 4x1 或 2x1 - 请阅读numpy broadcasting 了解更多信息)。

前面的要点很简单,当您尝试将 3x3 numpy 数组放入长度为 3(基本上是 3x1)的 pandas 列时,pandas 并没有真正的标准方法来处理它,并且不一致的行为是只是一个结果。如果 pandas 总是抛出异常可能会更好,但一般来说 pandas 会尝试做一些事情,但它可能没有什么用处。

其次,(我意识到这不是一个字面上的答案)从长远来看,我可以保证,如果你不花很多时间来解决死记硬背二维的血腥细节,你会过得更好数组到单个熊猫列。相反,只需遵循更典型的 pandas 方法,如下所示,这将生成以下代码:(1) 行为更可预测,(2) 更具可读性,(3) 运行速度更快。

x = np.arange(1,10).reshape(3,3)
y = x * 10
z = x * 100

df = pd.DataFrame( np.hstack((x,y)), columns=['x1 x2 x3 y1 y2 y3'.split()] )

#   x1 x2 x3  y1  y2  y3
# 0  1  2  3  10  20  30
# 1  4  5  6  40  50  60
# 2  7  8  9  70  80  90

df.loc[:,'x1':'x3'] = z

#     x1   x2   x3  y1  y2  y3
# 0  100  200  300  10  20  30
# 1  400  500  600  40  50  60
# 2  700  800  900  70  80  90

我将其保留为一个简单的索引,但看起来您正在尝试做的是建立一个更具层次结构的结构,而 pandas 可以提供帮助,它具有一个名为 MultiIndex 的功能。在这种情况下,结果是更清晰的语法,但请注意,在其他情况下使用起来可能会更复杂(这里不值得详细介绍):

df = pd.DataFrame( np.hstack((x,y)), 
     columns=pd.MultiIndex.from_product( [list('xy'),list('123')] ) )

df.loc[:,'x'] = z       # now you can replace 'x1':'x3' with 'x'

您可能知道这一点,但是从数据帧中提取 numpy 数组也非常容易,因此您只需将 numpy 数组放入多个列中就不会丢失任何内容。例如,在多索引情况下:

df.loc[:,'x'].values

# array([[100, 200, 300],
#        [400, 500, 600],
#        [700, 800, 900]])

【讨论】:

  • 我认为你应该留下这个答案!尽管它没有明确回答问题,但它提供的信息可能对未来的用户有用,这些用户的搜索将他们带到这里
  • 我很欣赏这个答案,尽管它的方向与我的预期不同。它确实暗示了我正在寻找的内容(不支持/不推荐的操作,但未记录是这种情况)。
猜你喜欢
  • 2021-08-22
  • 2018-12-27
  • 2019-05-05
  • 2022-01-24
  • 2021-04-12
  • 1970-01-01
  • 1970-01-01
  • 2018-01-01
  • 2017-02-13
相关资源
最近更新 更多