烹饪指南

本节列出了一些短小精悍的 Pandas 实例与链接。

我们希望 Pandas 用户能积极踊跃地为本文档添加更多内容。为本节添加实用示例的链接或代码,是 Pandas 用户提交第一个 Pull Request 最好的选择。

本节列出了简单、精练、易上手的实例代码,以及 Stack Overflow 或 GitHub 上的链接,这些链接包含实例代码的更多详情。

pdnp 是 Pandas 与 Numpy 的缩写。为了让新手易于理解,其它模块是显式导入的。

下列实例均为 Python 3 代码,简单修改即可用于 Python 早期版本。

惯用语

以下是 Pandas 的惯用语

对一列数据执行 if-then / if-then-else 操作,把计算结果赋值给一列或多列:

  1. In [1]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
  2. ...: 'BBB': [10, 20, 30, 40],
  3. ...: 'CCC': [100, 50, -30, -50]})
  4. ...:
  5. In [2]: df
  6. Out[2]:
  7. AAA BBB CCC
  8. 0 4 10 100
  9. 1 5 20 50
  10. 2 6 30 -30
  11. 3 7 40 -50

if-then…

在一列上执行 if-then 操作:

  1. In [3]: df.loc[df.AAA >= 5, 'BBB'] = -1
  2. In [4]: df
  3. Out[4]:
  4. AAA BBB CCC
  5. 0 4 10 100
  6. 1 5 -1 50
  7. 2 6 -1 -30
  8. 3 7 -1 -50

在两列上执行 if-then 操作:

  1. In [5]: df.loc[df.AAA >= 5, ['BBB', 'CCC']] = 555
  2. In [6]: df
  3. Out[6]:
  4. AAA BBB CCC
  5. 0 4 10 100
  6. 1 5 555 555
  7. 2 6 555 555
  8. 3 7 555 555

再添加一行代码,执行 -else 操作:

  1. In [7]: df.loc[df.AAA < 5, ['BBB', 'CCC']] = 2000
  2. In [8]: df
  3. Out[8]:
  4. AAA BBB CCC
  5. 0 4 2000 2000
  6. 1 5 555 555
  7. 2 6 555 555
  8. 3 7 555 555

或用 Pandas 的 where 设置掩码(mask):

  1. In [9]: df_mask = pd.DataFrame({'AAA': [True] * 4,
  2. ...: 'BBB': [False] * 4,
  3. ...: 'CCC': [True, False] * 2})
  4. ...:
  5. In [10]: df.where(df_mask, -1000)
  6. Out[10]:
  7. AAA BBB CCC
  8. 0 4 -1000 2000
  9. 1 5 -1000 -1000
  10. 2 6 -1000 555
  11. 3 7 -1000 -1000

用 NumPy where() 函数实现 if-then-else

  1. In [11]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
  2. ....: 'BBB': [10, 20, 30, 40],
  3. ....: 'CCC': [100, 50, -30, -50]})
  4. ....:
  5. In [12]: df
  6. Out[12]:
  7. AAA BBB CCC
  8. 0 4 10 100
  9. 1 5 20 50
  10. 2 6 30 -30
  11. 3 7 40 -50
  12. In [13]: df['logic'] = np.where(df['AAA'] > 5, 'high', 'low')
  13. In [14]: df
  14. Out[14]:
  15. AAA BBB CCC logic
  16. 0 4 10 100 low
  17. 1 5 20 50 low
  18. 2 6 30 -30 high
  19. 3 7 40 -50 high

切割

用布尔条件切割 DataFrame

  1. In [15]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
  2. ....: 'BBB': [10, 20, 30, 40],
  3. ....: 'CCC': [100, 50, -30, -50]})
  4. ....:
  5. In [16]: df
  6. Out[16]:
  7. AAA BBB CCC
  8. 0 4 10 100
  9. 1 5 20 50
  10. 2 6 30 -30
  11. 3 7 40 -50
  12. In [17]: df[df.AAA <= 5]
  13. Out[17]:
  14. AAA BBB CCC
  15. 0 4 10 100
  16. 1 5 20 50
  17. In [18]: df[df.AAA > 5]
  18. Out[18]:
  19. AAA BBB CCC
  20. 2 6 30 -30
  21. 3 7 40 -50

设置条件

多列条件选择

  1. In [19]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
  2. ....: 'BBB': [10, 20, 30, 40],
  3. ....: 'CCC': [100, 50, -30, -50]})
  4. ....:
  5. In [20]: df
  6. Out[20]:
  7. AAA BBB CCC
  8. 0 4 10 100
  9. 1 5 20 50
  10. 2 6 30 -30
  11. 3 7 40 -50

和(&),不赋值,直接返回 Series:

  1. In [21]: df.loc[(df['BBB'] < 25) & (df['CCC'] >= -40), 'AAA']
  2. Out[21]:
  3. 0 4
  4. 1 5
  5. Name: AAA, dtype: int64

或(|),不赋值,直接返回 Series:

  1. In [22]: df.loc[(df['BBB'] > 25) | (df['CCC'] >= -40), 'AAA']
  2. Out[22]:
  3. 0 4
  4. 1 5
  5. 2 6
  6. 3 7
  7. Name: AAA, dtype: int64

或(|),赋值,修改 DataFrame:

  1. In [23]: df.loc[(df['BBB'] > 25) | (df['CCC'] >= 75), 'AAA'] = 0.1
  2. In [24]: df
  3. Out[24]:
  4. AAA BBB CCC
  5. 0 0.1 10 100
  6. 1 5.0 20 50
  7. 2 0.1 30 -30
  8. 3 0.1 40 -50

用 argsort 选择最接近指定值的行

  1. In [25]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
  2. ....: 'BBB': [10, 20, 30, 40],
  3. ....: 'CCC': [100, 50, -30, -50]})
  4. ....:
  5. In [26]: df
  6. Out[26]:
  7. AAA BBB CCC
  8. 0 4 10 100
  9. 1 5 20 50
  10. 2 6 30 -30
  11. 3 7 40 -50
  12. In [27]: aValue = 43.0
  13. In [28]: df.loc[(df.CCC - aValue).abs().argsort()]
  14. Out[28]:
  15. AAA BBB CCC
  16. 1 5 20 50
  17. 0 4 10 100
  18. 2 6 30 -30
  19. 3 7 40 -50

用二进制运算符动态减少条件列表

  1. In [29]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
  2. ....: 'BBB': [10, 20, 30, 40],
  3. ....: 'CCC': [100, 50, -30, -50]})
  4. ....:
  5. In [30]: df
  6. Out[30]:
  7. AAA BBB CCC
  8. 0 4 10 100
  9. 1 5 20 50
  10. 2 6 30 -30
  11. 3 7 40 -50
  12. In [31]: Crit1 = df.AAA <= 5.5
  13. In [32]: Crit2 = df.BBB == 10.0
  14. In [33]: Crit3 = df.CCC > -40.0

硬编码方式为:

  1. In [34]: AllCrit = Crit1 & Crit2 & Crit3

生成动态条件列表:

  1. In [35]: import functools
  2. In [36]: CritList = [Crit1, Crit2, Crit3]
  3. In [37]: AllCrit = functools.reduce(lambda x, y: x & y, CritList)
  4. In [38]: df[AllCrit]
  5. Out[38]:
  6. AAA BBB CCC
  7. 0 4 10 100

选择

DataFrames

更多信息,请参阅索引文档。

行标签与值作为条件

  1. In [39]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
  2. ....: 'BBB': [10, 20, 30, 40],
  3. ....: 'CCC': [100, 50, -30, -50]})
  4. ....:
  5. In [40]: df
  6. Out[40]:
  7. AAA BBB CCC
  8. 0 4 10 100
  9. 1 5 20 50
  10. 2 6 30 -30
  11. 3 7 40 -50
  12. In [41]: df[(df.AAA <= 6) & (df.index.isin([0, 2, 4]))]
  13. Out[41]:
  14. AAA BBB CCC
  15. 0 4 10 100
  16. 2 6 30 -30

