Caveats and Gotchas

原文:http://pandas.pydata.org/pandas-docs/stable/gotchas.html

译者:飞龙 UsyiyiCN

校对:(虚位以待)

Using If/Truth Statements with pandas

pandas遵循numpy约定,当你尝试将某个东西转换为bool时产生错误。这发生在if中或使用布尔运算andornot时。不清楚的结果是什么

>>> if pd.Series([False, True, False]):
     ...

应该。应该是True,因为它不是零长度吗?False因为有False值?目前还不清楚,所以相反,熊猫会引发一个ValueError

>>> if pd.Series([False, True, False]):
    print("I was true")
Traceback
    ...
ValueError: The truth value of an array is ambiguous. Use a.empty, a.any() or a.all().

如果你看到,你需要明确选择你想做什么(例如,使用any()all()>)。或者,您可能想要比较如果pandas对象是None

>>> if pd.Series([False, True, False]) is not None:
       print("I was not None")
>>> I was not None

或如果any值为True则返回。

>>> if pd.Series([False, True, False]).any():
       print("I am any")
>>> I am any

要在布尔上下文中评估单元素熊猫对象,请使用方法.bool()

In [1]: pd.Series([True]).bool()
Out[1]: True

In [2]: pd.Series([False]).bool()
Out[2]: False

In [3]: pd.DataFrame([[True]]).bool()
Out[3]: True

In [4]: pd.DataFrame([[False]]).bool()
Out[4]: False

Bitwise boolean

==!=的位布尔运算符将返回布尔Series,这几乎总是你想要的。

>>> s = pd.Series(range(5))
>>> s == 4
0    False
1    False
2    False
3    False
4     True
dtype: bool

有关更多示例,请参见boolean comparisons

Using the in operator

在系列中使用中的Python in

如果这种行为是令人惊讶的,请记住,在Python字典中使用in中测试键,而不是值,并且Series是dict类似的。要测试值的成员资格,请使用方法isin()

对于DataFrames,同样,in中适用于列轴,测试列名列表中的成员资格。

NaN, Integer NA values and NA type promotions

Choice of NA representation

由于在NumPy和Python中缺少NA(缺失)支持,我们在两者之间的困难选择

由于很多原因,我们选择后者。经过多年的生产使用,它已经证明,至少在我看来,是给出了NumPy和Python中的一般状态的最佳决定。特殊值NaN(Not-A-Number)随处可用作NA值,并且有API函数isnullnotnull,可以跨越dtypes使用以检测NA值。

然而,它有一些权衡,我绝对不能忽视它。

Support for integer NA

在没有高性能NA支持从头开始构建到NumPy中的情况下,主要的伤亡是在整数数组中表示NA的能力。例如:

In [5]: s = pd.Series([1, 2, 3, 4, 5], index=list('abcde'))

In [6]: s
Out[6]: 
a    1
b    2
c    3
d    4
e    5
dtype: int64

In [7]: s.dtype
Out[7]: dtype('int64')

In [8]: s2 = s.reindex(['a', 'b', 'c', 'f', 'u'])

In [9]: s2
Out[9]: 
a    1.0
b    2.0
c    3.0
f    NaN
u    NaN
dtype: float64

In [10]: s2.dtype
Out[10]: dtype('float64')

这种折衷主要是因为存储器和性能的原因,并且还使得所得到的系列继续是“数字”。一种可能性是使用dtype=object数组。

NA type promotions

当通过reindex或其他方法将NA引入现有的Series或DataFrame时,布尔型和整型将被提升为不同的dtype,以便存储NA。这些表总结如下:

类型类 用于存储NAs的升级类型
floating 不用找了
object 不用找了
integer 转换为float64
boolean 转换为object

虽然这似乎是一个沉重的权衡,我发现很少的案例,这是一个问题在实践中。这里的动机的一些解释在下一节。

Why not make NumPy like R?

许多人建议NumPy应该简单地模拟更特定领域的统计编程语言R中存在的NA支持。部分原因是NumPy类型层次结构:

类型类 Dtypes
numpy.floating float16, float32, float64, float128
numpy.integer int8, int16, int32, int64
numpy.unsignedinteger uint8, uint16, uint32, uint64
numpy.object_ object_
numpy.bool_ bool_
numpy.character string _, unicode _

