本文将主要介绍以下内容:
1. 针对内存使用优化数据集
2. 按单一条件筛选
3. 按多个条件筛选
4. 其它筛选方法
5. 处理重复项
在前面的文章中,我们练习了从DataFrame中选择单独的行,列和值。现在让我们探索如何基于一个或多个条件来筛选数据。
1. 针对内存使用优化数据集
和往常一样,让我们从导入pandas开始:
In [1]: import pandas as pd
接下来让我们看看要使用的employee.csv测试数据集,它是公司虚构的员工集合。每条记录都包括员工的名字、性别、在公司的开始日期、薪水、管理状态和所在的团队:
In [2]: pd.read_csv("employees.csv")
Out [2]: First Name Gender Start Date Salary Mgmt Team
0 Douglas Male 8/6/93 NaN True Marketing
1 Thomas Male 3/31/96 61933.0 True NaN
2 Maria Female NaN 130590.0 False Finance
3 Jerry NaN 3/4/05 138705.0 True Finance
4 Larry Male 1/24/98 101004.0 True IT
… … … … … … …
996 Phillip Male 1/31/84 42392.0 False Finance
997 Russell Male 5/20/13 96914.0 False Product
998 Larry Male 4/20/13 60500.0 False Business Dev
999 Albert Male 5/15/12 129949.0 True Sales
1000 NaN NaN NaN NaN NaN NaN
1001 rows × 6 columns
不难看出数据集在每列中都有缺失的值,实际上最后一行仅包含NaN值。在现实世界中的数据会根据导出系统的不同而变化很大,空行是常见的情况。
第一个优化可以使用parse_dates参数将Start Date列中的文本值转换为datetime对象:
In [3]: pd.read_csv("employees.csv", parse_dates = ["Start Date"]).head()
Out [3]: First Name Gender Start Date Salary Mgmt Team
0 Douglas Male 1993-08-06 NaN True Marketing
1 Thomas Male 1996-03-31 61933.0 True NaN
2 Maria Female NaT 130590.0 False Finance
3 Jerry NaN 2005-03-04 138705.0 True Finance
4 Larry Male 1998-01-24 101004.0 True IT
下面让我们将DataFrame对象分配给变量employees:
In [4]: employees = pd.read_csv("employees.csv",
parse_dates = ["Start Date"])
1.1 使用as_type方法转换数据类型
info方法返回数据集的摘要,包括列名、非空值数量、数据类型和内存消耗:
In [5]: employees.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1001 entries, 0 to 1000
Data columns (total 6 columns):
# Column Non-Null Count Dtype
0 First Name 933 non-null object
1 Gender 854 non-null object
2 Start Date 999 non-null datetime64[ns]
3 Salary 999 non-null float64
4 Mgmt 933 non-null object
5 Team 957 non-null object
dtypes: datetime64[ns](1), float64(1), object(4)
memory usage: 47.0+ KB
RangeIndex显示数据集有1001行,然后我们可以从Non-Null Count列确定每列非空值的数量,所有六列都缺少数据。当前的内存使用量约为47kb,让我们看看是否可以减少它。
astype方法用于把Pandas对象的值转换为其它数据类型。下例是把Mgmt列转换为bool数据类型,返回值是一个新的Series对象。请注意,NaN值会被转换为True值。在本文的后面,我会介绍如何删除和替换缺失值。
In [6]: employees["Mgmt"].astype(bool)
Out [6]: 0 True
1 True
2 False
3 True
4 True
...
996 False
997 False
998 False
999 True
1000 True
Name: Mgmt, Length: 1001, dtype: bool
在DataFrame中覆盖存在列或创建新列的原理和向字典添加键值对是相似的,如果存在具有指定名称的列,Pandas会使用新的Series覆盖它;如果该列不存在,Pandas会把新的Series添加到DataFrame的右侧,并通过两个数据结构中的公共索引标签进行匹配。
In [7]: employees["Mgmt"] = employees["Mgmt"].astype(bool)
让我们用info方法再查看一下内存消耗:
In [8]: employees.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1001 entries, 0 to 1000
Data columns (total 6 columns):
# Column Non-Null Count Dtype
0 First Name 933 non-null object
1 Gender 854 non-null object
2 Start Date 999 non-null datetime64[ns]
3 Salary 999 non-null float64
4 Mgmt 1001 non-null bool
5 Team 957 non-null object
dtypes: bool(1), datetime64[ns](1), float64(1), object(3)
memory usage: 40.2+ KB
啊哈!我们把内存使用量减少了近15%,这是一个很好的开始!接下来,让我们看一下Salary列。如果打开原始CSV文件会看到该列的值实际上存储为整数,出于技术原因,Pandas需要将其从整数转换为浮点值,以支持分散在各处的NaN值。如果我们尝试把列的值强制转为整数,则会抛出ValueError异常:
In [9]: employees["Salary"].astype(int)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-99-b148c8b8be90> in <module>
----> 1 employees["Salary"].astype(int)
ValueError: Cannot convert non-finite values (NA or inf) to integer
在本文的后面,我将会介绍如何完全删除最后一行。现在,我们可以使用fillna方法把NaN替换为指定的值,返回一个新的Series。例如替换为0:
In [10]: employees["Salary"].fillna(0).tail()
Out [10]: 99 42392.0
99 96914.0
998 60500.0
999 129949.0
1000 0.0
Name: Salary, dtype: float64
现在Salary列中已经没有缺少的值,可以将其值转换为整数:
In [11]: employees["Salary"].fillna(0).astype(int).head()
Out [11]: 0 0
1 61933
2 130590
3 138705
4 101004
Name: Salary, dtype: int64
转换为整数后覆盖原来的列:
In [12]: employees["Salary"] = employees["Salary"].fillna(0).astype(int)
我们已经转换了Start Date和Mgmt列,以存储比字符串更合适的数据类型。还有什么可以优化的吗?绝对有!
Pandas包含一种称为category的特殊数据类型,当列包含相对于其总数量的少量唯一值时,它是理想的选择。数量有限的常见数据包括性别、工作日、血型等。category的值存储为普通的Python对象而不是NumPy的ndarrays,并针对性能进行了优化。
之前介绍过nunique方法可以返回DataFrame每列中唯一值的数量。请注意,默认情况下将排除缺失值NaN,不过您也可以使用dropna = False参数把其计算在内。
In [13]: employees.nunique()
Out [13]: First Name 200
Gender 2
Start Date 971
Salary 994
Mgmt 2
Team 10
dtype: int64
最适合作为category类型的两列是Gender和Team,在1001行数据中,它们分别仅有2和10个唯一值。让我们再次练习使用astype方法:
In [14]: employees["Gender"].astype("category")
Out [14]: 0 Male
1 Male
2 Female
3 NaN
4 Male
...
996 Male
997 Male
998 Male
999 Male
1000 NaN
Name: Gender, Length: 1001, dtype: category
Categories (2, object): [Female, Male]
让我们覆盖原有的Gender列并检查内存使用情