Python Pandas:你可能不知道的技巧和功能

举报
Yuchuan 发表于 2021/12/23 19:45:50 2021/12/23
【摘要】 Pandas 是一个用于分析、数据处理和数据科学的基础库。这是一个巨大的项目,具有大量的可选性和深度。 本教程将涵盖借给你的代码可读性更好,通用性和速度,一些较少使用,但习惯大熊猫能力点菜了来自Buzzfeed listicle。

目录

Pandas 是一个用于分析、数据处理和数据科学的基础库。这是一个巨大的项目,具有大量的可选性和深度。

本教程将涵盖借给你的代码可读性更好,通用性和速度,一些较少使用,但习惯大熊猫能力点菜了来自Buzzfeed listicle。

如果您对 Python 的 Pandas 库的核心概念感到满意,希望您能在本文中找到一两个您以前没有遇到过的技巧。(如果您刚开始使用图书馆,10 分钟到 Pandas是一个不错的起点。)

注意:本文中的示例使用 Pandas 版本 0.23.2 和 Python 3.6.6 进行测试。但是,它们在旧版本中也应该有效。

1. 在解释器启动时配置选项和设置

您之前可能遇到过 Pandas 丰富的选项和设置系统。

在解释器启动时设置自定义的 Pandas 选项可以极大地节省生产力,尤其是当您在脚本环境中工作时。您可以使用PythonIPython启动文件pd.set_option()来根据自己的喜好进行配置。

选项使用点符号,例如pd.set_option('display.max_colwidth', 25),它非常适合嵌套的选项字典

import pandas as pd

def start():
    options = {
        'display': {
            'max_columns': None,
            'max_colwidth': 25,
            'expand_frame_repr': False,  # Don't wrap to multiple pages
            'max_rows': 14,
            'max_seq_items': 50,         # Max length of printed sequence
            'precision': 4,
            'show_dimensions': False
        },
        'mode': {
            'chained_assignment': None   # Controls SettingWithCopyWarning
        }
    }

    for category, option in options.items():
        for op, value in option.items():
            pd.set_option(f'{category}.{op}', value)  # Python 3.6+

if __name__ == '__main__':
    start()
    del start  # Clean up namespace in the interpreter

如果您启动解释器会话,您将看到启动脚本中的所有内容都已执行,并且 Pandas 会通过您的选项套件自动为您导入:

>>>
>>> pd.__name__
'pandas'
>>> pd.get_option('display.max_rows')
14

让我们使用UCI 机器学习存储库托管的鲍鱼的一些数据来演示在启动文件中设置的格式。数据将在 14 行处截断,浮点数为 4 位精度:

>>>
>>> url = ('https://archive.ics.uci.edu/ml/'
...        'machine-learning-databases/abalone/abalone.data')
>>> cols = ['sex', 'length', 'diam', 'height', 'weight', 'rings']
>>> abalone = pd.read_csv(url, usecols=[0, 1, 2, 3, 4, 8], names=cols)

>>> abalone
     sex  length   diam  height  weight  rings
0      M   0.455  0.365   0.095  0.5140     15
1      M   0.350  0.265   0.090  0.2255      7
2      F   0.530  0.420   0.135  0.6770      9
3      M   0.440  0.365   0.125  0.5160     10
4      I   0.330  0.255   0.080  0.2050      7
5      I   0.425  0.300   0.095  0.3515      8
6      F   0.530  0.415   0.150  0.7775     20
# ...
4170   M   0.550  0.430   0.130  0.8395     10
4171   M   0.560  0.430   0.155  0.8675      8
4172   F   0.565  0.450   0.165  0.8870     11
4173   M   0.590  0.440   0.135  0.9660     10
4174   M   0.600  0.475   0.205  1.1760      9
4175   F   0.625  0.485   0.150  1.0945     10
4176   M   0.710  0.555   0.195  1.9485     12

稍后您还会在其他示例中看到此数据集弹出。

2. 使用 Pandas 的测试模块制作玩具数据结构

注意:该pandas.util.testing模块在 Pandas 1.0弃用。该“公开测试API”从pandas.testing现在仅限于assert_extension_array_equal(), assert_frame_equal()assert_series_equal(),和assert_index_equal()。作者承认,他因为依赖 Pandas 库中未记录的部分而尝到了自己的滋味。