标签切片用 loc,位置切片用 iloc

  1. In [42]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
  2. ....: 'BBB': [10, 20, 30, 40],
  3. ....: 'CCC': [100, 50, -30, -50]},
  4. ....: index=['foo', 'bar', 'boo', 'kar'])
  5. ....:

前 2 个是显式切片方法,第 3 个是通用方法:

  1. 位置切片,Python 切片风格,不包括结尾数据;
  2. 标签切片,非 Python 切片风格,包括结尾数据;
  3. 通用切片,支持两种切片风格,取决于切片用的是标签还是位置。
  1. In [43]: df.loc['bar':'kar'] # Label
  2. Out[43]:
  3. AAA BBB CCC
  4. bar 5 20 50
  5. boo 6 30 -30
  6. kar 7 40 -50
  7. # Generic
  8. In [44]: df.iloc[0:3]
  9. Out[44]:
  10. AAA BBB CCC
  11. foo 4 10 100
  12. bar 5 20 50
  13. boo 6 30 -30
  14. In [45]: df.loc['bar':'kar']
  15. Out[45]:
  16. AAA BBB CCC
  17. bar 5 20 50
  18. boo 6 30 -30
  19. kar 7 40 -50

包含整数,且不从 0 开始的索引,或不是逐步递增的索引会引发歧义。

  1. In [46]: data = {'AAA': [4, 5, 6, 7],
  2. ....: 'BBB': [10, 20, 30, 40],
  3. ....: 'CCC': [100, 50, -30, -50]}
  4. ....:
  5. In [47]: df2 = pd.DataFrame(data=data, index=[1, 2, 3, 4]) # Note index starts at 1.
  6. In [48]: df2.iloc[1:3] # Position-oriented
  7. Out[48]:
  8. AAA BBB CCC
  9. 2 5 20 50
  10. 3 6 30 -30
  11. In [49]: df2.loc[1:3] # Label-oriented
  12. Out[49]:
  13. AAA BBB CCC
  14. 1 4 10 100
  15. 2 5 20 50
  16. 3 6 30 -30

用逆运算符 (~)提取掩码的反向内容

  1. In [50]: df = pd.DataFrame({'AAA': [4, 5, 6, 7],
  2. ....: 'BBB': [10, 20, 30, 40],
  3. ....: 'CCC': [100, 50, -30, -50]})
  4. ....:
  5. In [51]: df
  6. Out[51]:
  7. AAA BBB CCC
  8. 0 4 10 100
  9. 1 5 20 50
  10. 2 6 30 -30
  11. 3 7 40 -50
  12. In [52]: df[~((df.AAA <= 6) & (df.index.isin([0, 2, 4])))]
  13. Out[52]:
  14. AAA BBB CCC
  15. 1 5 20 50
  16. 3 7 40 -50

生成新列

用 applymap 高效动态生成新列

  1. In [53]: df = pd.DataFrame({'AAA': [1, 2, 1, 3],
  2. ....: 'BBB': [1, 1, 2, 2],
  3. ....: 'CCC': [2, 1, 3, 1]})
  4. ....:
  5. In [54]: df
  6. Out[54]:
  7. AAA BBB CCC
  8. 0 1 1 2
  9. 1 2 1 1
  10. 2 1 2 3
  11. 3 3 2 1
  12. In [55]: source_cols = df.columns # Or some subset would work too
  13. In [56]: new_cols = [str(x) + "_cat" for x in source_cols]
  14. In [57]: categories = {1: 'Alpha', 2: 'Beta', 3: 'Charlie'}
  15. In [58]: df[new_cols] = df[source_cols].applymap(categories.get)
  16. In [59]: df
  17. Out[59]:
  18. AAA BBB CCC AAA_cat BBB_cat CCC_cat
  19. 0 1 1 2 Alpha Alpha Beta
  20. 1 2 1 1 Beta Alpha Alpha
  21. 2 1 2 3 Alpha Beta Charlie
  22. 3 3 2 1 Charlie Beta Alpha

分组时用 min()

  1. In [60]: df = pd.DataFrame({'AAA': [1, 1, 1, 2, 2, 2, 3, 3],
  2. ....: 'BBB': [2, 1, 3, 4, 5, 1, 2, 3]})
  3. ....:
  4. In [61]: df
  5. Out[61]:
  6. AAA BBB
  7. 0 1 2
  8. 1 1 1
  9. 2 1 3
  10. 3 2 4
  11. 4 2 5
  12. 5 2 1
  13. 6 3 2
  14. 7 3 3

方法1:用 idxmin() 提取每组最小值的索引

  1. In [62]: df.loc[df.groupby("AAA")["BBB"].idxmin()]
  2. Out[62]:
  3. AAA BBB
  4. 1 1 1
  5. 5 2 1
  6. 6 3 2

方法 2:先排序,再提取每组的第一个值

  1. In [63]: df.sort_values(by="BBB").groupby("AAA", as_index=False).first()
  2. Out[63]:
  3. AAA BBB
  4. 0 1 1
  5. 1 2 1
  6. 2 3 2

注意,提取的数据一样,但索引不一样。

多层索引

更多信息,请参阅多层索引文档。

用带标签的字典创建多层索引

  1. In [64]: df = pd.DataFrame({'row': [0, 1, 2],
  2. ....: 'One_X': [1.1, 1.1, 1.1],
  3. ....: 'One_Y': [1.2, 1.2, 1.2],
  4. ....: 'Two_X': [1.11, 1.11, 1.11],
  5. ....: 'Two_Y': [1.22, 1.22, 1.22]})
  6. ....:
  7. In [65]: df
  8. Out[65]:
  9. row One_X One_Y Two_X Two_Y
  10. 0 0 1.1 1.2 1.11 1.22
  11. 1 1 1.1 1.2 1.11 1.22
  12. 2 2 1.1 1.2 1.11 1.22
  13. # 设置索引标签
  14. In [66]: df = df.set_index('row')
  15. In [67]: df
  16. Out[67]:
  17. One_X One_Y Two_X Two_Y
  18. row
  19. 0 1.1 1.2 1.11 1.22
  20. 1 1.1 1.2 1.11 1.22
  21. 2 1.1 1.2 1.11 1.22
  22. # 多层索引的列
  23. In [68]: df.columns = pd.MultiIndex.from_tuples([tuple(c.split('_'))
  24. ....: for c in df.columns])
  25. ....:
  26. In [69]: df
  27. Out[69]:
  28. One Two
  29. X Y X Y
  30. row
  31. 0 1.1 1.2 1.11 1.22
  32. 1 1.1 1.2 1.11 1.22
  33. 2 1.1 1.2 1.11 1.22
  34. # 先 stack,然后 Reset 索引
  35. In [70]: df = df.stack(0).reset_index(1)
  36. In [71]: df
  37. Out[71]:
  38. level_1 X Y
  39. row
  40. 0 One 1.10 1.20
  41. 0 Two 1.11 1.22
  42. 1 One 1.10 1.20
  43. 1 Two 1.11 1.22
  44. 2 One 1.10 1.20
  45. 2 Two 1.11 1.22
  46. # 修整标签,注意自动添加了标签 `level_1`
  47. In [72]: df.columns = ['Sample', 'All_X', 'All_Y']
  48. In [73]: df
  49. Out[73]:
  50. Sample All_X All_Y
  51. row
  52. 0 One 1.10 1.20
  53. 0 Two 1.11 1.22
  54. 1 One 1.10 1.20
  55. 1 Two 1.11 1.22
  56. 2 One 1.10 1.20
  57. 2 Two 1.11 1.22

运算

