Python 数据处理(四)——DataFrame
2. DataFrame
DataFrame
数二维带标签的数据结构,每列可以是不同的数据类型,你可以把它想象成一个 excel
表格或 SQL
表,或者是由 Series 对象构成的字典。
DataFrame
是我们最常用的对象,它也支持多种类型的输入数据
- 一维数组字典,列表、字典或
Series
- 二维数组
- 结构化数组或记录数组
Series
- 其他
DataFrame
除了传入数据,也可以同时指定数据的索引(index
)和列名(columns
)。如果输入数据是字典类型的 Series
且带有索引,那么会将与指定索引不匹配的数据删除
如果没有设置轴标签,则按常规数据解析构建索引和列名
注意:与前面提到的从字典构建 Series
对象类似,在 Python > = 3.6
,且 Pandas > = 0.23
时,当输入数据是字典,且未指定 columns
参数时,DataFrame
的列按字典的插入顺序排序。其他版本会按照字典键的字母顺序对列排序
值为 Series 的字典
用 Series
字典构建 DataFrame
其生成的索引是各个 Series
索引的并集。如果存在嵌套的字典,会先把嵌套字典转换为 Series
。如果未指定列名,会将字典的键作为列名
In [37]: d = {'one': pd.Series([1., 2., 3.], index=['a', 'b', 'c']),
...: 'two': pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])
...: }
In [38]: df = pd.DataFrame(d)
In [39]: df
Out[40]:
one two
a 1.0 1.0
b 2.0 2.0
c 3.0 3.0
d NaN 4.0
In [40]: pd.DataFrame(d, index=["d", "b", "a"])
Out[40]:
one two
d NaN 4.0
b 2.0 2.0
a 1.0 1.0
In [41]: pd.DataFrame(d, index=["d", "b", "a"], columns=["two", "three"])
Out[41]:
two three
d 4.0 NaN
b 2.0 NaN
a 1.0 NaN
可以使用成员属性 index
和 columns
访问索引和列名
In [42]: df.index
Out[42]: Index(['a', 'b', 'c', 'd'], dtype='object')
In [43]: df.columns
Out[43]: Index(['one', 'two'], dtype='object')
注意:如果设置了列名参数,会覆盖原本字典的键
值为数组/列表的字典
字典里的每个数组长度必须一致,如果指定了索引参数,那么索引的长度必须与数组长度一致。如果未指定索引,默认会将 range(n)
作为索引,n
为数组长度
In [44]: d = {"one": [1.0, 2.0, 3.0, 4.0], "two": [4.0, 3.0, 2.0, 1.0]}
In [45]: pd.DataFrame(d)
Out[45]:
one two
0 1.0 4.0
1 2.0 3.0
2 3.0 2.0
3 4.0 1.0
In [46]: pd.DataFrame(d, index=["a", "b", "c", "d"])
Out[46]:
one two
a 1.0 4.0
b 2.0 3.0
c 3.0 2.0
d 4.0 1.0
如果长度不一致,会抛出异常
>>>pd.DataFrame(d, index=["a", "b", "c"])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
...
结构化数组和记录型数组
这种情况的处理方式与值为数组的字典一样
In [47]: data = np.zeros((2,), dtype=[("A", "i4"), ("B", "f4"), ("C", "a10")])
In [48]: data[:] = [(1, 2.0, "Hello"), (2, 3.0, "World")]
In [49]: pd.DataFrame(data)
Out[49]:
A B C
0 1 2.0 b'Hello'
1 2 3.0 b'World'
In [50]: pd.DataFrame(data, index=["first", "second"])
Out[50]:
A B C
first 1 2.0 b'Hello'
second 2 3.0 b'World'
In [51]: pd.DataFrame(data, columns=["C", "A", "B"])
Out[51]:
C A B
0 b'Hello' 1 2.0
1 b'World' 2 3.0
注意:DataFrame
的运作方式与二维 NumPy
数组是不一样的
字典列表
In [52]: data = [{"a": 1, "b": 2}, {"a": 5, "b": 10, "c": 20}]
In [53]: pd.DataFrame(data)
Out[53]:
a b c
0 1 2 NaN
1 5 10 20.0
In [54]: pd.DataFrame(data, index=["first", "second"])
Out[54]:
a b c
first 1 2 NaN
second 5 10 20.0
In [55]: pd.DataFrame(data, columns=["a", "b"])
Out[55]:
a b
0 1 2
1 5 10
元组型字典
通过传递元组字典,可以自动创建多级索引
In [56]: pd.DataFrame(
....: {
....: ("a", "b"): {("A", "B"): 1, ("A", "C"): 2},
....: ("a", "a"): {("A", "C"): 3, ("A", "B"): 4},
....: ("a", "c"): {("A", "B"): 5, ("A", "C"): 6},
....: ("b", "a"): {("A", "C"): 7, ("A", "B"): 8},
....: ("b", "b"): {("A", "D"): 9, ("A", "B"): 10},
....: }
....: )
....:
Out[56]:
a b
b a c a b
A B 1.0 4.0 5.0 8.0 10.0
C 2.0 3.0 6.0 7.0 NaN
D NaN NaN NaN NaN 9.0
Series
从 Series
构建的 DataFrame
将会有同样的索引,如果未设置列名,默认将使用 Series
的 name
>>> s = pd.Series(np.random.randn(5), index=list('abcde'), name='something')
>>> s
a -0.722865
b 1.317593
c -1.843862
d -1.530197
e 0.404915
Name: something, dtype: float64
>>> pd.DataFrame(s)
something
a -0.722865
b 1.317593
c -1.843862
d -1.530197
e 0.404915
命名元组列表
DataFrame
的列数由列表中第一个 namedtuple
的字段名称确定,其余的命名元组(或元组)会被简单地解压缩,并填到新的行中
如果其中任何一个元组比第一个命名的元组短,则相应的行中后面的列将标记为缺失值。如果存在长度超过第一个命名元组的元组,则会引发 ValueError
。
In [57]: from collections import namedtuple
In [58]: Point = namedtuple("Point", "x y")
In [59]: pd.DataFrame([Point(0, 0), Point(0, 3), (2, 3)])
Out[59]:
x y
0 0 0
1 0 3
2 2 3
In [60]: Point3D = namedtuple("Point3D", "x y z")
In [61]: pd.DataFrame([Point3D(0, 0, 0), Point3D(0, 3, 5), Point(2, 3)])
Out[61]:
x y z
0 0 0 0.0
1 0 3 5.0
2 2 3 NaN
备选构造函数
DataFrame.from_dict
DataFrame.from_dict
接收嵌套字典或数组序列的字典,并生成 DataFrame
。
其中 orient
参数默认为 columns
,该构建器的操作与 DataFrame
构建器类似。
In [65]: pd.DataFrame.from_dict(dict([("A", [1, 2, 3]), ("B", [4, 5, 6])]))
Out[65]:
A B
0 1 4
1 2 5
2 3 6
如果 orient
参数设置为 index
, 则会把字典的键作为行索引,同时还可以设置列名。
In [66]: pd.DataFrame.from_dict(
....: dict([("A", [1, 2, 3]), ("B", [4, 5, 6])]),
....: orient="index",
....: columns=["one", "two", "three"],
....: )
....:
Out[66]:
one two three
A 1 2 3
B 4 5 6
DataFrame.from_records
DataFrame.from_records
接受元组列表或结构数据类型(dtype
)的多维数组。
与 DataFrame
构建器类似,只不过生成的 DataFrame
索引是结构数据类型指定的字段。例如
In [67]: data
Out[67]:
array([(1, 2., b'Hello'), (2, 3., b'World')],
dtype=[('A', '<i4'), ('B', '<f4'), ('C', 'S10')])
In [68]: pd.DataFrame.from_records(data, index="C")
Out[68]:
A B
C
b'Hello' 1 2.0
b'World' 2 3.0
列的选择、添加、删除
你可以在语义上将 DataFrame
看作是类似 Series
的字典,获取、设置和删除列的语法与字典类似
In [69]: df["one"]
Out[69]:
a 1.0
b 2.0
c 3.0
d NaN
Name: one, dtype: float64
In [70]: df["three"] = df["one"] * df["two"]
In [71]: df["flag"] = df["one"] > 2
In [72]: df
Out[72]:
one two three flag
a 1.0 1.0 1.0 False
b 2.0 2.0 4.0 False
c 3.0 3.0 9.0 True
d NaN 4.0 NaN False
可以像字典一样删除或 pop
列
In [73]: del df["two"]
In [74]: three = df.pop("three")
In [75]: df
Out[75]:
one flag
a 1.0 False
b 2.0 False
c 3.0 True
d NaN False
当插入标量值时,它会自动扩展来填充列
In [76]: df["foo"] = "bar"
In [77]: df
Out[77]:
one flag foo
a 1.0 False bar
b 2.0 False bar
c 3.0 True bar
d NaN False bar
当插入与 DataFrame
索引不同的 Series
时,它将与 DataFrame
的索引对齐,未匹配的赋值为 NaN
In [78]: df["one_trunc"] = df["one"][:2]
In [79]: df
Out[79]:
one flag foo one_trunc
a 1.0 False bar 1.0
b 2.0 False bar 2.0
c 3.0 True bar NaN
d NaN False bar NaN
您可以插入原始的 ndarray
数组,但是它们的长度必须与 DataFrame
索引的长度一致
默认情况下,列会插入到末尾。 使用 insert
函数可用于在列中的特定位置插入
In [80]: df.insert(1, "bar", df["one"])
In [81]: df
Out[81]:
one bar flag foo one_trunc
a 1.0 1.0 False bar 1.0
b 2.0 2.0 False bar 2.0
c 3.0 3.0 True bar NaN
d NaN NaN False bar NaN
用方法链分配新列
受 dplyr
的 mutate
动词的启发,DataFrame
也提供了一个 assign
方法,利用现有的列创建新的列
In [82]: iris = pd.read_csv("data/iris.data")
In [83]: iris.head()
Out[83]:
SepalLength SepalWidth PetalLength PetalWidth Name
0 5.1 3.5 1.4 0.2 Iris-setosa
1 4.9 3.0 1.4 0.2 Iris-setosa
2 4.7 3.2 1.3 0.2 Iris-setosa
3 4.6 3.1 1.5 0.2 Iris-setosa
4 5.0 3.6 1.4 0.2 Iris-setosa
In [84]: iris.assign(sepal_ratio=iris["SepalWidth"] / iris["SepalLength"]).head()
Out[84]:
SepalLength SepalWidth PetalLength PetalWidth Name sepal_ratio
0 5.1 3.5 1.4 0.2 Iris-setosa 0.686275
1 4.9 3.0 1.4 0.2 Iris-setosa 0.612245
2 4.7 3.2 1.3 0.2 Iris-setosa 0.680851
3 4.6 3.1 1.5 0.2 Iris-setosa 0.673913
4 5.0 3.6 1.4 0.2 Iris-setosa 0.720000
还可以传递带参数的函数进行求值
In [85]: iris.assign(sepal_ratio=lambda x: (x["SepalWidth"] / x["SepalLength"])).head()
Out[85]:
SepalLength SepalWidth PetalLength PetalWidth Name sepal_ratio
0 5.1 3.5 1.4 0.2 Iris-setosa 0.686275
1 4.9 3.0 1.4 0.2 Iris-setosa 0.612245
2 4.7 3.2 1.3 0.2 Iris-setosa 0.680851
3 4.6 3.1 1.5 0.2 Iris-setosa 0.673913
4 5.0 3.6 1.4 0.2 Iris-setosa 0.720000
assign
返回的是一个数据拷贝,而不会修改原数据
当你在数据操作链的中间想要添加一列数据,又不想写入数据中时,会非常有用。
例如,对于花萼长度大于 5
的数据,计算比例并绘制图像
In [86]: (
....: iris.query("SepalLength > 5")
....: .assign(
....: SepalRatio=lambda x: x.SepalWidth / x.SepalLength,
....: PetalRatio=lambda x: x.PetalWidth / x.PetalLength,
....: )
....: .plot(kind="scatter", x="SepalRatio", y="PetalRatio")
....: )
....:
Out[86]: <AxesSubplot:xlabel='SepalRatio', ylabel='PetalRatio'>
image.png
在这个例子中,我们使用了 DataFrame
的链式操作,先筛选出花萼长度大于 5
的数据,然后用 assign
计算并添加两列比例值,最后绘制这两列计算值的散点图
从 3.6
版开始,Python
可以保存 **kwargs
顺序。也就是说前面的参数会保存,可以让后面的参数调用。
In [87]: dfa = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
In [88]: dfa.assign(C=lambda x: x["A"] + x["B"], D=lambda x: x["A"] + x["C"])
Out[88]:
A B C D
0 1 4 5 6
1 2 5 7 9
2 3 6 9 12
在这个例子中,在新建 D
列的时候我们调用了之前创建的 C
列。即 D = A + (A + B)
要兼容所有的版本,可以使用链式操作
>>> dfa.assign(C=lambda x: x["A"] + x["B"]).assign(D=lambda x: x["A"] + x["C"])
A B C D
0 1 4 5 6
1 2 5 7 9
2 3 6 9 12