Python

Python 数据处理(四)——DataFrame

2021-02-05  本文已影响0人  名本无名

2. DataFrame

DataFrame 数二维带标签的数据结构,每列可以是不同的数据类型,你可以把它想象成一个 excel 表格或 SQL 表,或者是由 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

可以使用成员属性 indexcolumns 访问索引和列名

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 将会有同样的索引,如果未设置列名,默认将使用 Seriesname

>>> 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

其中 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 接受元组列表或结构数据类型(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
用方法链分配新列

dplyrmutate 动词的启发,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
上一篇下一篇

猜你喜欢

热点阅读