多层索引运算要用广播机制

  1. In [74]: cols = pd.MultiIndex.from_tuples([(x, y) for x in ['A', 'B', 'C']
  2. ....: for y in ['O', 'I']])
  3. ....:
  4. In [75]: df = pd.DataFrame(np.random.randn(2, 6), index=['n', 'm'], columns=cols)
  5. In [76]: df
  6. Out[76]:
  7. A B C
  8. O I O I O I
  9. n 0.469112 -0.282863 -1.509059 -1.135632 1.212112 -0.173215
  10. m 0.119209 -1.044236 -0.861849 -2.104569 -0.494929 1.071804
  11. In [77]: df = df.div(df['C'], level=1)
  12. In [78]: df
  13. Out[78]:
  14. A B C
  15. O I O I O I
  16. n 0.387021 1.633022 -1.244983 6.556214 1.0 1.0
  17. m -0.240860 -0.974279 1.741358 -1.963577 1.0 1.0

切片

用 xs 切片多层索引

  1. In [79]: coords = [('AA', 'one'), ('AA', 'six'), ('BB', 'one'), ('BB', 'two'),
  2. ....: ('BB', 'six')]
  3. ....:
  4. In [80]: index = pd.MultiIndex.from_tuples(coords)
  5. In [81]: df = pd.DataFrame([11, 22, 33, 44, 55], index, ['MyData'])
  6. In [82]: df
  7. Out[82]:
  8. MyData
  9. AA one 11
  10. six 22
  11. BB one 33
  12. two 44
  13. six 55

提取第一层与索引第一个轴的交叉数据:

  1. # 注意:level 与 axis 是可选项,默认为 0
  2. In [83]: df.xs('BB', level=0, axis=0)
  3. Out[83]:
  4. MyData
  5. one 33
  6. two 44
  7. six 55

……现在是第 1 个轴的第 2 层

  1. In [84]: df.xs('six', level=1, axis=0)
  2. Out[84]:
  3. MyData
  4. AA 22
  5. BB 55

用 xs 切片多层索引,方法 #2

  1. In [85]: import itertools
  2. In [86]: index = list(itertools.product(['Ada', 'Quinn', 'Violet'],
  3. ....: ['Comp', 'Math', 'Sci']))
  4. ....:
  5. In [87]: headr = list(itertools.product(['Exams', 'Labs'], ['I', 'II']))
  6. In [88]: indx = pd.MultiIndex.from_tuples(index, names=['Student', 'Course'])
  7. In [89]: cols = pd.MultiIndex.from_tuples(headr) # Notice these are un-named
  8. In [90]: data = [[70 + x + y + (x * y) % 3 for x in range(4)] for y in range(9)]
  9. In [91]: df = pd.DataFrame(data, indx, cols)
  10. In [92]: df
  11. Out[92]:
  12. Exams Labs
  13. I II I II
  14. Student Course
  15. Ada Comp 70 71 72 73
  16. Math 71 73 75 74
  17. Sci 72 75 75 75
  18. Quinn Comp 73 74 75 76
  19. Math 74 76 78 77
  20. Sci 75 78 78 78
  21. Violet Comp 76 77 78 79
  22. Math 77 79 81 80
  23. Sci 78 81 81 81
  24. In [93]: All = slice(None)
  25. In [94]: df.loc['Violet']
  26. Out[94]:
  27. Exams Labs
  28. I II I II
  29. Course
  30. Comp 76 77 78 79
  31. Math 77 79 81 80
  32. Sci 78 81 81 81
  33. In [95]: df.loc[(All, 'Math'), All]
  34. Out[95]:
  35. Exams Labs
  36. I II I II
  37. Student Course
  38. Ada Math 71 73 75 74
  39. Quinn Math 74 76 78 77
  40. Violet Math 77 79 81 80
  41. In [96]: df.loc[(slice('Ada', 'Quinn'), 'Math'), All]
  42. Out[96]:
  43. Exams Labs
  44. I II I II
  45. Student Course
  46. Ada Math 71 73 75 74
  47. Quinn Math 74 76 78 77
  48. In [97]: df.loc[(All, 'Math'), ('Exams')]
  49. Out[97]:
  50. I II
  51. Student Course
  52. Ada Math 71 73
  53. Quinn Math 74 76
  54. Violet Math 77 79
  55. In [98]: df.loc[(All, 'Math'), (All, 'II')]
  56. Out[98]:
  57. Exams Labs
  58. II II
  59. Student Course
  60. Ada Math 73 74
  61. Quinn Math 76 77
  62. Violet Math 79 80

用 xs 设置多层索引比例

排序

用多层索引按指定列或列序列表排序x

  1. In [99]: df.sort_values(by=('Labs', 'II'), ascending=False)
  2. Out[99]:
  3. Exams Labs
  4. I II I II
  5. Student Course
  6. Violet Sci 78 81 81 81
  7. Math 77 79 81 80
  8. Comp 76 77 78 79
  9. Quinn Sci 75 78 78 78
  10. Math 74 76 78 77
  11. Comp 73 74 75 76
  12. Ada Sci 72 75 75 75
  13. Math 71 73 75 74
  14. Comp 70 71 72 73

部分选择,需要排序

层级

为多层索引添加一层

平铺结构化列

缺失数据

缺失数据 文档。

向前填充逆序时间序列。

  1. In [100]: df = pd.DataFrame(np.random.randn(6, 1),
  2. .....: index=pd.date_range('2013-08-01', periods=6, freq='B'),
  3. .....: columns=list('A'))
  4. .....:
  5. In [101]: df.loc[df.index[3], 'A'] = np.nan
  6. In [102]: df
  7. Out[102]:
  8. A
  9. 2013-08-01 0.721555
  10. 2013-08-02 -0.706771
  11. 2013-08-05 -1.039575
  12. 2013-08-06 NaN
  13. 2013-08-07 -0.424972
  14. 2013-08-08 0.567020
  15. In [103]: df.reindex(df.index[::-1]).ffill()
  16. Out[103]:
  17. A
  18. 2013-08-08 0.567020
  19. 2013-08-07 -0.424972
  20. 2013-08-06 -0.424972
  21. 2013-08-05 -1.039575
  22. 2013-08-02 -0.706771
  23. 2013-08-01 0.721555

空值时重置为 0,有值时累加

替换

用反引用替换

分组

分组 文档。

用 apply 执行分组基础操作

与聚合不同,传递给 DataFrame 子集的 apply 可回调,可以访问所有列。

  1. In [104]: df = pd.DataFrame({'animal': 'cat dog cat fish dog cat cat'.split(),
  2. .....: 'size': list('SSMMMLL'),
  3. .....: 'weight': [8, 10, 11, 1, 20, 12, 12],
  4. .....: 'adult': [False] * 5 + [True] * 2})
  5. .....:
  6. In [105]: df
  7. Out[105]:
  8. animal size weight adult
  9. 0 cat S 8 False
  10. 1 dog S 10 False
  11. 2 cat M 11 False
  12. 3 fish M 1 False
  13. 4 dog M 20 False
  14. 5 cat L 12 True
  15. 6 cat L 12 True
  16. # 提取 size 列最重的动物列表
  17. In [106]: df.groupby('animal').apply(lambda subf: subf['size'][subf['weight'].idxmax()])
  18. Out[106]:
  19. animal
  20. cat L
  21. dog M
  22. fish M
  23. dtype: object

使用 get_group

  1. In [107]: gb = df.groupby(['animal'])
  2. In [108]: gb.get_group('cat')
  3. Out[108]:
  4. animal size weight adult
  5. 0 cat S 8 False
  6. 2 cat M 11 False
  7. 5 cat L 12 True
  8. 6 cat L 12 True

为同一分组的不同内容使用 Apply 函数

  1. In [109]: def GrowUp(x):
  2. .....: avg_weight = sum(x[x['size'] == 'S'].weight * 1.5)
  3. .....: avg_weight += sum(x[x['size'] == 'M'].weight * 1.25)
  4. .....: avg_weight += sum(x[x['size'] == 'L'].weight)
  5. .....: avg_weight /= len(x)
  6. .....: return pd.Series(['L', avg_weight, True],
  7. .....: index=['size', 'weight', 'adult'])
  8. .....:
  9. In [110]: expected_df = gb.apply(GrowUp)
  10. In [111]: expected_df
  11. Out[111]:
  12. size weight adult
  13. animal
  14. cat L 12.4375 True
  15. dog L 20.0000 True
  16. fish L 1.2500 True

