将2级numpy数组分配给pandas DataFrame列的行为不一致

时间:2018-08-29 14:45:53

标签: python pandas numpy dataframe

我注意到,分配给pandas DataFrame列(使用.loc索引器)的行为不同,具体取决于DataFrame中存在的其他列作业的确切形式。使用三个示例DataFrame

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
      

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

我的问题是:

  • 为什么其他列会在这种分配中有所作为?
  • 为什么不同版本的作业不相同?特别是,在没有导致ValueError的情况下,为什么DataFrame列中填充{{1的 first column }}数组?

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

2 个答案:

答案 0 :(得分:10)

  

为什么其他列会在这种情况下有所作为   任务?

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

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

所使用的逻辑根据_is_mixed_type的值而不同。具体来说,当您提供的输入的_setitem_with_indexer_is_mixed_type时,True中的以下测试将失败:

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

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

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 我要说的是,默认情况下应将其禁用 ,而只能通过设置启用。这是存储和处理数据的极其低效的方式。有时它提供短期的便利,但是却以混乱的代码为代价。

答案 1 :(得分:5)

首先,让我尝试一下@jpp解释的技术性和严格性较低的版本。一般而言,当您尝试将numpy数组插入熊猫数据帧时,熊猫希望它们具有相同的等级和尺寸(例如,两者均为4x2,尽管如果numpy数组的等级低于熊猫也可以)例如,如果pandas尺寸为4x2,而numpy尺寸为4x1或2x1,则可以使用numpy broadcasting以获得更多信息)。

前面的观点很简单,当您尝试将3x3的numpy数组放入长度为3(基本上为3x1)的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

我把它作为一个简单的索引,但是看起来您想要做的是建立一个更高层次的结构,而熊猫可以通过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]])