在 Pandas 的testing模块中隐藏着许多方便的功能,用于快速构建准真实的系列和数据帧:

>>>
>>> import pandas.util.testing as tm
>>> tm.N, tm.K = 15, 3  # Module-level default rows/columns

>>> import numpy as np
>>> np.random.seed(444)

>>> tm.makeTimeDataFrame(freq='M').head()
                 A       B       C
2000-01-31  0.3574 -0.8804  0.2669
2000-02-29  0.3775  0.1526 -0.4803
2000-03-31  1.3823  0.2503  0.3008
2000-04-30  1.1755  0.0785 -0.1791
2000-05-31 -0.9393 -0.9039  1.1837

>>> tm.makeDataFrame().head()
                 A       B       C
nTLGGTiRHF -0.6228  0.6459  0.1251
WPBRn9jtsR -0.3187 -0.8091  1.1501
7B3wWfvuDA -1.9872 -1.0795  0.2987
yJ0BTjehH1  0.8802  0.7403 -1.2154
0luaYUYvy1 -0.9320  1.2912 -0.2907

其中大约有 30 个,您可以通过调用dir()模块对象来查看完整列表。这里有一些:

>>>
>>> [i for i in dir(tm) if i.startswith('make')]
['makeBoolIndex',
 'makeCategoricalIndex',
 'makeCustomDataframe',
 'makeCustomIndex',
 # ...,
 'makeTimeSeries',
 'makeTimedeltaIndex',
 'makeUIntIndex',
 'makeUnicodeIndex']

这些对于基准测试、测试断言和试验您不太熟悉的 Pandas 方法非常有用。

3. 利用存取方法

也许您听说过accessor一词,它有点像 getter(尽管 getter 和 setter 在 Python 中很少使用)。出于我们的目的,您可以将 Pandas 访问器视为一个属性,用作其他方法的接口。

Pandas 系列包含三个:

>>>
>>> pd.Series._accessors
{'cat', 'str', 'dt'}

是的,上面的定义很啰嗦,所以在讨论内部结构之前,让我们先看几个例子。

.cat用于分类数据,.str用于字符串(对象)数据,.dt用于类似日期时间的数据。让我们从以下开始.str:假设您有一些原始城市/州/邮政编码数据作为 Pandas 系列中的单个字段。

Pandas 字符串方法是向量化的,这意味着它们在没有显式 for 循环的情况下对整个数组进行操作:

>>>
>>> addr = pd.Series([
...     'Washington, D.C. 20003',
...     'Brooklyn, NY 11211-1755',
...     'Omaha, NE 68154',
...     'Pittsburgh, PA 15211'
... ])

>>> addr.str.upper()
0     WASHINGTON, D.C. 20003
1    BROOKLYN, NY 11211-1755
2            OMAHA, NE 68154
3       PITTSBURGH, PA 15211
dtype: object

>>> addr.str.count(r'\d')  # 5 or 9-digit zip?
0    5
1    9
2    5
3    5
dtype: int64

对于更复杂的示例,假设您想将三个城市/州/邮政编码组件整齐地分离到 DataFrame 字段中。

您可以传递正则表达式.str.extract()“提取”系列中每个单元格的部分。在.str.extract(),.str是访问器,并且.str.extract()是访问器方法:

>>>
>>> regex = (r'(?P<city>[A-Za-z ]+), '      # One or more letters
...          r'(?P<state>[A-Z]{2}) '        # 2 capital letters
...          r'(?P<zip>\d{5}(?:-\d{4})?)')  # Optional 4-digit extension
...
>>> addr.str.replace('.', '').str.extract(regex)
         city state         zip
0  Washington    DC       20003
1    Brooklyn    NY  11211-1755
2       Omaha    NE       68154
3  Pittsburgh    PA       15211

这也说明了所谓的方法链,其中.str.extract(regex)在 的结果上调用addr.str.replace('.', ''),它清除句点的使用以获得一个很好的 2 字符状态缩写。

稍微了解一下这些访问器方法的工作原理是有帮助的,作为您应该首先使用它们的动机,而不是像addr.apply(re.findall, ...).

每个访问器本身都是一个真正的 Python 类:

这些独立的类然后使用CachedAccessor. 当类被包裹起来时CachedAccessor,就会发生一些神奇的事情。