Apply 函数扩展

  1. In [112]: S = pd.Series([i / 100.0 for i in range(1, 11)])
  2. In [113]: def cum_ret(x, y):
  3. .....: return x * (1 + y)
  4. .....:
  5. In [114]: def red(x):
  6. .....: return functools.reduce(cum_ret, x, 1.0)
  7. .....:
  8. In [115]: S.expanding().apply(red, raw=True)
  9. Out[115]:
  10. 0 1.010000
  11. 1 1.030200
  12. 2 1.061106
  13. 3 1.103550
  14. 4 1.158728
  15. 5 1.228251
  16. 6 1.314229
  17. 7 1.419367
  18. 8 1.547110
  19. 9 1.701821
  20. dtype: float64

用分组里的剩余值的平均值进行替换

  1. In [116]: df = pd.DataFrame({'A': [1, 1, 2, 2], 'B': [1, -1, 1, 2]})
  2. In [117]: gb = df.groupby('A')
  3. In [118]: def replace(g):
  4. .....: mask = g < 0
  5. .....: return g.where(mask, g[~mask].mean())
  6. .....:
  7. In [119]: gb.transform(replace)
  8. Out[119]:
  9. B
  10. 0 1.0
  11. 1 -1.0
  12. 2 1.5
  13. 3 1.5

按聚合数据排序

  1. In [120]: df = pd.DataFrame({'code': ['foo', 'bar', 'baz'] * 2,
  2. .....: 'data': [0.16, -0.21, 0.33, 0.45, -0.59, 0.62],
  3. .....: 'flag': [False, True] * 3})
  4. .....:
  5. In [121]: code_groups = df.groupby('code')
  6. In [122]: agg_n_sort_order = code_groups[['data']].transform(sum).sort_values(by='data')
  7. In [123]: sorted_df = df.loc[agg_n_sort_order.index]
  8. In [124]: sorted_df
  9. Out[124]:
  10. code data flag
  11. 1 bar -0.21 True
  12. 4 bar -0.59 False
  13. 0 foo 0.16 False
  14. 3 foo 0.45 True
  15. 2 baz 0.33 False
  16. 5 baz 0.62 True

创建多个聚合列

  1. In [125]: rng = pd.date_range(start="2014-10-07", periods=10, freq='2min')
  2. In [126]: ts = pd.Series(data=list(range(10)), index=rng)
  3. In [127]: def MyCust(x):
  4. .....: if len(x) > 2:
  5. .....: return x[1] * 1.234
  6. .....: return pd.NaT
  7. .....:
  8. In [128]: mhc = {'Mean': np.mean, 'Max': np.max, 'Custom': MyCust}
  9. In [129]: ts.resample("5min").apply(mhc)
  10. Out[129]:
  11. Mean 2014-10-07 00:00:00 1
  12. 2014-10-07 00:05:00 3.5
  13. 2014-10-07 00:10:00 6
  14. 2014-10-07 00:15:00 8.5
  15. Max 2014-10-07 00:00:00 2
  16. 2014-10-07 00:05:00 4
  17. 2014-10-07 00:10:00 7
  18. 2014-10-07 00:15:00 9
  19. Custom 2014-10-07 00:00:00 1.234
  20. 2014-10-07 00:05:00 NaT
  21. 2014-10-07 00:10:00 7.404
  22. 2014-10-07 00:15:00 NaT
  23. dtype: object
  24. In [130]: ts
  25. Out[130]:
  26. 2014-10-07 00:00:00 0
  27. 2014-10-07 00:02:00 1
  28. 2014-10-07 00:04:00 2
  29. 2014-10-07 00:06:00 3
  30. 2014-10-07 00:08:00 4
  31. 2014-10-07 00:10:00 5
  32. 2014-10-07 00:12:00 6
  33. 2014-10-07 00:14:00 7
  34. 2014-10-07 00:16:00 8
  35. 2014-10-07 00:18:00 9
  36. Freq: 2T, dtype: int64

为 DataFrame 创建值计数列

  1. In [131]: df = pd.DataFrame({'Color': 'Red Red Red Blue'.split(),
  2. .....: 'Value': [100, 150, 50, 50]})
  3. .....:
  4. In [132]: df
  5. Out[132]:
  6. Color Value
  7. 0 Red 100
  8. 1 Red 150
  9. 2 Red 50
  10. 3 Blue 50
  11. In [133]: df['Counts'] = df.groupby(['Color']).transform(len)
  12. In [134]: df
  13. Out[134]:
  14. Color Value Counts
  15. 0 Red 100 3
  16. 1 Red 150 3
  17. 2 Red 50 3
  18. 3 Blue 50 1

基于索引唯一某列不同分组的值

  1. In [135]: df = pd.DataFrame({'line_race': [10, 10, 8, 10, 10, 8],
  2. .....: 'beyer': [99, 102, 103, 103, 88, 100]},
  3. .....: index=['Last Gunfighter', 'Last Gunfighter',
  4. .....: 'Last Gunfighter', 'Paynter', 'Paynter',
  5. .....: 'Paynter'])
  6. .....:
  7. In [136]: df
  8. Out[136]:
  9. line_race beyer
  10. Last Gunfighter 10 99
  11. Last Gunfighter 10 102
  12. Last Gunfighter 8 103
  13. Paynter 10 103
  14. Paynter 10 88
  15. Paynter 8 100
  16. In [137]: df['beyer_shifted'] = df.groupby(level=0)['beyer'].shift(1)
  17. In [138]: df
  18. Out[138]:
  19. line_race beyer beyer_shifted
  20. Last Gunfighter 10 99 NaN
  21. Last Gunfighter 10 102 99.0
  22. Last Gunfighter 8 103 102.0
  23. Paynter 10 103 NaN
  24. Paynter 10 88 103.0
  25. Paynter 8 100 88.0

选择每组最大值的行

  1. In [139]: df = pd.DataFrame({'host': ['other', 'other', 'that', 'this', 'this'],
  2. .....: 'service': ['mail', 'web', 'mail', 'mail', 'web'],
  3. .....: 'no': [1, 2, 1, 2, 1]}).set_index(['host', 'service'])
  4. .....:
  5. In [140]: mask = df.groupby(level=0).agg('idxmax')
  6. In [141]: df_count = df.loc[mask['no']].reset_index()
  7. In [142]: df_count
  8. Out[142]:
  9. host service no
  10. 0 other web 2
  11. 1 that mail 1
  12. 2 this mail 2

Python itertools.groupby 式分组

  1. In [143]: df = pd.DataFrame([0, 1, 0, 1, 1, 1, 0, 1, 1], columns=['A'])
  2. In [144]: df.A.groupby((df.A != df.A.shift()).cumsum()).groups
  3. Out[144]:
  4. {1: Int64Index([0], dtype='int64'),
  5. 2: Int64Index([1], dtype='int64'),
  6. 3: Int64Index([2], dtype='int64'),
  7. 4: Int64Index([3, 4, 5], dtype='int64'),
  8. 5: Int64Index([6], dtype='int64'),
  9. 6: Int64Index([7, 8], dtype='int64')}
  10. In [145]: df.A.groupby((df.A != df.A.shift()).cumsum()).cumsum()
  11. Out[145]:
  12. 0 0
  13. 1 1
  14. 2 0
  15. 3 1
  16. 4 2
  17. 5 3
  18. 6 0
  19. 7 1
  20. 8 2
  21. Name: A, dtype: int64

扩展数据

Alignment and to-date

基于计数值进行移动窗口计算

按时间间隔计算滚动平均

分割

分割 DataFrame

