Caveats and Gotchas¶
Using If/Truth Statements with pandas¶
pandas遵循numpy约定,当你尝试将某个东西转换为bool
时产生错误。这发生在if
中或使用布尔运算and
,or
或not
时。不清楚的结果是什么
>>> 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。
NaN
, Integer NA
values and NA
type promotions¶
Choice of NA
representation¶
由于在NumPy和Python中缺少NA
(缺失)支持,我们在两者之间的困难选择
- 屏蔽数组解决方案:数据数组和布尔值数组,表示数值
- 使用特殊的标记值,位模式或一组标记值来表示跨越dty的
NA
由于很多原因,我们选择后者。经过多年的生产使用,它已经证明,至少在我看来,是给出了NumPy和Python中的一般状态的最佳决定。特殊值NaN
(Not-A-Number)随处可用作NA
值,并且有API函数isnull
和notnull
,可以跨越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语言只有一些内置数据类型:integer
,numeric
(浮点),character
和boolean
。NA
类型是通过为每个类型保留特殊位模式以用作缺失值来实现的。虽然使用完整的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¶
如果Series
或DataFrame
的索引是单调递增或递减,则基于标签的切片的边界可能在索引的范围之外,一个普通的Python list
。可以使用is_monotonic_increasing
和is_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
但是,如果只有c
和e
,则确定索引中的下一个元素可能会有些复杂。例如,以下不工作:
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
有些人可能会得出结论,基于此,ix
和reindex
是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
仅查找在索引中传递的值,因此找到整数0
和1
。虽然可以插入一些逻辑来检查传递的序列是否全部包含在索引中,但是该逻辑在大数据集中将会产生非常高的成本。
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
会静默插入NaNs
和dtype
。当使用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不会不对其解析的结果做任何保证,除非 t>给出strictly valid markup。
- 鉴于上述,我们选择允许您,用户使用lxml后端,但此后端将使用 html5lib if lxml无法解析
- 因此,强烈建议您安装BeautifulSoup4和html5lib,以便您仍然可以获得有效的结果(如果一切都有效)即使lxml失败。
与 BeautifulSoup4 使用 lxml 作为后端
- 上面的问题也在这里,因为BeautifulSoup4本质上只是一个解析器后端的包装。
Issues with BeautifulSoup4 using html5lib as a backend
- 好处
- 缺点
- 使用html5lib的最大缺点是它作为糖蜜慢。然而,考虑到网络上的许多表对于解析算法运行时来说不够大的事实。更有可能的是,瓶颈将在从web上的URL读取原始文本的过程中,即IO(输入 - 输出)。对于非常大的表,这可能不是真的。
使用 Anaconda的问题
- Anaconda附带lxml版本3.2.0;以下针对Anaconda的解决方法已成功用于处理围绕lxml和BeautifulSoup4的版本问题。
注意
除非您同时拥有:
- 对包含
read_html()
的一些代码的运行时上限的强限制- 完全知道您将要解析的HTML将始终有效100%
那么你应该安装html5lib,并且事情会自动运行,而不必使用conda。如果你想要两个世界中最好的,然后安装html5lib和lxml。如果您安装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'
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文档。