CachedAccessor受到“缓存属性”设计的启发:每个实例只计算一次属性,然后替换为普通属性。它通过重载.__get__()方法来做到这一点,它是 Python描述符协议的一部分。

注意:如果您想了解更多有关其工作原理的信息,请参阅Python 描述符 HOWTO和有关缓存属性设计的这篇文章。Python 3 还引入了functools.lru_cache(),它提供了类似的功能。到处都有这种模式的例子,比如在aiohttp包中。

第二个访问器 ,.dt用于类似日期时间的数据。它在技术上属于 Pandas' DatetimeIndex,如果在系列上调用,它会转换为DatetimeIndex第一个:

>>>
>>> daterng = pd.Series(pd.date_range('2017', periods=9, freq='Q'))
>>> daterng
0   2017-03-31
1   2017-06-30
2   2017-09-30
3   2017-12-31
4   2018-03-31
5   2018-06-30
6   2018-09-30
7   2018-12-31
8   2019-03-31
dtype: datetime64[ns]

>>>  daterng.dt.day_name()
0      Friday
1      Friday
2    Saturday
3      Sunday
4    Saturday
5    Saturday
6      Sunday
7      Monday
8      Sunday
dtype: object

>>> # Second-half of year only
>>> daterng[daterng.dt.quarter > 2]
2   2017-09-30
3   2017-12-31
6   2018-09-30
7   2018-12-31
dtype: datetime64[ns]

>>> daterng[daterng.dt.is_year_end]
3   2017-12-31
7   2018-12-31
dtype: datetime64[ns]

第三个访问器.cat仅用于分类数据,您很快就会在其自己的部分中看到。

4.从组件列创建一个日期时间索引

说到类似日期时间的数据,如上daterng所示,可以DatetimeIndex从一起形成日期或日期时间的多个组件列创建 Pandas :

>>>
>>> from itertools import product
>>> datecols = ['year', 'month', 'day']

>>> df = pd.DataFrame(list(product([2017, 2016], [1, 2], [1, 2, 3])),
...                   columns=datecols)
>>> df['data'] = np.random.randn(len(df))
>>> df
    year  month  day    data
0   2017      1    1 -0.0767
1   2017      1    2 -1.2798
2   2017      1    3  0.4032
3   2017      2    1  1.2377
4   2017      2    2 -0.2060
5   2017      2    3  0.6187
6   2016      1    1  2.3786
7   2016      1    2 -0.4730
8   2016      1    3 -2.1505
9   2016      2    1 -0.6340
10  2016      2    2  0.7964
11  2016      2    3  0.0005

>>> df.index = pd.to_datetime(df[datecols])
>>> df.head()
            year  month  day    data
2017-01-01  2017      1    1 -0.0767
2017-01-02  2017      1    2 -1.2798
2017-01-03  2017      1    3  0.4032
2017-02-01  2017      2    1  1.2377
2017-02-02  2017      2    2 -0.2060

最后,您可以删除旧的单个列并转换为系列:

>>>
>>> df = df.drop(datecols, axis=1).squeeze()
>>> df.head()
2017-01-01   -0.0767
2017-01-02   -1.2798
2017-01-03    0.4032
2017-02-01    1.2377
2017-02-02   -0.2060
Name: data, dtype: float64

>>> df.index.dtype_str
'datetime64[ns]

传递 DataFrame 背后的直觉是,DataFrame 类似于 Python 字典,其中列名是键,各个列(系列)是字典值。这就是为什么pd.to_datetime(df[datecols].to_dict(orient='list'))在这种情况下也会起作用的原因。这反映了 Python 的构造datetime.datetime,您可以在其中传递关键字参数,例如datetime.datetime(year=2000, month=1, day=15, hour=10).

5. 使用分类数据节省时间和空间

Pandas 的一项强大功能是它的Categoricaldtype。

即使您并不总是在 RAM 中处理千兆字节的数据,您也可能遇到过对大型 DataFrame 进行简单操作似乎挂起超过几秒钟的情况。

Pandas objectdtype 通常是转换为类别数据的绝佳选择。(object是 Python str、异构数据类型或“其他”类型的容器。)字符串在内存中占用大量空间:

>>>
>>> colors = pd.Series([
...     'periwinkle',
...     'mint green',
...     'burnt orange',
...     'periwinkle',
...     'burnt orange',
...     'rose',
...     'rose',
...     'mint green',
...     'rose',
...     'navy'
... ])
...
>>> import sys
>>> colors.apply(sys.getsizeof)
0    59
1    59
2    61
3    59
4    61
5    53
6    53
7    59
8    53
9    53
dtype: int64

注意:我曾经sys.getsizeof()展示过系列中每个单独的值所占用的内存。请记住,这些 Python 对象首先具有一些开销。(sys.getsizeof('')将返回 49 个字节。)

还有colors.memory_usage(),它总结了内存使用情况并依赖于.nbytes底层 NumPy 数组的属性。不要太拘泥于这些细节:重要的是类型转换导致的相对内存使用量,正如您接下来将看到的。

现在,如果我们可以采用上面的独特颜色并将每个颜色映射到一个不那么占用空间的整数呢?这是一个简单的实现:

>>>
>>> mapper = {v: k for k, v in enumerate(colors.unique())}
>>> mapper
{'periwinkle': 0, 'mint green': 1, 'burnt orange': 2, 'rose': 3, 'navy': 4}

>>> as_int = colors.map(mapper)
>>> as_int
0    0
1    1
2    2
3    0
4    2
5    3
6    3
7    1
8    3
9    4
dtype: int64

>>> as_int.apply(sys.getsizeof)
0    24
1    28
2    28
3    24
4    28
5    28
6    28
7    28
8    28
9    28
dtype: int64

注意:做同样事情的另一种方法是使用 Pandas' pd.factorize(colors)

>>>
>>> pd.factorize(colors)[0]
array([0, 1, 2, 0, 2, 3, 3, 1, 3, 4])

无论哪种方式,您都将对象编码为枚举类型(分类变量)。

您会立即注意到,与将完整字符串与objectdtype一起使用时,内存使用量几乎减少了一半。

在前面关于访问器的部分中,我提到了.cat(分类)访问器。上面的 withmapper粗略地说明了 Pandas 的Categoricaldtype内部发生的事情:

“a 的内存使用量与Categorical类别数加上数据长度成正比。相比之下,objectdtype 是数据长度的常数倍。” (来源)

colors上面,每个唯一值(类别)的比率为 2 个值:

>>>
>>> len(colors) / colors.nunique()
2.0

因此,转换为 的内存节省Categorical是好的,但不是很好:

>>>
>>> # Not a huge space-saver to encode as Categorical
>>> colors.memory_usage(index=False, deep=True)
650
>>> colors.astype('category').memory_usage(index=False, deep=True)
495

但是,如果你把上面的比例吹大,数据很多,唯一值很少(想想人口统计或字母测试分数的数据),所需的内存减少了 10 倍以上:

>>>
>>> manycolors = colors.repeat(10)
>>> len(manycolors) / manycolors.nunique()  # Much greater than 2.0x
20.0

>>> manycolors.memory_usage(index=False, deep=True)
6500
>>> manycolors.astype('category').memory_usage(index=False, deep=True)
585

一个好处是计算效率也得到了提升:对于 categorical Series,字符串操作是在.cat.categories属性上执行的,而不是在 的每个原始元素上执行的Series

换句话说,对每个唯一类别执行一次操作,并将结果映射回值。分类数据有一个.cat访问器,它是一个窗口,可以访问用于操作类别的属性和方法:

>>>
>>> ccolors = colors.astype('category')
>>> ccolors.cat.categories
Index(['burnt orange', 'mint green', 'navy', 'periwinkle', 'rose'], dtype='object')

事实上,您可以重现类似于您手动执行的上述示例的内容:

>>>
>>> ccolors.cat.codes
0    3
1    1
2    0
3    3
4    0
5    4
6    4
7    1
8    4
9    2
dtype: int8

要完全模仿早期的手动输出,您需要做的就是重新排序代码:

>>>
>>> ccolors.cat.reorder_categories(mapper).cat.codes
0    0
1    1
2    2
3    0
4    2
5    3
6    3
7    1
8    3
9    4
dtype: int8