相反,R语言只有一些内置数据类型:integernumeric(浮点),characterbooleanNA类型是通过为每个类型保留特殊位模式以用作缺失值来实现的。虽然使用完整的NumPy类型层次结构是可能的,但它将是一个更实质的权衡(尤其是对于8位和16位数据类型)和实现承诺。

另一种方法是使用掩码数组。掩码数组是具有相关布尔掩码的数据数组,表示每个值是否应被视为NA我个人不喜欢这种方法,因为我觉得整体它给用户和库实现者带来相当沉重的负担。此外,与使用NaN的简单方法相比,使用数值数据时,它会产生相当高的性能成本。因此,我选择了Pythonic的“实用性节拍纯度”方法和交易整数NA能力,使用一个更简单的方法在浮点和对象数组中使用一个特殊值来表示NA ,并且当必须引入NAs时,将整数数组提升为浮点。

Integer indexing

使用整数轴标签的基于标签的索引是一个棘手的主题。它已经在邮件列表和科学Python社区的各种成员中进行了大量讨论。在大熊猫,我们的一般观点是,标签不止于整数位置。因此,对于整数轴索引,只有可以使用标准工具(如.ix)进行基于标签的索引。以下代码将生成异常:

s = pd.Series(range(5))
s[-1]
df = pd.DataFrame(np.random.randn(5, 4))
df
df.ix[-2:]

这个故意的决定是为了防止歧义和微妙的错误(许多用户报告发现错误,当API更改停止“退回”基于位置的索引)。

Label-based slicing conventions

Non-monotonic indexes require exact matches

如果SeriesDataFrame的索引是单调递增或递减,则基于标签的切片的边界可能在索引的范围之外,一个普通的Python list可以使用is_monotonic_increasingis_monotonic_decreasing属性来测试索引的单调性。

In [11]: df = pd.DataFrame(index=[2,3,3,4,5], columns=['data'], data=range(5))

In [12]: df.index.is_monotonic_increasing
Out[12]: True

# no rows 0 or 1, but still returns rows 2, 3 (both of them), and 4:
In [13]: df.loc[0:4, :]
Out[13]: 
   data
2     0
3     1
3     2
4     3

# slice is are outside the index, so empty DataFrame is returned
In [14]: df.loc[13:15, :]
Out[14]: 
Empty DataFrame
Columns: [data]
Index: []

另一方面,如果索引不是单调的,则两个片边界必须是索引的唯一成员。

In [15]: df = pd.DataFrame(index=[2,3,1,4,3,5], columns=['data'], data=range(6))

In [16]: df.index.is_monotonic_increasing
Out[16]: False

# OK because 2 and 4 are in the index
In [17]: df.loc[2:4, :]
Out[17]: 
   data
2     0
3     1
1     2
4     3
# 0 is not in the index
In [9]: df.loc[0:4, :]
KeyError: 0

# 3 is not a unique label
In [11]: df.loc[2:3, :]
KeyError: 'Cannot get right slice bound for non-unique label: 3'

Endpoints are inclusive

与标准Python序列切片(其中切片端点不包括)相比,pandas 中的基于标签的切片是包含的。这样做的主要原因是,通常不可能容易地确定索引中特定标签之后的“后继者”或下一个元素。例如,考虑以下系列:

In [18]: s = pd.Series(np.random.randn(6), index=list('abcdef'))

In [19]: s
Out[19]: 
a    1.544821
b   -1.708552
c    1.545458
d   -0.735738
e   -0.649091
f   -0.403878
dtype: float64

假设我们希望从c切割到e,使用整数,这将是

In [20]: s[2:5]
Out[20]: 
c    1.545458
d   -0.735738
e   -0.649091
dtype: float64

但是,如果只有ce,则确定索引中的下一个元素可能会有些复杂。例如,以下不工作:

s.ix['c':'e'+1]

一个非常常见的用例是限制时间序列在两个特定日期开始和结束。为了实现这一点,我们进行了设计设计,使基于标签的切片包括两个端点:

In [21]: s.ix['c':'e']
Out[21]: 
c    1.545458
d   -0.735738
e   -0.649091
dtype: float64

这绝对是一个“实用性节拍纯度”的事情,但它是值得注意的是,如果你期望基于标签的切片行为完全符合标准的Python整数切片的工作方式。

Miscellaneous indexing gotchas

Reindex versus ix gotchas

许多用户会发现自己使用ix索引功能作为从pandas对象中选择数据的简单方法:

In [22]: df = pd.DataFrame(np.random.randn(6, 4), columns=['one', 'two', 'three', 'four'],
   ....:                   index=list('abcdef'))
   ....: 

In [23]: df
Out[23]: 
        one       two     three      four
a -2.474932  0.975891 -0.204206  0.452707
b  3.478418 -0.591538 -0.508560  0.047946
c -0.170009 -1.615606 -0.894382  1.334681
d -0.418002 -0.690649  0.128522  0.429260
e  1.207515 -1.308877 -0.548792 -1.520879
f  1.153696  0.609378 -0.825763  0.218223

In [24]: df.ix[['b', 'c', 'e']]
Out[24]: 
        one       two     three      four
b  3.478418 -0.591538 -0.508560  0.047946
c -0.170009 -1.615606 -0.894382  1.334681
e  1.207515 -1.308877 -0.548792 -1.520879

这当然是使用reindex方法完全等同于在这种情况下

In [25]: df.reindex(['b', 'c', 'e'])
Out[25]: 
        one       two     three      four
b  3.478418 -0.591538 -0.508560  0.047946
c -0.170009 -1.615606 -0.894382  1.334681
e  1.207515 -1.308877 -0.548792 -1.520879

有些人可能会得出结论,基于此,ixreindex是100%等效。除非在整数索引的情况下,这的确是真的例如,上述操作可以替代地被表示为:

In [26]: df.ix[[1, 2, 4]]
Out[26]: 
        one       two     three      four
b  3.478418 -0.591538 -0.508560  0.047946
c -0.170009 -1.615606 -0.894382  1.334681
e  1.207515 -1.308877 -0.548792 -1.520879

如果您通过[1, 2, 4]reindex另一件事完全是:

In [27]: df.reindex([1, 2, 4])
Out[27]: 
   one  two  three  four
1  NaN  NaN    NaN   NaN
2  NaN  NaN    NaN   NaN
4  NaN  NaN    NaN   NaN

因此,请务必记住reindex仅限严格标签索引这可能导致在索引包含整数和字符串的病理情况下的一些潜在的令人惊讶的结果:

In [28]: s = pd.Series([1, 2, 3], index=['a', 0, 1])

In [29]: s
Out[29]: 
a    1
0    2
1    3
dtype: int64

In [30]: s.ix[[0, 1]]
Out[30]: 
0    2
1    3
dtype: int64

In [31]: s.reindex([0, 1])
Out[31]: 
0    2
1    3
dtype: int64

因为在这种情况下的索引不仅包含整数,所以ix返回整数索引。相比之下,reindex仅查找在索引中传递的值,因此找到整数01虽然可以插入一些逻辑来检查传递的序列是否全部包含在索引中,但是该逻辑在大数据集中将会产生非常高的成本。

Reindex potentially changes underlying Series dtype

使用reindex_like可以更改Series的dtype。

In [32]: series = pd.Series([1, 2, 3])

In [33]: x = pd.Series([True])

In [34]: x.dtype
Out[34]: dtype('bool')

In [35]: x = pd.Series([True]).reindex_like(series)

In [36]: x.dtype
Out[36]: dtype('O')

这是因为reindex_like会静默插入NaNsdtype当使用numpy ufuncs(例如numpy.logical_and)时,可能会导致一些问题。

有关详细讨论,请参阅此旧问题

Parsing Dates from Text Files

当将多个文本文件列解析为单个日期列时,新的日期列将预置在数据前,然后index_col规范将从新的列集合中索引,而不是原始列的索引;

In [37]: print(open('tmp.csv').read())
KORD,19990127, 19:00:00, 18:56:00, 0.8100
KORD,19990127, 20:00:00, 19:56:00, 0.0100
KORD,19990127, 21:00:00, 20:56:00, -0.5900
KORD,19990127, 21:00:00, 21:18:00, -0.9900
KORD,19990127, 22:00:00, 21:56:00, -0.5900
KORD,19990127, 23:00:00, 22:56:00, -0.5900

In [38]: date_spec = {'nominal': [1, 2], 'actual': [1, 3]}

In [39]: df = pd.read_csv('tmp.csv', header=None,
   ....:                  parse_dates=date_spec,
   ....:                  keep_date_col=True,
   ....:                  index_col=0)
   ....: 

# index_col=0 refers to the combined column "nominal" and not the original
# first column of 'KORD' strings
In [40]: df
Out[40]: 
                                 actual     0         1          2          3  \