按指定逻辑,将不同的行,分割成 DataFrame 列表。

  1. In [146]: df = pd.DataFrame(data={'Case': ['A', 'A', 'A', 'B', 'A', 'A', 'B', 'A',
  2. .....: 'A'],
  3. .....: 'Data': np.random.randn(9)})
  4. .....:
  5. In [147]: dfs = list(zip(*df.groupby((1 * (df['Case'] == 'B')).cumsum()
  6. .....: .rolling(window=3, min_periods=1).median())))[-1]
  7. .....:
  8. In [148]: dfs[0]
  9. Out[148]:
  10. Case Data
  11. 0 A 0.276232
  12. 1 A -1.087401
  13. 2 A -0.673690
  14. 3 B 0.113648
  15. In [149]: dfs[1]
  16. Out[149]:
  17. Case Data
  18. 4 A -1.478427
  19. 5 A 0.524988
  20. 6 B 0.404705
  21. In [150]: dfs[2]
  22. Out[150]:
  23. Case Data
  24. 7 A 0.577046
  25. 8 A -1.715002

透视表

透视表 文档。

部分汇总与小计

  1. In [151]: df = pd.DataFrame(data={'Province': ['ON', 'QC', 'BC', 'AL', 'AL', 'MN', 'ON'],
  2. .....: 'City': ['Toronto', 'Montreal', 'Vancouver',
  3. .....: 'Calgary', 'Edmonton', 'Winnipeg',
  4. .....: 'Windsor'],
  5. .....: 'Sales': [13, 6, 16, 8, 4, 3, 1]})
  6. .....:
  7. In [152]: table = pd.pivot_table(df, values=['Sales'], index=['Province'],
  8. .....: columns=['City'], aggfunc=np.sum, margins=True)
  9. .....:
  10. In [153]: table.stack('City')
  11. Out[153]:
  12. Sales
  13. Province City
  14. AL All 12.0
  15. Calgary 8.0
  16. Edmonton 4.0
  17. BC All 16.0
  18. Vancouver 16.0
  19. ... ...
  20. All Montreal 6.0
  21. Toronto 13.0
  22. Vancouver 16.0
  23. Windsor 1.0
  24. Winnipeg 3.0
  25. [20 rows x 1 columns]

类似 R 的 plyr 频率表

  1. In [154]: grades = [48, 99, 75, 80, 42, 80, 72, 68, 36, 78]
  2. In [155]: df = pd.DataFrame({'ID': ["x%d" % r for r in range(10)],
  3. .....: 'Gender': ['F', 'M', 'F', 'M', 'F',
  4. .....: 'M', 'F', 'M', 'M', 'M'],
  5. .....: 'ExamYear': ['2007', '2007', '2007', '2008', '2008',
  6. .....: '2008', '2008', '2009', '2009', '2009'],
  7. .....: 'Class': ['algebra', 'stats', 'bio', 'algebra',
  8. .....: 'algebra', 'stats', 'stats', 'algebra',
  9. .....: 'bio', 'bio'],
  10. .....: 'Participated': ['yes', 'yes', 'yes', 'yes', 'no',
  11. .....: 'yes', 'yes', 'yes', 'yes', 'yes'],
  12. .....: 'Passed': ['yes' if x > 50 else 'no' for x in grades],
  13. .....: 'Employed': [True, True, True, False,
  14. .....: False, False, False, True, True, False],
  15. .....: 'Grade': grades})
  16. .....:
  17. In [156]: df.groupby('ExamYear').agg({'Participated': lambda x: x.value_counts()['yes'],
  18. .....: 'Passed': lambda x: sum(x == 'yes'),
  19. .....: 'Employed': lambda x: sum(x),
  20. .....: 'Grade': lambda x: sum(x) / len(x)})
  21. .....:
  22. Out[156]:
  23. Participated Passed Employed Grade
  24. ExamYear
  25. 2007 3 2 3 74.000000
  26. 2008 3 3 0 68.500000
  27. 2009 3 2 2 60.666667

按年生成 DataFrame

跨列表创建年月:

  1. In [157]: df = pd.DataFrame({'value': np.random.randn(36)},
  2. .....: index=pd.date_range('2011-01-01', freq='M', periods=36))
  3. .....:
  4. In [158]: pd.pivot_table(df, index=df.index.month, columns=df.index.year,
  5. .....: values='value', aggfunc='sum')
  6. .....:
  7. Out[158]:
  8. 2011 2012 2013
  9. 1 -1.039268 -0.968914 2.565646
  10. 2 -0.370647 -1.294524 1.431256
  11. 3 -1.157892 0.413738 1.340309
  12. 4 -1.344312 0.276662 -1.170299
  13. 5 0.844885 -0.472035 -0.226169
  14. 6 1.075770 -0.013960 0.410835
  15. 7 -0.109050 -0.362543 0.813850
  16. 8 1.643563 -0.006154 0.132003
  17. 9 -1.469388 -0.923061 -0.827317
  18. 10 0.357021 0.895717 -0.076467
  19. 11 -0.674600 0.805244 -1.187678
  20. 12 -1.776904 -1.206412 1.130127

Apply 函数

把嵌入列表转换为多层索引 DataFrame

  1. In [159]: df = pd.DataFrame(data={'A': [[2, 4, 8, 16], [100, 200], [10, 20, 30]],
  2. .....: 'B': [['a', 'b', 'c'], ['jj', 'kk'], ['ccc']]},
  3. .....: index=['I', 'II', 'III'])
  4. .....:
  5. In [160]: def SeriesFromSubList(aList):
  6. .....: return pd.Series(aList)
  7. .....:
  8. In [161]: df_orgz = pd.concat({ind: row.apply(SeriesFromSubList)
  9. .....: for ind, row in df.iterrows()})
  10. .....:
  11. In [162]: df_orgz
  12. Out[162]:
  13. 0 1 2 3
  14. I A 2 4 8 16.0
  15. B a b c NaN
  16. II A 100 200 NaN NaN
  17. B jj kk NaN NaN
  18. III A 10 20 30 NaN
  19. B ccc NaN NaN NaN

返回 Series

Rolling Apply to multiple columns where function calculates a Series before a Scalar from the Series is returned

  1. In [163]: df = pd.DataFrame(data=np.random.randn(2000, 2) / 10000,
  2. .....: index=pd.date_range('2001-01-01', periods=2000),
  3. .....: columns=['A', 'B'])
  4. .....:
  5. In [164]: df
  6. Out[164]:
  7. A B
  8. 2001-01-01 -0.000144 -0.000141
  9. 2001-01-02 0.000161 0.000102
  10. 2001-01-03 0.000057 0.000088
  11. 2001-01-04 -0.000221 0.000097
  12. 2001-01-05 -0.000201 -0.000041
  13. ... ... ...
  14. 2006-06-19 0.000040 -0.000235
  15. 2006-06-20 -0.000123 -0.000021
  16. 2006-06-21 -0.000113 0.000114
  17. 2006-06-22 0.000136 0.000109
  18. 2006-06-23 0.000027 0.000030
  19. [2000 rows x 2 columns]
  20. In [165]: def gm(df, const):
  21. .....: v = ((((df.A + df.B) + 1).cumprod()) - 1) * const
  22. .....: return v.iloc[-1]
  23. .....:
  24. In [166]: s = pd.Series({df.index[i]: gm(df.iloc[i:min(i + 51, len(df) - 1)], 5)
  25. .....: for i in range(len(df) - 50)})
  26. .....:
  27. In [167]: s
  28. Out[167]:
  29. 2001-01-01 0.000930
  30. 2001-01-02 0.002615
  31. 2001-01-03 0.001281
  32. 2001-01-04 0.001117
  33. 2001-01-05 0.002772
  34. ...
  35. 2006-04-30 0.003296
  36. 2006-05-01 0.002629
  37. 2006-05-02 0.002081
  38. 2006-05-03 0.004247
  39. 2006-05-04 0.003928
  40. Length: 1950, dtype: float64

返回标量值