请注意,dtype 是 NumPy 的int8,这是一个8 位有符号整数,可以采用从 -127 到 128 的值。(只需要一个字节来表示内存中的值。ints就内存使用而言,64 位有符号会过大.) 我们的粗略示例int64默认生成数据,而 Pandas 足够聪明,可以将分类数据向下转换为尽可能小的数字 dtype。

的大多数属性.cat都与查看和操作底层类别本身有关:

>>>
>>> [i for i in dir(ccolors.cat) if not i.startswith('_')]
['add_categories',
 'as_ordered',
 'as_unordered',
 'categories',
 'codes',
 'ordered',
 'remove_categories',
 'remove_unused_categories',
 'rename_categories',
 'reorder_categories',
 'set_categories']

不过,有一些警告。分类数据通常不太灵活。例如,如果插入以前未见过的值,则需要先将此值添加到.categories容器中:

>>>
>>> ccolors.iloc[5] = 'a new color'
# ...
ValueError: Cannot setitem on a Categorical with a new category,
set the categories first

>>> ccolors = ccolors.cat.add_categories(['a new color'])
>>> ccolors.iloc[5] = 'a new color'  # No more ValueError

如果您计划设置值或重塑数据而不是派生新计算,则Categorical类型可能不那么灵活。

6. 通过迭代自省 Groupby 对象

当您调用 时df.groupby('x'),生成的Pandasgroupby对象可能有点不透明。这个对象是惰性实例化的,它本身没有任何有意义的表示。

您可以使用示例 1 中的鲍鱼数据集进行演示:

>>>
>>> abalone['ring_quartile'] = pd.qcut(abalone.rings, q=4, labels=range(1, 5))
>>> grouped = abalone.groupby('ring_quartile')

>>> grouped
<pandas.core.groupby.groupby.DataFrameGroupBy object at 0x11c1169b0>

好的,现在你有了一个groupby对象,但这是什么东西,我怎么看?

在调用类似的东西之前grouped.apply(func),您可以利用groupby对象是可迭代的这一事实:

>>>
>>> help(grouped.__iter__)

        Groupby iterator

        Returns
        -------
        Generator yielding sequence of (name, subsetted object)
        for each group

由 产生的每个“事物”grouped.__iter__()都是一个元组(name, subsetted object),其中name是您要分组的列的值,并且subsetted object是一个 DataFrame,它是基于您指定的任何分组条件的原始 DataFrame 的子集。也就是说,数据按组分块:

>>>
>>> for idx, frame in grouped:
...     print(f'Ring quartile: {idx}')
...     print('-' * 16)
...     print(frame.nlargest(3, 'weight'), end='\n\n')
...
Ring quartile: 1
----------------
     sex  length   diam  height  weight  rings ring_quartile
2619   M   0.690  0.540   0.185  1.7100      8             1
1044   M   0.690  0.525   0.175  1.7005      8             1
1026   M   0.645  0.520   0.175  1.5610      8             1

Ring quartile: 2
----------------
     sex  length  diam  height  weight  rings ring_quartile
2811   M   0.725  0.57   0.190  2.3305      9             2
1426   F   0.745  0.57   0.215  2.2500      9             2
1821   F   0.720  0.55   0.195  2.0730      9             2

Ring quartile: 3
----------------
     sex  length  diam  height  weight  rings ring_quartile
1209   F   0.780  0.63   0.215   2.657     11             3
1051   F   0.735  0.60   0.220   2.555     11             3
3715   M   0.780  0.60   0.210   2.548     11             3

Ring quartile: 4
----------------
     sex  length   diam  height  weight  rings ring_quartile
891    M   0.730  0.595    0.23  2.8255     17             4
1763   M   0.775  0.630    0.25  2.7795     12             4
165    M   0.725  0.570    0.19  2.5500     14             4

相关地,一个groupby对象还具有.groups和基团的吸气剂,.get_group()

>>>
>>> grouped.groups.keys()
dict_keys([1, 2, 3, 4])

>>> grouped.get_group(2).head()
   sex  length   diam  height  weight  rings ring_quartile
2    F   0.530  0.420   0.135  0.6770      9             2
8    M   0.475  0.370   0.125  0.5095      9             2
19   M   0.450  0.320   0.100  0.3810      9             2
23   F   0.550  0.415   0.135  0.7635      9             2
39   M   0.355  0.290   0.090  0.3275      9             2