nominal                                                                         
1999-01-27 19:00:00 1999-01-27 18:56:00  KORD  19990127   19:00:00   18:56:00   
1999-01-27 20:00:00 1999-01-27 19:56:00  KORD  19990127   20:00:00   19:56:00   
1999-01-27 21:00:00 1999-01-27 20:56:00  KORD  19990127   21:00:00   20:56:00   
1999-01-27 21:00:00 1999-01-27 21:18:00  KORD  19990127   21:00:00   21:18:00   
1999-01-27 22:00:00 1999-01-27 21:56:00  KORD  19990127   22:00:00   21:56:00   
1999-01-27 23:00:00 1999-01-27 22:56:00  KORD  19990127   23:00:00   22:56:00   

                        4  
nominal                    
1999-01-27 19:00:00  0.81  
1999-01-27 20:00:00  0.01  
1999-01-27 21:00:00 -0.59  
1999-01-27 21:00:00 -0.99  
1999-01-27 22:00:00 -0.59  
1999-01-27 23:00:00 -0.59  

Differences with NumPy

对于Series和DataFrame对象,var通过N-1归一化以产生样本方差的无偏估计,而NumPy的var测量样本的方差。注意,在pandas和NumPy中,cov通过N-1进行归一化。

Thread-safety

从熊猫0.11,熊猫不是100%线程安全。已知问题与DataFrame.copy方法有关。如果你正在做很多线程之间共享的DataFrame对象的复制,我们建议在发生数据复制的线程中保持锁。

有关详细信息,请参阅此链接

HTML Table Parsing

围绕库的一些版本化问题用于解析顶级pandas io函数read_html中的HTML表。

lxml有关的问题

  • 好处
    • lxml非常快
    • lxml需要Cython才能正确安装。
  • 缺点
    • lxml不会对其解析的结果做任何保证,除非 t>给出strictly valid markup
    • 鉴于上述,我们选择允许您,用户使用lxml后端,但此后端将使用 html5lib if lxml无法解析
    • 因此,强烈建议您安装BeautifulSoup4html5lib,以便您仍然可以获得有效的结果(如果一切都有效)即使lxml失败。

BeautifulSoup4 使用 lxml 作为后端

  • 上面的问题也在这里,因为BeautifulSoup4本质上只是一个解析器后端的包装。

Issues with BeautifulSoup4 using html5lib as a backend

  • 好处
    • html5liblxml宽松得多,因此以更清楚的方式处理现实生活标记,而不仅仅是放弃元素而不通知您。
    • html5lib 自动从无效标记生成有效的HTML5标记这对于解析HTML表非常重要,因为它保证了有效的文档。然而,这并不意味着它是“正确的”,因为固定标记的过程没有单一的定义。
    • html5lib是纯Python,除了自己的安装外,不需要额外的构建步骤。
  • 缺点
    • 使用html5lib的最大缺点是它作为糖蜜慢。然而,考虑到网络上的许多表对于解析算法运行时来说不够大的事实。更有可能的是,瓶颈将在从web上的URL读取原始文本的过程中,即IO(输入 - 输出)。对于非常大的表,这可能不是真的。

使用 Anaconda的问题

注意

除非您同时拥有

  • 对包含read_html()的一些代码的运行时上限的强限制
  • 完全知道您将要解析的HTML将始终有效100%

那么你应该安装html5lib,并且事情会自动运行,而不必使用conda如果你想要两个世界中最好的,然后安装html5liblxml如果您安装lxml,则需要执行以下命令以确保lxml正常工作:

# remove the included version
conda remove lxml

# install the latest version of lxml
pip install 'git+git://github.com/lxml/lxml.git'

# install the latest version of beautifulsoup4
pip install 'bzr+lp:beautifulsoup'

请注意,您需要安装bzrgit才能执行最后两个操作。

Byte-Ordering Issues

有时,您可能必须处理在机器上创建的数据具有与运行Python不同的字节顺序。这个问题的常见症状是错误

Traceback
    ...
ValueError: Big-endian buffer not supported on little-endian compiler

要处理这个问题,应该使用类似于以下内容的方法将底层NumPy数组转换为本地系统字节顺序之后传递给Series / DataFrame / Panel构造函数:

In [41]: x = np.array(list(range(10)), '>i4') # big endian

In [42]: newx = x.byteswap().newbyteorder() # force native byteorder

In [43]: s = pd.Series(newx)

有关详细信息,请参阅有关字节顺序的NumPy文档