Rolling Apply to multiple columns where function returns a Scalar (Volume Weighted Average Price) 对多列执行滚动 Apply,函数返回标量值(成交价加权平均价)

  1. In [168]: rng = pd.date_range(start='2014-01-01', periods=100)
  2. In [169]: df = pd.DataFrame({'Open': np.random.randn(len(rng)),
  3. .....: 'Close': np.random.randn(len(rng)),
  4. .....: 'Volume': np.random.randint(100, 2000, len(rng))},
  5. .....: index=rng)
  6. .....:
  7. In [170]: df
  8. Out[170]:
  9. Open Close Volume
  10. 2014-01-01 -1.611353 -0.492885 1219
  11. 2014-01-02 -3.000951 0.445794 1054
  12. 2014-01-03 -0.138359 -0.076081 1381
  13. 2014-01-04 0.301568 1.198259 1253
  14. 2014-01-05 0.276381 -0.669831 1728
  15. ... ... ... ...
  16. 2014-04-06 -0.040338 0.937843 1188
  17. 2014-04-07 0.359661 -0.285908 1864
  18. 2014-04-08 0.060978 1.714814 941
  19. 2014-04-09 1.759055 -0.455942 1065
  20. 2014-04-10 0.138185 -1.147008 1453
  21. [100 rows x 3 columns]
  22. In [171]: def vwap(bars):
  23. .....: return ((bars.Close * bars.Volume).sum() / bars.Volume.sum())
  24. .....:
  25. In [172]: window = 5
  26. In [173]: s = pd.concat([(pd.Series(vwap(df.iloc[i:i + window]),
  27. .....: index=[df.index[i + window]]))
  28. .....: for i in range(len(df) - window)])
  29. .....:
  30. In [174]: s.round(2)
  31. Out[174]:
  32. 2014-01-06 0.02
  33. 2014-01-07 0.11
  34. 2014-01-08 0.10
  35. 2014-01-09 0.07
  36. 2014-01-10 -0.29
  37. ...
  38. 2014-04-06 -0.63
  39. 2014-04-07 -0.02
  40. 2014-04-08 -0.03
  41. 2014-04-09 0.34
  42. 2014-04-10 0.29
  43. Length: 95, dtype: float64

时间序列

删除指定时间之外的数据

用 indexer 提取在时间范围内的数据

创建不包括周末,且只包含指定时间的日期时间范围

矢量查询

聚合与绘制时间序列

把以小时为列,天为行的矩阵转换为连续的时间序列。 如何重排 DataFrame?

重建索引为指定频率时,如何处理重复值

为 DatetimeIndex 里每条记录计算当月第一天

  1. In [175]: dates = pd.date_range('2000-01-01', periods=5)
  2. In [176]: dates.to_period(freq='M').to_timestamp()
  3. Out[176]:
  4. DatetimeIndex(['2000-01-01', '2000-01-01', '2000-01-01', '2000-01-01',
  5. '2000-01-01'],
  6. dtype='datetime64[ns]', freq=None)

重采样

重采样 文档。

用 Grouper 代替 TimeGrouper 处理时间分组的值

含缺失值的时间分组

Grouper 的有效时间频率参数

用多层索引分组

用 TimeGrouper 与另一个分组创建子分组,再 Apply 自定义函数

按自定义时间段重采样

不添加新日期,重采样某日数据

按分钟重采样数据

分组重采样

合并

连接 docs. The Join文档。

模拟 R 的 rbind:追加两个重叠索引的 DataFrame

  1. In [177]: rng = pd.date_range('2000-01-01', periods=6)
  2. In [178]: df1 = pd.DataFrame(np.random.randn(6, 3), index=rng, columns=['A', 'B', 'C'])
  3. In [179]: df2 = df1.copy()

基于 df 构建器,需要ignore_index

  1. In [180]: df = df1.append(df2, ignore_index=True)
  2. In [181]: df
  3. Out[181]:
  4. A B C
  5. 0 -0.870117 -0.479265 -0.790855
  6. 1 0.144817 1.726395 -0.464535
  7. 2 -0.821906 1.597605 0.187307
  8. 3 -0.128342 -1.511638 -0.289858
  9. 4 0.399194 -1.430030 -0.639760
  10. 5 1.115116 -2.012600 1.810662
  11. 6 -0.870117 -0.479265 -0.790855
  12. 7 0.144817 1.726395 -0.464535
  13. 8 -0.821906 1.597605 0.187307
  14. 9 -0.128342 -1.511638 -0.289858
  15. 10 0.399194 -1.430030 -0.639760
  16. 11 1.115116 -2.012600 1.810662

自连接 DataFrame

  1. In [182]: df = pd.DataFrame(data={'Area': ['A'] * 5 + ['C'] * 2,
  2. .....: 'Bins': [110] * 2 + [160] * 3 + [40] * 2,
  3. .....: 'Test_0': [0, 1, 0, 1, 2, 0, 1],
  4. .....: 'Data': np.random.randn(7)})
  5. .....:
  6. In [183]: df
  7. Out[183]:
  8. Area Bins Test_0 Data
  9. 0 A 110 0 -0.433937
  10. 1 A 110 1 -0.160552
  11. 2 A 160 0 0.744434
  12. 3 A 160 1 1.754213
  13. 4 A 160 2 0.000850
  14. 5 C 40 0 0.342243
  15. 6 C 40 1 1.070599
  16. In [184]: df['Test_1'] = df['Test_0'] - 1
  17. In [185]: pd.merge(df, df, left_on=['Bins', 'Area', 'Test_0'],
  18. .....: right_on=['Bins', 'Area', 'Test_1'],
  19. .....: suffixes=('_L', '_R'))
  20. .....:
  21. Out[185]:
  22. Area Bins Test_0_L Data_L Test_1_L Test_0_R Data_R Test_1_R
  23. 0 A 110 0 -0.433937 -1 1 -0.160552 0
  24. 1 A 160 0 0.744434 -1 1 1.754213 0
  25. 2 A 160 1 1.754213 0 2 0.000850 1
  26. 3 C 40 0 0.342243 -1 1 1.070599 0

如何设置索引与连接

KDB 式的 asof 连接

基于符合条件的值进行连接

基于范围里的值,用 searchsorted 合并

可视化

可视化 文档。

让 Matplotlib 看上去像 R

设置 x 轴的主次标签

在 iPython Notebook 里创建多个可视图

创建多行可视图

绘制热力图

标记时间序列图

标记时间序列图 #2

用 Pandas、Vincent、xlsxwriter 生成 Excel 文件里的嵌入可视图

为分层变量的每个四分位数绘制箱型图

  1. In [186]: df = pd.DataFrame(
  2. .....: {'stratifying_var': np.random.uniform(0, 100, 20),
  3. .....: 'price': np.random.normal(100, 5, 20)})
  4. .....:
  5. In [187]: df['quartiles'] = pd.qcut(
  6. .....: df['stratifying_var'],
  7. .....: 4,
  8. .....: labels=['0-25%', '25-50%', '50-75%', '75-100%'])
  9. .....:
  10. In [188]: df.boxplot(column='price', by='quartiles')
  11. Out[188]: <matplotlib.axes._subplots.AxesSubplot at 0x7efff077f910>

../_images/quartile_boxplot.png

数据输入输出

SQL 与 HDF5 性能对比

CSV

CSV文档

read_csv 函数实战

把 DataFrame 追加到 CSV 文件

分块读取 CSV

分块读取指定的行

只读取 DataFrame 的前几列

读取不是 gzip 或 bz2 压缩(read_csv 可识别的内置压缩格式)的文件。本例在介绍如何读取 WinZip 压缩文件的同时,还介绍了在环境管理器里打开文件,并读取内容的通用操作方式。详见本链接

推断文件数据类型

处理出错数据

处理出错数据 II

用 Unix 时间戳读取 CSV,并转为本地时区

写入多行索引 CSV 时,不写入重复值

从多个文件读取数据,创建单个 DataFrame