这可以帮助您更加确信您正在执行的操作是您想要的操作:

>>>
>>> grouped['height', 'weight'].agg(['mean', 'median'])
               height         weight
                 mean median    mean  median
ring_quartile
1              0.1066  0.105  0.4324  0.3685
2              0.1427  0.145  0.8520  0.8440
3              0.1572  0.155  1.0669  1.0645
4              0.1648  0.165  1.1149  1.0655

无论您执行什么计算grouped,无论是单个 Pandas 方法还是自定义函数,这些“子框架”中的每一个都作为参数一一传递给该可调用对象。这就是术语“拆分-应用-组合”的由来:按组分解数据,执行每组计算,并以某种聚合方式重新组合。

如果您无法准确地可视化这些组的实际外观,只需迭代它们并打印一些可能会非常有用。

7. 使用此映射技巧进行会员分级

假设您有一个系列和一个相应的“映射表”,其中每个值都属于一个多成员组,或者根本不属于任何组:

>>>
>>> countries = pd.Series([
...     'United States',
...     'Canada',
...     'Mexico',
...     'Belgium',
...     'United Kingdom',
...     'Thailand'
... ])
...
>>> groups = {
...     'North America': ('United States', 'Canada', 'Mexico', 'Greenland'),
...     'Europe': ('France', 'Germany', 'United Kingdom', 'Belgium')
... }

换句话说,您需要映射countries到以下结果:

>>>
0    North America
1    North America
2    North America
3           Europe
4           Europe
5            other
dtype: object

您在这里需要的是一个类似于 Pandas' 的函数pd.cut(),但用于基于分类成员资格的分箱。您可以使用pd.Series.map()示例 #5 中已经看到的来模仿这一点:

from typing import Any

def membership_map(s: pd.Series, groups: dict,
                   fillvalue: Any=-1) -> pd.Series:
    # Reverse & expand the dictionary key-value pairs
    groups = {x: k for k, v in groups.items() for x in v}
    return s.map(groups).fillna(fillvalue)

这应该是显著快于通过嵌套的Python循环groups每个国家countries

这是一个试驾:

>>>
>>> membership_map(countries, groups, fillvalue='other')
0    North America
1    North America
2    North America
3           Europe
4           Europe
5            other
dtype: object

让我们分解一下这里发生了什么。(旁注:这是一个很好的地方,可以使用 Python 的调试器进入函数的作用域pdb,以检查函数的局部变量。)

目标是将每个组映射groups到一个整数。但是,Series.map()不会识别'ab'——它需要将每个组中的每个字符映射到一个整数的分解版本。这就是字典理解正在做的事情:

>>>
>>> groups = dict(enumerate(('ab', 'cd', 'xyz')))
>>> {x: k for k, v in groups.items() for x in v}
{'a': 0, 'b': 0, 'c': 1, 'd': 1, 'x': 2, 'y': 2, 'z': 2}

可以传递此字典以s.map()将其值映射或“翻译”到相应的组索引。

8. 了解 Pandas 如何使用布尔运算符

您可能熟悉Python的运算符优先级,其中andnotor比算术运算符,如较低的优先级<<=>>=!=,和==。考虑下面的两个语句,其中<>and运算符具有更高的优先级:

>>>
>>> # Evaluates to "False and True"
>>> 4 < 3 and 5 > 4
False

>>> # Evaluates to 4 < 5 > 4
>>> 4 < (3 and 5) > 4
True

注意:它不是专门与 Pandas 相关的,但由于短路评估而3 and 5评估为5

“短路运算符的返回值是最后评估的参数。” (来源)

熊猫(和NumPy的,其上熊猫内置)不使用andornot。相反,它分别使用&|~,它们是正常的、真正的 Python按位运算符

这些操作符不是 Pandas 发明的。相反,&,|~是有效的 Python 内置运算符,它们具有比算术运算符更高(而不是更低)的优先级。(Pandas 覆盖了 dunder 方法,例如.__ror__()映射到|运算符。)为了牺牲一些细节,您可以将“按位”视为“按元素”,因为它与 Pandas 和 NumPy 相关:

>>>
>>> pd.Series([True, True, False]) & pd.Series([True, False, False])
0     True
1    False
2    False
dtype: bool