最好的方式是先一个个读取单个文件,然后再把每个文件的内容存成列表,再用 pd.concat() 组合成一个 DataFrame:

  1. In [189]: for i in range(3):
  2. .....: data = pd.DataFrame(np.random.randn(10, 4))
  3. .....: data.to_csv('file_{}.csv'.format(i))
  4. .....:
  5. In [190]: files = ['file_0.csv', 'file_1.csv', 'file_2.csv']
  6. In [191]: result = pd.concat([pd.read_csv(f) for f in files], ignore_index=True)

还可以用同样的方法读取所有匹配同一模式的文件,下面这个例子使用的是glob

  1. In [192]: import glob
  2. In [193]: import os
  3. In [194]: files = glob.glob('file_*.csv')
  4. In [195]: result = pd.concat([pd.read_csv(f) for f in files], ignore_index=True)

最后,这种方式也适用于 io 文档 介绍的其它 pd.read_* 函数。

解析多列里的日期组件

用一种格式解析多列的日期组件,速度更快。

  1. In [196]: i = pd.date_range('20000101', periods=10000)
  2. In [197]: df = pd.DataFrame({'year': i.year, 'month': i.month, 'day': i.day})
  3. In [198]: df.head()
  4. Out[198]:
  5. year month day
  6. 0 2000 1 1
  7. 1 2000 1 2
  8. 2 2000 1 3
  9. 3 2000 1 4
  10. 4 2000 1 5
  11. In [199]: %timeit pd.to_datetime(df.year * 10000 + df.month * 100 + df.day, format='%Y%m%d')
  12. .....: ds = df.apply(lambda x: "%04d%02d%02d" % (x['year'],
  13. .....: x['month'], x['day']), axis=1)
  14. .....: ds.head()
  15. .....: %timeit pd.to_datetime(ds)
  16. .....:
  17. 10.6 ms +- 698 us per loop (mean +- std. dev. of 7 runs, 100 loops each)
  18. 3.21 ms +- 36.4 us per loop (mean +- std. dev. of 7 runs, 100 loops each)

跳过标题与数据之间的行

  1. In [200]: data = """;;;;
  2. .....: ;;;;
  3. .....: ;;;;
  4. .....: ;;;;
  5. .....: ;;;;
  6. .....: ;;;;
  7. .....: ;;;;
  8. .....: ;;;;
  9. .....: ;;;;
  10. .....: ;;;;
  11. .....: date;Param1;Param2;Param4;Param5
  12. .....: ;m²;°C;m²;m
  13. .....: ;;;;
  14. .....: 01.01.1990 00:00;1;1;2;3
  15. .....: 01.01.1990 01:00;5;3;4;5
  16. .....: 01.01.1990 02:00;9;5;6;7
  17. .....: 01.01.1990 03:00;13;7;8;9
  18. .....: 01.01.1990 04:00;17;9;10;11
  19. .....: 01.01.1990 05:00;21;11;12;13
  20. .....: """
  21. .....:
选项 1:显式跳过行
  1. In [201]: from io import StringIO
  2. In [202]: pd.read_csv(StringIO(data), sep=';', skiprows=[11, 12],
  3. .....: index_col=0, parse_dates=True, header=10)
  4. .....:
  5. Out[202]:
  6. Param1 Param2 Param4 Param5
  7. date
  8. 1990-01-01 00:00:00 1 1 2 3
  9. 1990-01-01 01:00:00 5 3 4 5
  10. 1990-01-01 02:00:00 9 5 6 7
  11. 1990-01-01 03:00:00 13 7 8 9
  12. 1990-01-01 04:00:00 17 9 10 11
  13. 1990-01-01 05:00:00 21 11 12 13
选项 2:读取列名,然后再读取数据
  1. In [203]: pd.read_csv(StringIO(data), sep=';', header=10, nrows=10).columns
  2. Out[203]: Index(['date', 'Param1', 'Param2', 'Param4', 'Param5'], dtype='object')
  3. In [204]: columns = pd.read_csv(StringIO(data), sep=';', header=10, nrows=10).columns
  4. In [205]: pd.read_csv(StringIO(data), sep=';', index_col=0,
  5. .....: header=12, parse_dates=True, names=columns)
  6. .....:
  7. Out[205]:
  8. Param1 Param2 Param4 Param5
  9. date
  10. 1990-01-01 00:00:00 1 1 2 3
  11. 1990-01-01 01:00:00 5 3 4 5
  12. 1990-01-01 02:00:00 9 5 6 7
  13. 1990-01-01 03:00:00 13 7 8 9
  14. 1990-01-01 04:00:00 17 9 10 11
  15. 1990-01-01 05:00:00 21 11 12 13

SQL

SQL 文档

用 SQL 读取数据库

Excel

Excel 文档

读取文件式句柄

用 XlsxWriter 修改输出格式

HTML

从不能处理默认请求 header 的服务器读取 HTML 表格

HDFStore

HDFStores文档

时间戳索引简单查询

用链式多表架构管理异构数据

在硬盘上合并数百万行的表格

避免多进程/线程存储数据出现不一致

按块对大规模数据存储去重的本质是递归还原操作。这里介绍了一个函数,可以从 CSV 文件里按块提取数据,解析日期后,再按块存储。

按块读取 CSV 文件,并保存

追加到已存储的文件,且确保索引唯一

大规模数据工作流

读取一系列文件,追加时采用全局唯一索引

用低分组密度分组 HDFStore 文件

用高分组密度分组 HDFStore 文件

HDFStore 文件结构化查询

HDFStore 计数

HDFStore 异常解答

用字符串设置 min_itemsize

用 ptrepack 创建完全排序索引

把属性存至分组节点

  1. In [206]: df = pd.DataFrame(np.random.randn(8, 3))
  2. In [207]: store = pd.HDFStore('test.h5')
  3. In [208]: store.put('df', df)
  4. # 用 pickle 存储任意 Python 对象
  5. In [209]: store.get_storer('df').attrs.my_attribute = {'A': 10}
  6. In [210]: store.get_storer('df').attrs.my_attribute
  7. Out[210]: {'A': 10}

二进制文件

读取 C 结构体数组组成的二进制文件,Pandas 支持 NumPy 记录数组。 比如说,名为 main.c 的文件包含下列 C 代码,并在 64 位机器上用 gcc main.c -std=gnu99 进行编译。

  1. #include <stdio.h>
  2. #include <stdint.h>
  3. typedef struct _Data
  4. {
  5. int32_t count;
  6. double avg;
  7. float scale;
  8. } Data;
  9. int main(int argc, const char *argv[])
  10. {
  11. size_t n = 10;
  12. Data d[n];
  13. for (int i = 0; i < n; ++i)
  14. {
  15. d[i].count = i;
  16. d[i].avg = i + 1.0;
  17. d[i].scale = (float) i + 2.0f;
  18. }
  19. FILE *file = fopen("binary.dat", "wb");
  20. fwrite(&d, sizeof(Data), n, file);
  21. fclose(file);
  22. return 0;
  23. }

下列 Python 代码读取二进制二建 binary.dat,并将之存为 pandas DataFrame,每个结构体的元素对应 DataFrame 里的列:

  1. names = 'count', 'avg', 'scale'
  2. # 注意:因为结构体填充,位移量比类型尺寸大
  3. offsets = 0, 8, 16
  4. formats = 'i4', 'f8', 'f4'
  5. dt = np.dtype({'names': names, 'offsets': offsets, 'formats': formats},
  6. align=True)
  7. df = pd.DataFrame(np.fromfile('binary.dat', dt))

::: tip 注意

不同机器上创建的文件因其架构不同,结构化元素的位移量也不同,原生二进制格式文件不能跨平台使用,因此不建议作为通用数据存储格式。建议用 Pandas IO 功能支持的 HDF5 或 msgpack 文件。

:::

计算

基于采样的时间序列数值整合

相关性