完全理解这个概念是值得的。假设您有一个类似范围的系列:

>>>
>>> s = pd.Series(range(10))

我猜你可能已经在某个时候看到了这个异常:

>>>
>>> s % 2 == 0 & s > 3
ValueError: The truth value of a Series is ambiguous.
Use a.empty, a.bool(), a.item(), a.any() or a.all().

这里发生了什么事?用括号递增地绑定表达式是有帮助的,说明 Python 是如何逐步扩展这个表达式的:

s % 2 == 0 & s > 3                      # Same as above, original expression
(s % 2) == 0 & s > 3                    # Modulo is most tightly binding here
(s % 2) == (0 & s) > 3                  # Bitwise-and is second-most-binding
(s % 2) == (0 & s) and (0 & s) > 3      # Expand the statement
((s % 2) == (0 & s)) and ((0 & s) > 3)  # The `and` operator is least-binding

该表达式s % 2 == 0 & s > 3等效于 (或被视为) ((s % 2) == (0 & s)) and ((0 & s) > 3)。这称为扩展x < y <= z相当于x < y and y <= z

好的,现在到此为止,让我们将其带回 Pandas 语言。你有两个熊猫系列,我们会打电话leftright

>>>
>>> left = (s % 2) == (0 & s)
>>> right = (0 & s) > 3
>>> left and right  # This will raise the same ValueError

您知道该形式的语句left and right是对left和 进行真值测试right,如下所示:

>>>
>>> bool(left) and bool(right)

问题是 Pandas 开发人员故意不为整个系列建立真值(truthiness)。系列是真还是假?谁知道?结果模棱两可:

>>>
>>> bool(s)
ValueError: The truth value of a Series is ambiguous.
Use a.empty, a.bool(), a.item(), a.any() or a.all().

唯一有意义的比较是元素比较。这就是为什么,如果涉及算术运算符,您将需要括号

>>>
>>> (s % 2 == 0) & (s > 3)
0    False
1    False
2    False
3    False
4     True
5    False
6     True
7    False
8     True
9    False
dtype: bool

简而言之,如果你看到ValueError上面弹出布尔索引,你可能应该做的第一件事就是在一些需要的括号中加入。

9. 从剪贴板加载数据

需要将数据从 Excel 或Sublime Text等地方传输到 Pandas 数据结构是一种常见情况。理想情况下,您希望在不经过将数据保存到文件然后将文件读入 Pandas的中间步骤的情况下执行此操作。

您可以使用pd.read_clipboard(). 它的关键字参数传递给pd.read_table().

这允许您将结构化文本直接复制到 DataFrame 或 Series。在 Excel 中,数据看起来像这样:

Excel 剪贴板数据

它的纯文本表示(例如,在文本编辑器中)如下所示:

a   b           c       d
0   1           inf     1/1/00
2   7.389056099 N/A     5-Jan-13
4   54.59815003 nan     7/24/18
6   403.4287935 None    NaT

只需突出显示并复制上面的纯文本,然后调用pd.read_clipboard()

>>>
>>> df = pd.read_clipboard(na_values=[None], parse_dates=['d'])
>>> df
   a         b    c          d
0  0    1.0000  inf 2000-01-01
1  2    7.3891  NaN 2013-01-05
2  4   54.5982  NaN 2018-07-24
3  6  403.4288  NaN        NaT

>>> df.dtypes
a             int64
b           float64
c           float64
d    datetime64[ns]
dtype: object

10. 将 Pandas 对象直接写入压缩格式

这是一个简短而甜蜜的清单。从 Pandas 0.21.0 版本开始,您可以将 Pandas 对象直接写入 gzip、bz2、zip 或 xz 压缩,而不是将未压缩的文件存放在内存中并进行转换。这是使用技巧 #1 中abalone数据的示例:

abalone.to_json('df.json.gz', orient='records',
                lines=True, compression='gzip')

在这种情况下,大小差异为 11.6 倍:

>>>
>>> import os.path
>>> abalone.to_json('df.json', orient='records', lines=True)
>>> os.path.getsize('df.json') / os.path.getsize('df.json.gz')
11.603035760226396

想要添加到此列表中?让我们知道

希望您能够从这个列表中学到一些有用的技巧,让您的 Pandas 代码具有更好的可读性、多功能性和性能。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。