DataFrame.corr() 计算得出的相关矩阵的下(或上)三角形式一般都非常有用。下例通过把布尔掩码传递给 where 可以实现这一功能:

  1. In [211]: df = pd.DataFrame(np.random.random(size=(100, 5)))
  2. In [212]: corr_mat = df.corr()
  3. In [213]: mask = np.tril(np.ones_like(corr_mat, dtype=np.bool), k=-1)
  4. In [214]: corr_mat.where(mask)
  5. Out[214]:
  6. 0 1 2 3 4
  7. 0 NaN NaN NaN NaN NaN
  8. 1 -0.018923 NaN NaN NaN NaN
  9. 2 -0.076296 -0.012464 NaN NaN NaN
  10. 3 -0.169941 -0.289416 0.076462 NaN NaN
  11. 4 0.064326 0.018759 -0.084140 -0.079859 NaN

除了命名相关类型之外,DataFrame.corr 还接受回调,此处计算 DataFrame 对象的距离相关矩阵

  1. In [215]: def distcorr(x, y):
  2. .....: n = len(x)
  3. .....: a = np.zeros(shape=(n, n))
  4. .....: b = np.zeros(shape=(n, n))
  5. .....: for i in range(n):
  6. .....: for j in range(i + 1, n):
  7. .....: a[i, j] = abs(x[i] - x[j])
  8. .....: b[i, j] = abs(y[i] - y[j])
  9. .....: a += a.T
  10. .....: b += b.T
  11. .....: a_bar = np.vstack([np.nanmean(a, axis=0)] * n)
  12. .....: b_bar = np.vstack([np.nanmean(b, axis=0)] * n)
  13. .....: A = a - a_bar - a_bar.T + np.full(shape=(n, n), fill_value=a_bar.mean())
  14. .....: B = b - b_bar - b_bar.T + np.full(shape=(n, n), fill_value=b_bar.mean())
  15. .....: cov_ab = np.sqrt(np.nansum(A * B)) / n
  16. .....: std_a = np.sqrt(np.sqrt(np.nansum(A**2)) / n)
  17. .....: std_b = np.sqrt(np.sqrt(np.nansum(B**2)) / n)
  18. .....: return cov_ab / std_a / std_b
  19. .....:
  20. In [216]: df = pd.DataFrame(np.random.normal(size=(100, 3)))
  21. In [217]: df.corr(method=distcorr)
  22. Out[217]:
  23. 0 1 2
  24. 0 1.000000 0.199653 0.214871
  25. 1 0.199653 1.000000 0.195116
  26. 2 0.214871 0.195116 1.000000

时间差

时间差文档。

使用时间差

  1. In [218]: import datetime
  2. In [219]: s = pd.Series(pd.date_range('2012-1-1', periods=3, freq='D'))
  3. In [220]: s - s.max()
  4. Out[220]:
  5. 0 -2 days
  6. 1 -1 days
  7. 2 0 days
  8. dtype: timedelta64[ns]
  9. In [221]: s.max() - s
  10. Out[221]:
  11. 0 2 days
  12. 1 1 days
  13. 2 0 days
  14. dtype: timedelta64[ns]
  15. In [222]: s - datetime.datetime(2011, 1, 1, 3, 5)
  16. Out[222]:
  17. 0 364 days 20:55:00
  18. 1 365 days 20:55:00
  19. 2 366 days 20:55:00
  20. dtype: timedelta64[ns]
  21. In [223]: s + datetime.timedelta(minutes=5)
  22. Out[223]:
  23. 0 2012-01-01 00:05:00
  24. 1 2012-01-02 00:05:00
  25. 2 2012-01-03 00:05:00
  26. dtype: datetime64[ns]
  27. In [224]: datetime.datetime(2011, 1, 1, 3, 5) - s
  28. Out[224]:
  29. 0 -365 days +03:05:00
  30. 1 -366 days +03:05:00
  31. 2 -367 days +03:05:00
  32. dtype: timedelta64[ns]
  33. In [225]: datetime.timedelta(minutes=5) + s
  34. Out[225]:
  35. 0 2012-01-01 00:05:00
  36. 1 2012-01-02 00:05:00
  37. 2 2012-01-03 00:05:00
  38. dtype: datetime64[ns]

日期加减

  1. In [226]: deltas = pd.Series([datetime.timedelta(days=i) for i in range(3)])
  2. In [227]: df = pd.DataFrame({'A': s, 'B': deltas})
  3. In [228]: df
  4. Out[228]:
  5. A B
  6. 0 2012-01-01 0 days
  7. 1 2012-01-02 1 days
  8. 2 2012-01-03 2 days
  9. In [229]: df['New Dates'] = df['A'] + df['B']
  10. In [230]: df['Delta'] = df['A'] - df['New Dates']
  11. In [231]: df
  12. Out[231]:
  13. A B New Dates Delta
  14. 0 2012-01-01 0 days 2012-01-01 0 days
  15. 1 2012-01-02 1 days 2012-01-03 -1 days
  16. 2 2012-01-03 2 days 2012-01-05 -2 days
  17. In [232]: df.dtypes
  18. Out[232]:
  19. A datetime64[ns]
  20. B timedelta64[ns]
  21. New Dates datetime64[ns]
  22. Delta timedelta64[ns]
  23. dtype: object

其它示例

与 datetime 类似,用 np.nan 可以把值设为 NaT

  1. In [233]: y = s - s.shift()
  2. In [234]: y
  3. Out[234]:
  4. 0 NaT
  5. 1 1 days
  6. 2 1 days
  7. dtype: timedelta64[ns]
  8. In [235]: y[1] = np.nan
  9. In [236]: y
  10. Out[236]:
  11. 0 NaT
  12. 1 NaT
  13. 2 1 days
  14. dtype: timedelta64[ns]

轴别名

设置全局轴别名,可以定义以下两个函数:

  1. In [237]: def set_axis_alias(cls, axis, alias):
  2. .....: if axis not in cls._AXIS_NUMBERS:
  3. .....: raise Exception("invalid axis [%s] for alias [%s]" % (axis, alias))
  4. .....: cls._AXIS_ALIASES[alias] = axis
  5. .....:
  6. In [238]: def clear_axis_alias(cls, axis, alias):
  7. .....: if axis not in cls._AXIS_NUMBERS:
  8. .....: raise Exception("invalid axis [%s] for alias [%s]" % (axis, alias))
  9. .....: cls._AXIS_ALIASES.pop(alias, None)
  10. .....:
  11. In [239]: set_axis_alias(pd.DataFrame, 'columns', 'myaxis2')
  12. In [240]: df2 = pd.DataFrame(np.random.randn(3, 2), columns=['c1', 'c2'],
  13. .....: index=['i1', 'i2', 'i3'])
  14. .....:
  15. In [241]: df2.sum(axis='myaxis2')
  16. Out[241]:
  17. i1 -0.461013
  18. i2 2.040016
  19. i3 0.904681
  20. dtype: float64
  21. In [242]: clear_axis_alias(pd.DataFrame, 'columns', 'myaxis2')

创建示例数据

类似 R 的 expand.grid() 函数,用不同类型的值组生成 DataFrame,需要创建键是列名,值是数据值列表的字典:

  1. In [243]: def expand_grid(data_dict):
  2. .....: rows = itertools.product(*data_dict.values())
  3. .....: return pd.DataFrame.from_records(rows, columns=data_dict.keys())
  4. .....:
  5. In [244]: df = expand_grid({'height': [60, 70],
  6. .....: 'weight': [100, 140, 180],
  7. .....: 'sex': ['Male', 'Female']})
  8. .....:
  9. In [245]: df
  10. Out[245]:
  11. height weight sex
  12. 0 60 100 Male
  13. 1 60 100 Female
  14. 2 60 140 Male
  15. 3 60 140 Female
  16. 4 60 180 Male
  17. 5 60 180 Female
  18. 6 70 100 Male
  19. 7 70 100 Female
  20. 8 70 140 Male
  21. 9 70 140 Female
  22. 10 70 180 Male
  23. 11 70 180 Female