QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2768|回复: 0
打印 上一主题 下一主题

[代码资源] datawhale8月组队学习《pandas数据处理与分析》(下)(文本、分类、时序数据)

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2022-9-5 16:11 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta

    * ^) ]- B' X: `, H0 T/ ?9 O
    : U- c5 _+ K0 M" L. i) S
    ! q1 W! l5 x, Z4 P" s文章目录+ d' x0 O6 ]0 g2 a) u
    第八章 文本数据- w/ e" E( B9 w
    8.1 str对象, j8 ]4 ]) _: |1 {
    8.1.1 str对象的设计意图, T5 V0 c# O* @% b& p
    8.1.3 string类型
    ) n* x3 l- _7 {$ _! D2 s8.2 正则表达式基础
    5 y6 z% g) T2 h) S- i$ N8.2.1 . 一般字符的匹配( g" m9 N& x+ ^
    8.2.2 元字符基础
    ; ~0 @5 S) m' F  K; u8.2.3 简写字符集" I0 M4 {: o3 S* p" [! k. K
    8.3 文本处理的五类操作
    / x# ]4 f+ U0 C  o2 M5 N9 h8.3.1 `str.split `拆分
    4 Y1 i9 C0 k( W1 H( S0 L  S$ a8.3.2 `str.join` 或 `str.cat `合并
    $ y2 P' S- e3 R5 j- Y& E8.3.3 匹配6 |  o! B' U, M9 C4 G% `  Q
    8.3.5 提取+ j7 O1 |5 H# y! e2 |+ r2 Z; k
    8.4、常用字符串函数
    ( |* `  ]$ R& y, c8.4.1 字母型函数) |$ g0 H: V2 F) s
    8.4.2 数值型函数
    % T4 c, |) ^, E( I# N# P# h: U/ N8.4.3 统计型函数) b+ O( p- I: W! F% @# b7 q  F  j
    8.4.4 格式型函数+ Z* w* o; U3 N2 j- g7 B
    8.5 练习' K0 Q3 w/ D! ]! y5 D* c# H
    Ex1:房屋信息数据集
    5 d9 a  a1 ^( M, g3 ?7 F. n0 Z2 nEx2:《权力的游戏》剧本数据集
    & e' Q0 A3 n  ^, X. b# K第九章 分类数据" [6 o! ~( b2 N& y7 G
    9.1 cat对象7 T1 C7 P7 ~4 R) D* d7 D6 t
    9.1.1 cat对象的属性
      U) r2 P) b- z' H  |* T9.1.2 类别的增加、删除和修改
    7 Y: j- E0 P1 H# u& j$ N) F9.2 有序分类
    ' p4 S/ A* \/ _5 Y# m: o9.2.1 序的建立+ E, X6 T4 F, h; @. x+ J
    9.2.2 排序和比较* K4 n3 n8 d3 v* F8 m% [* f
    9.3 区间类别
    * p5 n1 H& i- J0 s8 M5 H9.3.1 利用cut和qcut进行区间构造
    7 I3 Z$ F7 o7 W( h- G9.3.2 一般区间的构造0 t4 T2 M& g* l
    9.3.3 区间的属性与方法; H+ `; {# D3 g2 N5 a# e
    9.4 练习
    2 G$ G% ^2 T  d) |Ex1: 统计未出现的类别- `5 ~0 _. }) B" y5 t/ s& V
    Ex2: 钻石数据集
    % `7 H' m( Z% x( G, @" P第十章 时序数据
    , s% h# U3 B5 s  p, I# q+ b10.1 时序中的基本对象' J/ }' U. G# s% e' x
    10.2 时间戳
    ( g# o" L- X+ C7 T4 _" S& k. {1 \' r10.2.1 Timestamp的构造与属性
    8 z& `! I8 e9 p1 x6 x10.2.2 Datetime序列的生成9 u! f7 {9 `2 i/ }- h3 H6 e
    10.2.3 dt对象
    ) |# p4 v- S1 L% {0 c6 r- _0 @10.2.4 时间戳的切片与索引& I  T+ D: k! v3 Q: J5 K7 S
    10.3 时间差
    $ ]- N& v2 t- u7 c6 h10.3.1 Timedelta的生成
    6 a% M/ ?8 ?9 i( s0 e1 v/ X5 o+ C10.2.2 Timedelta的运算
    7 K* }5 {, ?$ D2 y% m0 n10.4 日期偏置
    " Z8 O% v/ }( E3 g+ w10.4.1 Offset对象: J7 ?1 H; K) ?7 S
    10.4.2 偏置字符串
    : R9 \" x/ }; p* I+ L$ ?' T; r10.5、时序中的滑窗与分组- b& i; m1 t: B* s# E' n
    10.5.1 滑动窗口' s# w& h6 K4 m/ Y) y6 r5 \
    10.5.2 重采样
    ! _/ a) f1 R4 D5 r) ~10.6 练习
    6 n4 U+ n( Y  \0 IEx1:太阳辐射数据集2 K  X( N9 K/ L) w& @% P
    Ex2:水果销量数据集1 Z! E; A& ?9 D4 u( l
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网6 `  ^! w% y5 W8 m+ R+ N/ O
    传送门:
    - g! J0 n$ Q& j- R2 F1 d0 b' b/ @: X% p6 r6 t) P% d9 G, b
    datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)/ w6 ^5 f- W7 a
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据). X  C" S* _+ ?% {* l' g
    第八章 文本数据0 q( _7 [9 n! {" m6 ^! T/ P
    8.1 str对象) B, L' N+ t# G
    8.1.1 str对象的设计意图' F1 f+ X- H- }) J( m
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    + N, \* n6 c/ S6 B0 u' T. B- Z, X( d. m; G+ R" ^, ?  R' c
    var = 'abcd'3 t8 R' ?4 {& j5 ~  Z
    str.upper(var) # Python内置str模块! u( S' c- e* `3 e* `
    Out[4]: 'ABCD'" m: `2 w& q) r0 c
    ) R3 y, b3 o8 l1 O$ p+ O' H
    s = pd.Series(['abcd', 'efg', 'hi'])% z  B  I/ c0 `$ _9 Z0 j  z% B
    # H% `, K0 l& R8 l, \
    s.str  D2 J6 {: b2 Z
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    7 j, D0 C1 a' X( `- e9 A
    ; C& a8 J* C3 Y; ]( \s.str.upper() # pandas中str对象上的upper方法
    & q* v# h" B) l& B3 mOut[7]:
    & z, O7 \$ D. @1 I( R8 o2 G3 e0    ABCD
    4 w7 i: G; _2 \. W. G1     EFG3 }+ g+ Z" G2 A* I
    2      HI2 E4 }' O7 \7 |7 x( {
    dtype: object5 f2 ~9 o9 I1 p% P6 r; i. G
    1
    / L9 A/ ^5 N/ E5 L6 j# W22 c* k2 d( m  t# V: |
    3
    + M9 q5 V6 L# c' P  e! |4' X, ~! q5 S0 n2 ~, g
    5
    0 o& H$ d: _5 B9 H  u6. r+ b: k; j( W
    77 Q* v! M4 q7 J5 ?+ b' F$ E
    8, _7 W5 w# h, _8 B4 r, p4 M6 X! u
    9! }5 U; \3 q; e9 \* h
    10' }6 Q; b+ O8 t) Z2 H3 w, s2 l
    11
    9 p/ `- F5 \9 |9 K7 E0 W12
    2 [, u9 Q3 b# u8 i! j% o13
    ; l0 [2 E( {8 o0 ^14
    * B; G* e9 H0 W" g15
    " v( T$ d, ?0 p8.1.2 []索引器
    7 V" \' r4 B0 a/ U8 u$ ~# g  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
    , K, w; H2 H( y! ?. u  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:  `+ R! C! t% r- \3 t5 I
    0 g/ e( a$ o- ^' ]  g
    s.str[0]
    * [5 U" r! b5 U! \3 ~. q: fOut[10]:
    : w* Q, d' g2 h4 v1 D0    a
    $ x$ o6 @9 w' H$ F+ m1    e
    " Q4 c, f! k* N9 J) M, }  _2    h1 A! _) t3 O2 A% N1 a& d) u
    dtype: object' x' s1 v/ a5 D5 B/ \0 |1 P

      P# g3 r' o: d& O/ ?s.str[-1: 0: -2]
    / r/ A( d6 D( E, S. oOut[11]: 4 q( b. I) @9 d7 r- W) y& R! a
    0    db
    1 u" ~& M) a  y& R6 r. ^7 |' V1     g
    ) \6 H2 I( }8 N3 \: B- Y6 {2     i
    5 S& R, R: K* _' P2 \/ Pdtype: object
    , G1 ~! a- `# @2 G  S* Q4 ^; C1 P* y" e8 N" d. J
    s.str[2]* c" T4 r7 Z' k3 G0 E. B
    Out[12]: + E+ U7 g: L# S- x/ ?% |
    0      c
    - h% a$ M6 ?# P& y1      g
    - L5 j6 o+ V) {0 q+ y2    NaN+ m7 W3 u- g  A& q) O: C" l2 C
    dtype: object
    $ \; m; x5 Q  l+ g, d+ Z( P$ B1 v1 a( |/ l
    1
    ; [& o0 [5 z+ b6 c7 g& i23 f9 q+ h) D! k; x4 i9 a
    3% e$ J# S/ Q( `: H1 w$ q7 p
    49 x$ [& _% M. ?8 r6 y
    5$ H- c, h# S& L' f1 q) K6 g4 F' e
    6; e" T- H* E1 j& c- }! x6 E
    7
    ' D, ^1 K9 S% z- B8
    , U2 d7 U; y8 C5 v( C, _9 F9) \: ~  ]6 }( |( _
    10
    9 Y' r  t; o$ j% G. S) _" X+ N! E11
    6 d/ y7 @7 `" M% d! }$ B0 Y12( N0 h/ ~4 D: H, Q5 g+ M0 C. w+ o
    13
    ! P% g- q$ @. m# t% Q! t144 h3 j1 m# b$ b8 Y9 m. v/ W( L- u/ {
    155 w; x" r2 }; Z) |+ p1 }: e5 k. w# c# j
    16
    , N/ V8 w6 g2 Y" m3 L5 w17$ o8 v7 k: f! }, _8 {
    18+ Z1 t" }" m4 E4 q6 C
    19/ j$ z: B) y- [# ~& k# x
    20
      O8 h* _& q& ~4 iimport numpy as np
    ( D0 n, j9 B' Q0 t+ Z/ M6 p) u7 cimport pandas as pd
    + x+ \  E$ \5 C- I9 O) V5 U3 Y' e$ |+ V& n$ W
    s = pd.Series(['abcd', 'efg', 'hi'])- d2 K) H" ~3 o: ]' ~6 w, Q
    s.str[0]
    ) D/ O! h6 B- o7 `+ s! @1 M1, [/ b/ V2 D, L) w9 S
    25 N7 j0 F* r! C+ d7 A8 c
    3" J5 A4 }* P" D8 K
    4& h, L7 @3 M+ D% Z
    5! d% j: O- k* e4 e) V( C! @, J
    0    a
    ( `; O$ I6 O7 k" h% @6 w8 p1    e6 o. _$ v& [- s; L! n0 M
    2    h
    5 Z6 I  \& M6 p0 ~9 Y0 qdtype: object! I- @- A" x$ q+ v1 x# g+ {2 m. G
    1. I3 K$ {! V, l; ]& u1 T
    25 `" h, D3 `3 X0 u& s% g- D
    3$ v1 J9 [6 n$ _! ?' H, P
    4  T, l- O* E8 T( g
    8.1.3 string类型  j/ @1 V. |" A
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
    # v7 W; r8 E* @" k, T( |  k  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
    3 A% a2 m# L* t8 C1 y
    7 g+ p7 d2 e/ t; @4 i( d3 x二者对于某些对象的 str 序列化方法不同。
    . {8 d* x+ L/ K9 [1 }0 y# I8 y$ Z可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:% f7 F: ], A" d/ ~7 D
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    ' _6 m9 }: H2 ks
    . U5 t: j+ ]7 m4 A- a, |) s  r9 D1( Q1 r+ |+ O) Z! L+ @
    21 Y6 G( E& I& f5 z
    0    {1: 'temp_1', 2: 'temp_2'}
    5 T" i) v. T4 N$ T# `1                        [a, b]
    : t" Z, r1 h: Z: _2                           0.5
    : f7 k7 g* P: F4 {' W3                     my_string
    . n. T! ^( q0 Ldtype: object- |1 R3 }. b# ^' `+ }
    13 d; i; `( D+ P. S
    23 T' v5 ~% o: y5 C4 r" Q
    3: f! X- R6 Y% a$ f; {6 I1 m0 B4 o# R
    43 O! E. J" g, q# a5 `
    5
      m* ?" p3 Z8 Y# \2 x! m( Zs.str[1] # 对每个元素取[1]的操作
    & q$ k$ `& j5 M: N0 l5 N4 ?1
    % X" L5 S4 ]5 |- ~% x! B0    temp_10 W5 Y! g6 q( f+ h8 P( f7 ]6 k$ M
    1         b. G5 [* d6 _! R
    2       NaN
    $ e% j! _1 \% f# ~1 m3         y
    8 `! S6 {. u- k, Jdtype: object6 p8 o8 R; ^% g  S' O; v6 P
    1
    + u* D! V5 s6 ]2
    1 v+ r8 G( P$ W5 M3% N% V) K0 h: m
    4
    " H) Q/ T" J1 a5 h& g# b5
    / \: R! f, I) u% `. y5 \s.astype('string').str[1]
    9 ~7 b2 z; A2 ~/ W4 \7 U# F+ v15 n0 o* _6 q! T( K% n
    0    1
    " s9 r" h" O8 H1    '
    & l0 @0 [8 M# {0 ]8 w3 X2    ." t( n5 y$ O4 d! S, y
    3    y. I. j8 d' R4 ?4 ^; c  _. Q
    dtype: string
    , j+ x" y6 M! h; B* }7 I1
    ' N- J; q# n, T/ @8 Q1 q2
    ! Z- q+ i# s; `+ @3
    : S& _& y0 x3 b. }. x' F46 ]6 C- e  x( S+ o
    5
    9 k$ \, f. V1 t1 j  T除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    5 f3 t  h# o# Z0 e% d
    # N/ _' e8 k' [) e5 Q7 w当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。) d8 j) y- N9 G. S
    string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
    2 h9 C. o( [5 M" R  @1 Mstring 类型是 Nullable 类型,但 object 不是
    ( K8 {1 }: [& v. x  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
    2 e  _* w  E2 [; o5 N  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。; s3 }5 S% l/ r5 {  s0 Q& \9 U7 Z
    s = pd.Series(['a'])
    - z4 D9 }8 i; ?+ N
    % g/ e# p3 Z1 _; Y8 ^3 E2 y, p" ^s.str.len()
    4 O6 r! p$ N$ E6 K; XOut[17]:
    - H$ [$ y6 W6 F0 y3 q3 V! I0    1  k& u$ b# [9 h9 }5 D7 m
    dtype: int64/ q! u8 P, `# C8 i. U- M8 j

    . d$ H, B* ^& Z2 d; r! i1 e% Ks.astype('string').str.len()
    2 e; u' F% j0 F' yOut[18]: ( C5 D  t+ {7 p. w4 g/ E9 \0 c
    0    1" r9 M  x, R8 K& Q) P
    dtype: Int64
    ! @- l% _$ ~, y3 f! G& ^. V
    3 d( k+ R, Q% Vs == 'a'
    8 h8 [  h; Y0 H+ v9 Z+ HOut[19]: # z9 A; z' k! G9 G
    0    True% H4 i  z+ ~  b7 o5 C5 b# H
    dtype: bool
    ) m8 w. B% q$ g6 U+ I+ ]% r; r0 [' C# x* v. U9 ~; X1 F/ H
    s.astype('string') == 'a'4 |2 [8 L% W# h3 b( y$ s
    Out[20]: 5 w* k, A& Z. y2 N- U+ R
    0    True
    + s' Z9 s" d7 U! i% r! pdtype: boolean: J/ R9 g/ x& H

    - {) C% a) A- s- p9 Os = pd.Series(['a', np.nan]) # 带有缺失值
    ! a. u3 d+ N7 \; d/ l1 s
      }( I2 O) a, @s.str.len()) ]: t* z0 o/ s" |* s+ o5 I* X
    Out[22]: 3 F) ], n3 q8 T0 p& S$ d$ \; i
    0    1.0* }+ c5 K% F. j: S$ D! A- M6 F, j
    1    NaN9 |$ w. ]3 Z6 p! A  ?
    dtype: float64% `& J0 Y& b, a" W: y/ ?% b
    2 W) ^8 M, b/ d5 D) m& S
    s.astype('string').str.len()# n- X( |$ m/ z9 r- X" M, j
    Out[23]: 0 j1 |* t% d( m
    0       1% e+ N" q% B$ P+ j+ L: G0 N
    1    <NA>9 [" k7 {1 H5 Z% x: X2 ~% I
    dtype: Int64
    - ~4 N+ `' ?& i3 [0 S; A8 X
    3 `+ v/ ^1 w$ d6 Vs == 'a'
    ; m8 H  C- G6 g  l0 D& AOut[24]:
      X3 w# \4 {, \" C0     True+ X* `4 G& f. e: i& U! o' g
    1    False* a6 q( r9 N  B5 t; W+ [
    dtype: bool4 ~+ ~$ g: P9 E; J: p2 \4 q
    0 g9 z2 \) l4 O' _2 U- |0 `# {
    s.astype('string') == 'a', u; r. l4 ?5 ]9 H) t
    Out[25]: 1 X5 G8 R3 t  t8 L/ o; B9 P! r
    0    True. e; R) x4 N* H0 n) v! G
    1    <NA>
    $ {! `, C# |9 i$ Q( j& |/ ydtype: boolean
      \: y& Y/ F5 u' T
    . i5 D; ^' {4 D9 y1
    - }+ o* ^0 k$ o+ D) t- a2
    ( ?! H$ j- E: i+ A3, F% P+ G- L8 U% l. ^0 h# y! y
    4
    - a: ~2 i1 a- x2 m' J5
    6 y/ {1 c, V# C$ W61 f( s. {/ h, I6 v' {* S
    7
    1 M. \9 }; F# V" Q, Q( {, ?8+ l) u! c  }# H. H% S- q$ J" V
    9' k' ~2 h) r1 O7 z' x* j
    10
    5 W5 `6 W2 z/ k1 e" [; O11
    6 c7 Y  e- {$ N' i2 u12
    ; E  J) {- _) V2 Q2 l5 n  ]6 H' s13. O! p* R5 f* s' |4 ^
    14
    6 u. M5 \+ d* c( |+ ?% c15- s! i% k4 h1 Y. K
    16
    $ e6 s0 b* L, i+ l17
    2 `. l2 V& ?8 N; I6 K# k% J' O5 k18
    : L5 y6 y' P. o  ]0 B199 l- j+ m. P1 R, K/ `- i( P4 n
    20, @- X3 {; q: |# }# x" Y
    21
    , T5 d" Q( B7 M  y! v/ s1 e22
    , g" M6 ?' [6 `, f' c23
    9 ]0 G6 v2 N6 Q& |: L9 ]24' h/ n; b0 P+ C+ C
    25
    1 U2 [5 q+ _" V5 k) x8 `$ w) i26- j+ n) d- z  |8 u8 i* t/ C) l
    27
    ! E% m4 R5 L8 g7 L8 h2 `28
    " j! r4 i. W. g3 E29
    4 v5 i# }% J6 p% Q% `( z30
    3 |0 R8 s! p# H$ X31/ x/ z8 B1 h9 G. g! X+ m
    324 B) V5 \* ]3 ^9 T" j$ B: H
    334 N$ g# K, K# b8 g( d6 v! c
    344 k5 p2 Q3 @& l
    35( X! q; F$ t* d3 _' f3 x7 `' Z
    36
    * A. u$ U9 z/ H' L7 l37) G% L. P8 e7 e; D( S1 X7 V
    385 G7 ^- ?$ ]' x
    39
    * P. ]0 L/ `8 D- r* M7 C1 [: |4 z40
    / Y- a5 A( n: B  y41
    " B2 l% C- \& I, t/ |42# L. ?) ]6 S# M* b$ n& g
    43
    , L7 k+ O# H, }% Y, }. u44
    1 ?4 g6 h& G  A45$ ?" D6 _* a5 O% E' c+ X7 G3 `2 u
    46
    2 X0 U8 q* z4 H. ]47- O/ g8 p% i/ F
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
    " k+ n. s6 y; G1 T5 `$ h) p# I9 U  I0 j% K
    s = pd.Series([12, 345, 6789])3 X- t/ S, U4 `/ x5 ]& p
    " M( P/ X  }3 i4 i! }( P
    s.astype('string').str[1]" G& J. K3 _7 e
    Out[27]:
    / T( a7 ?6 O4 A9 ~1 l! S0 @0    2
    / k3 @# A$ r% D* }1    4( D, w, D# r$ k! b$ X, N
    2    7
    8 C/ k: L5 z. j, I; u- g: pdtype: string, t0 c7 r) C3 `  I0 [
    13 v1 A6 v# ?% R: a- K5 W% J, j
    2
    0 Y! |' G( B- M) W. \) y9 r6 j3
    7 H3 w% y! D; o5 Z48 i7 x/ r& T4 R1 s3 n) @" T
    5
    / D% S" [; d  V9 o  A2 C67 n" S2 y& P2 P+ e! U
    7
    7 \2 _5 v, o( f8
    2 x3 e6 {  m7 a- f" b" H8.2 正则表达式基础
    : ?8 C/ {5 Y  u# c. z5 X: i9 m这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书) M, ?% v9 Q& Q
      U( g4 D! v4 U/ u& `6 R
    8.2.1 . 一般字符的匹配! A) w( w4 H7 w5 b8 \
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    ( G5 h. F/ x* j3 V3 ^& [! E) ^
    9 H! Y, W2 O) U% kimport re
    ) r- {. J' |9 z% x( {4 D
    " b4 }; _! \) [7 hre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配' X8 _) u9 \; a3 i  y+ q- R" W' P
    Out[29]: ['Apple', 'Apple']" ^: @* d& l" O. I' \: @1 k; ]
    17 W9 }0 \2 ^+ H0 M4 O* r* Z
    2
    6 k  Y* p3 Y0 d7 T! H3
    . N% {" f: X) @6 F5 K4) w5 f- U# k+ n$ v
    8.2.2 元字符基础' l% u& {8 }5 M3 K5 q9 X/ Z
    元字符        描述5 `, Z9 h# f! ]. S( c  n, R* D
    .        匹配除换行符以外的任意字符6 `/ Q4 s9 C6 u3 j4 ^
    [ ]        字符类,匹配方括号中包含的任意字符
    / m/ B) @/ K' U# F[^ ]        否定字符类,匹配方括号中不包含的任意字符+ ]+ t7 j5 Q2 N- C* s( U, ]
    *        匹配前面的子表达式零次或多次5 f. w0 k. K0 _1 T2 a& `
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
    ' C* J* l0 v9 Q' E1 n?        匹配前面的子表达式零次或一次,非贪婪方式% k& x6 ]6 z! l2 D6 ]! d3 i
    {n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
    ' b! |' E$ h9 H( n( [(xyz)        字符组,按照确切的顺序匹配字符xyz
    ) V% H. i4 i$ O+ ^1 O* D|        分支结构,匹配符号之前的字符或后面的字符
    # q: J6 `, |% Q( Y, Q\        转义符,它可以还原元字符原来的含义5 |- ?+ O% b6 \
    ^        匹配行的开始
    " x$ |: I8 V4 \: c$        匹配行的结束* j4 w" m9 A, ^: e6 T; J+ R+ s
    import re
    2 @! a/ ^7 O- H/ ?' w- \  |' Ore.findall(r'.', 'abc')
    # E* C/ p& m% WOut[30]: ['a', 'b', 'c']
    , d- q# Q/ q, ^, ]* T) p1 x3 m' E; R
    re.findall(r'[ac]', 'abc') # []中有的子串都匹配
    2 N$ g, u7 y6 [- X  f" MOut[31]: ['a', 'c']
    7 w% j3 c  D4 G" q+ ]# t' v6 h8 y/ \) q. ~8 s+ W
    re.findall(r'[^ac]', 'abc')
    + o  L: S3 X! f$ B( SOut[32]: ['b']
    . f% N/ b1 n$ y. q. b
    1 v# p5 |# a8 n0 {2 y+ U# Z  @re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    7 F7 W, r6 T: y& r0 T# O& DOut[33]: ['aa', 'aa', 'bb', 'bb']' q( ]" P7 W% B, r$ D5 ~' i

    * w+ Y" x% B: f, l8 S3 qre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串2 R" E! G  s: P; f
    Out[34]: ['ca', 'bbc', 'bbc']4 X" P1 X* \# Q  Q" d6 z

    5 z( H8 _, I3 P2 I3 Z# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。5 P* N4 l5 M! ~% u/ e# F$ h9 _; U
    """! o. z8 E1 ~' b
    1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。- a9 [! `& N0 A! n6 t
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
    / F  y: x2 N2 C% k* m# p& n$ I3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
    % J2 y( U; @  |3 F3 Q' @' B但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    ( d4 l! @$ T, h"""/ J& L( X# D8 W* A
    4 F  }3 A( o& l' l# O3 H2 P& k
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。2 s1 A! H+ u- N! y2 g" y- [* j
    Out[35]: ['a', 'a', 'a', 'a']
    ! b: C5 }2 n' B  q6 N- j0 R
    & S6 ^$ H! k+ \8 X  ?" ^# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
    5 g! m8 Z* O1 E( \) p) \8 u# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
    ! B! {. W( s# }3 `) G. mre.findall(r'\\', 'aa\a*a')
    : j; L/ e- ~1 W2 Q3 G3 z[]
    3 u1 |8 |4 S) F# R+ V" W+ p
    2 A$ r4 ^7 V2 z8 i/ H; zre.findall(r'a?.', 'abaacadaae')( q$ k5 V6 }4 I! r1 i5 o% ?
    Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    ( u1 k: G; {: ]- ~- t  m2 K
      `! C' ?5 z; e" P6 i# Y9 Sre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
    4 i* u3 Q' Y; U2 U# @) l- _9 Z[('width', '20'), ('height', '10')]
    # f+ U  V+ h2 ^# a$ v7 i! r
    " @" V. H% I# T7 N3 M% }2 C, A: x1
    7 k: E/ X% R1 D- h' F2* y6 U# x' s. Y  U7 A: L1 G% G7 q
    3) R/ _6 c# H  X+ K4 a
    48 g5 e' f, N: @. J' t/ J1 @
    5+ o; A7 x# Z, D% L6 S- i  `, M- A
    6
    ) u4 v* |- T- O79 e: v9 @8 E  k3 B8 b) g
    8, g  v+ ^# L* T8 v& t$ f; f  B
    9. G& R  s0 K7 l/ R9 C; N$ ~* h# V( s
    10
    8 l: R% h; i1 k! t* [4 K1 q) c7 H11
    4 s: x% w4 F& E8 ~/ v2 H) R12' r7 z' G: u4 G- X: a3 {
    13! R# z' @: f- A! c) k! f
    14
    / E3 w9 @* c" L/ Z* k15; v+ [3 A) X/ n
    16* R, h: w1 Z0 ]7 F" c; _: z
    17" F. U# m% e2 |+ g: `8 A
    18. A  p* s+ N0 b9 u5 a8 @! ^
    19" R$ _+ D5 \0 u0 n  m& \
    20
    + W! M5 I8 [2 A- |* l, j213 H+ R3 [, B3 v6 P4 C) G6 f
    22' g5 H5 d3 ]" Z. w: p
    233 c3 S) U& `& [: {& C# E; @
    24& y$ O8 O  d3 s5 Z) W- [
    25
    1 O0 p( f0 s$ G: i+ e0 t) `. Z26
    ) r5 t- A0 c% F. t8 @: e27' E2 r8 L+ g: o& v0 m8 f
    28
    * `/ ?; S3 A2 s% M( ]$ S' l1 Y29
    ( B: n+ _6 ]. W  i- u$ J* n30
    " D8 d% ]& a7 s5 b! d# Q31
    + X5 Z3 |% F7 U; ?! I" _$ Y32
    ; x1 c/ C* K7 J# E9 S4 o33
    8 Q' ^6 }- p+ c( H3 ~5 }34
    # F+ i/ L$ `2 B2 ^  s35
    . F) T; f+ L' p7 U. K* d  _36
    9 U1 @- A1 @' |0 M9 v/ S37
    4 b, R* n2 s4 T' A- [8.2.3 简写字符集
    - F% O! V. U: l则表达式中还有一类简写字符集,其等价于一组字符的集合:
    4 b, I8 M' D- T% e- `0 S0 P; j1 o/ j9 `4 G. y: i
    简写        描述5 \/ Z  \; B$ s5 S3 w
    \w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]# A/ |: {, I8 i
    \W        匹配非字母和数字的字符: [^\w]! o7 D% ]- c+ B; n: m$ h# v* {
    \d        匹配数字: [0-9], P! e/ S3 V; q# R6 |( b
    \D        匹配非数字: [^\d]6 w6 R" S( f/ X# s- R
    \s        匹配空格符: [\t\n\f\r\p{Z}]1 }+ g% Q, ?- o# t8 O
    \S        匹配非空格符: [^\s]
    8 Y+ l" @0 X$ \\B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    * ~( d5 ]. E" O  H% f6 [0 Jre.findall(r'.s', 'Apple! This Is an Apple!')! b, b* w% f) J. b+ z; e
    Out[37]: ['is', 'Is']
    0 v8 m" z& e7 I' _1 O. V
    * w* _4 {/ e1 X: r5 m; \/ {re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次/ D, A1 p  E* v& B' B
    Out[38]: ['09', '7w', 'c_', '9q']  o5 p  U0 r! S6 u: r* x$ Y
    6 G$ q8 v4 \3 X" ?, E
    re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)+ {% k) Z  s7 x" L* X' A& S
    Out[39]: ['8?', 'p@']
    . Y# s( P" R4 X/ q$ t4 N9 O! h: I$ K$ h
    re.findall(r'.\s.', 'Constant dropping wears the stone.')9 V( f5 j9 m$ M9 [
    Out[40]: ['t d', 'g w', 's t', 'e s']
    + n6 i6 \9 C2 I) Y5 u- @' g- B% u' q+ V
    re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',$ O0 n- W) k5 N# Y7 k) n
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
      s0 l, T% T% S" x3 I: Z7 I
    ) T0 i+ T. Z9 Y$ o7 U4 P& e* _* _Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]& N! Z, T( y3 b: t& p5 n
    3 H/ C' m7 e7 T& b  J5 K! ?
    1
    : m- I& D% A5 i2
    6 \9 L- }0 f; ]+ d36 {2 b0 V- H# p+ Y7 _. S8 L; |( S
    4
    2 a; ~; D- G" g8 o$ M! X! W, I5! ?7 l' m. i2 @  `, d
    6& _" j8 r, X0 h" v5 B
    7
    - D# e5 o: v& ~3 h- A5 b% w8# l# C. Y- x- z7 `
    9
    2 I+ _5 K( V0 X) y) y108 S6 L! r7 B- o9 n3 ?1 V
    11& L( u3 m' o% \+ g# F
    12& O( Z: ^+ @' d/ s
    137 d& j) E( @  q$ d& j7 R
    14
    1 I1 v; H+ |! V# G, }6 ]' s) P15
    , k" h& a3 e4 w4 N+ u* u$ N" R; t16
    " v# O4 W* I: Y' l8.3 文本处理的五类操作
    7 Z4 `) x) i, e1 N& M+ X7 r8.3.1 str.split 拆分
    : }2 W" N3 J& x  |: a* ]  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。( q9 c5 l! R3 ?, h  ?* }" C
    . Y# P. _/ i) e! O/ s" k+ c7 n
    s = pd.Series(['上海市黄浦区方浜中路249号',  p$ o) O  J2 a" {, ^4 N$ T: m
                '上海市宝山区密山路5号'])
    5 o! P5 m4 h0 b2 F5 t5 I
    ! b# W% P8 g$ |1 o# T$ `
    - n3 Q0 g2 ]7 |3 i( a5 Xs.str.split('[市区路]') # 每条结果为一行,相当于Series5 N% z7 C7 O. L( b
    Out[43]:
    ) O' t& Q/ L3 |; \% _# |0    [上海, 黄浦, 方浜中, 249号]
    4 s1 ]% P1 u8 Q( o0 }1       [上海, 宝山, 密山, 5号]3 N1 l' |8 Z+ e
    dtype: object4 J. X8 D3 N: y: }$ B5 P

    # n: Z$ _* U0 [* _* Z7 |7 vs.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame$ w+ W$ a, I! y
    Out[44]:
    & p8 W; ?: x. {8 L7 A    0   1         2
    # S5 K1 m+ e* G3 X& K6 l, q0  上海  黄浦  方浜中路249号
    6 R8 D6 Z2 D5 P1  上海  宝山     密山路5号  u  b! t1 D3 w! c! l
    1
    7 \  k% \9 b+ B( T7 {% t/ I+ \* V2
    + r, Z, u4 y5 |' e. o3
    ' j8 e, [* H# e4
    " g1 L6 A, M! Z1 n5
    # @% k% G1 D2 H- o6 }9 X2 O6
    9 i1 h# \1 A0 d' U" e7
    . K! V) J4 o( c) Q: o) c- d# E84 W0 y6 \! Y- @6 ]8 B# P
    9' R4 B* O& [$ U4 m# N* L; Q0 G
    10
    6 b# {2 b2 G& Y11) a, H" g3 P- ?* w' X% t) n; o
    12
    & E* s+ }4 C9 W9 v* Z6 z, {! q8 @13
    . a( \; F; L  G; r14
    $ H. T9 s  o5 L15  a; Z6 f* b# n) J* M% c
      类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
    9 X/ e# O) e& a' [- a* t/ l& a8 y0 o1 Z7 a4 a  \) ]7 a
    s.str.rsplit('[市区路]', n=2, expand=True)* S3 ]8 t" s/ ]3 K1 p4 q9 s
    Out[45]:
    ) u! @0 l4 b6 z; {! K; f! b' f                0
    " a% i8 i9 u7 B! A7 ]0  上海市黄浦区方浜中路249号4 R. l6 H9 t/ ~7 c! O
    1     上海市宝山区密山路5号
    9 {* ]+ ^2 o5 q1
    9 _9 Q2 c+ b6 b2 C& a# _. [) v2
    . A' r* O- i9 ^4 o  S+ M3
    6 W9 i/ d" a3 V# w' i# X& U4) u$ w7 n! ?8 r4 U
    5
    2 ]+ A( H; O) w) {2 l, E: S* z8.3.2 str.join 或 str.cat 合并
    / S. |* w7 ~1 o" K# vstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。0 B2 A+ G+ S  {! H1 a8 |: S# ~
    str.cat 用于合并两个序列,主要参数为:/ F& f3 L) I! w3 {0 q
    sep:连接符、9 h$ A3 S$ }; N+ R1 J# |/ v* G. f
    join:连接形式默认为以索引为键的左连接
    ) E/ M7 O* I# h# e3 [6 e% `na_rep:缺失值替代符号0 ^. ^$ P1 g  i& W7 o( k+ F0 k7 @) {; _
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])1 g2 b" x" ?1 a& u. t9 T
    s.str.join('-')
    - T. h, d4 p" x+ y4 lOut[47]:
    3 [6 s4 i) r0 \$ x& I0    a-b
    # Q- n8 s/ V$ p) J1 P( y: f1    NaN
    ) I6 V' F! U& S2    NaN1 Z1 R7 J# x3 L8 h7 O
    dtype: object9 i3 P$ S2 g$ x! W' L
    1' V8 l* H) U* q7 }5 Y2 g: `5 x
    2
    , b6 |7 s" k. v3 f5 s3
    # b" J  w; C) e! e2 n4
    4 o6 G" D. W0 O; ^1 a; P7 Y51 c& g  Y  f0 ^" i  o
    6
    * A5 n0 j6 N) D& p* ~! e: D7$ m  Y& U# P: i' \/ ]3 i5 j
    s1 = pd.Series(['a','b'])
    ) g  H2 e1 Z6 W7 ]- Ys2 = pd.Series(['cat','dog'])* ?) Y1 L+ ~, F0 ~
    s1.str.cat(s2,sep='-')
    ) t  i+ `$ ]& r$ L* v) v% ?1 LOut[50]: ! z8 I) V  c' c/ h. G. J
    0    a-cat- {( [+ l" n* L' |
    1    b-dog% A4 X/ S9 q$ m) \5 d& b; D- o; \
    dtype: object& |6 C4 n( {' Z$ q* F

    4 H8 _. C4 x, Z, |9 es2.index = [1, 2]
    " b% t7 {6 D) J5 Y) Ys1.str.cat(s2, sep='-', na_rep='?', join='outer')
    ; i( |2 h9 d6 V6 n& vOut[52]:
    ; K' U" R7 }( q( P  p& B3 e9 t% H6 i0      a-?
    + E, v! _2 D2 n1 A  m1    b-cat) M1 r- Z  v  ?7 v
    2    ?-dog/ ]  m9 O# {9 y* U4 S! g$ E
    dtype: object& j5 {7 Q' w; i8 J
    1# O- J  J, B/ }3 \& O1 c
    2# e* c* \+ j3 D2 B3 O6 N. G
    3
    5 m$ Z( k6 ^" i4$ z+ b) |+ T" Y8 L
    55 P9 _% V  h& [6 z# {
    6
    ( j2 S% v( c2 Y! ~7% ]) ]( i; }4 z2 I4 j! _
    8' O5 Q8 M# C" \& A: S
    9
    ( D7 w+ {7 z- r, ?" f/ g; P- I* d10
    + e+ |6 A& Y9 s* Z! M113 R( ?* U; U7 h+ A' |
    12
      S( A" E. o& b136 K9 }3 v% W# Q! t
    149 N6 l7 \' C/ d; o4 g
    15+ n% X2 i/ E, M( a
    8.3.3 匹配
    9 ^% i. A1 Z" X& Vstr.contains返回了每个字符串是否包含正则模式的布尔序列:
    * q# {& j4 |3 ^% f. s7 {; Zs = pd.Series(['my cat', 'he is fat', 'railway station'])9 v$ O" l2 z  o5 Q
    s.str.contains('\s\wat')
    " [, k) T6 ?( }! e" ~5 n1 E
    6 i& g! u# y  ]9 {1 R* z0     True( c- }# L; u6 n% E
    1     True
    3 t% p+ E% S* ~2    False
    ) [) p7 W8 v$ z+ t) zdtype: bool3 X4 y0 N2 d- R5 C% N3 H
    10 X( p- b+ M. \0 [/ _! W; z' Y
    2
    0 I# s, N+ B- e" W% i" {3
    . d7 Y: p& A2 z, I, @4
    5 N) z+ {' l  E" j% c# G$ R) E5" x5 h$ W8 f* c7 A( _
    6
    % n' C+ i8 ?: K! f. [0 x( q; J7
    ; _) F; p3 Q/ h' a3 estr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    : U) ?" A" I* l3 q0 G1 Is.str.startswith('my')
    6 p; ?9 E2 @0 F4 w; P* {( N" A4 l5 @- x
    0     True5 D! Q& z' g( v3 Z9 X* g* J
    1    False
    ; Y0 d" {1 Y8 a+ D0 h; F2    False7 S9 M) y) r4 v8 i# I* _
    dtype: bool
    8 |4 j/ U. l2 F  [1
    5 X8 P/ P7 W6 p: q2- u3 ]* I4 Y: R: L4 _
    3
    ' K" J) ?! H: ^4
    : p1 c$ Z1 a# U- b$ e4 J5
    % G9 d5 p1 n: J- B- Z) |% _/ t0 X6
    - b: D' {. w7 Es.str.endswith('t')+ W8 R& n* L  C% O; c
      v) C- d9 r9 V2 z) E
    0     True% ?9 Y/ ~8 `' O. I* T
    1     True! `" @. ?: L# g2 |
    2    False6 ]' u  g( _" u/ H
    dtype: bool
    + _( q% B9 _; C* L5 P0 |1
    0 L- K1 V+ q9 j21 ^. H, p5 o1 `
    32 @) P' t/ t* u# x5 |; ]
    4
    ! N* W. T! C1 f' T: i" i5
    - ~5 [0 P* U# L7 d' @6
    2 ]% F7 ^) C4 O* \str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    2 _5 h4 N; X2 a" n% ls.str.match('m|h')8 y' ]2 V: M; E, m% ^* a1 R+ {9 a# z
    s.str.contains('^[m|h]') # 二者等价
    6 ~: }5 a+ r( B7 D  B) G% V) G; D
    0     True
    8 s* o* e' U; `1     True5 p; v! z6 k- O2 M- j
    2    False
    7 M! c2 u2 r5 ^/ Edtype: bool0 f. k0 z7 f7 a: q7 i
    1
    3 ]! \& _# \4 Y6 _  E( M% e/ W9 y2+ i% \: W7 v- W9 N% q
    3
    4 c) x5 J; s! ^+ g7 n1 ]' s49 X: v0 W8 i+ A$ f
    5
    $ A5 f4 {# h0 d, p$ |6
    / q7 t. B/ E" k- ~, v7
    / N* |& n: R9 |% Is.str[::-1].str.match('ta[f|g]|n') # 反转后匹配6 j9 ]2 \% ^/ @2 F8 }
    s.str.contains('[f|g]at|n$')       # 二者等价
    & O. u& d& z! V) g3 \2 ^  @. ~' K& v
    0    False& S3 l& V! c$ l$ T5 D
    1     True. F: L% n4 O" i) L1 K
    2     True
    7 m* x" z: B0 T( k; ^% Xdtype: bool! o2 @# A; d# d' j
    1. n+ z: n, {$ w% u1 r8 B
    2
    ( H( R# \$ o% @) t30 C, f7 w: t9 V( ~3 R
    4
    2 _7 Y" X+ p0 V+ Y% R2 m5
    ; C) Z% J0 m! I" k3 ]" p2 p) |6
    + S3 r9 ?" ^5 ?2 p4 x) t7
    3 k6 y" y8 n8 O: v% }str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
    2 {0 Z4 M& B# D' v6 `s = pd.Series(['This is an apple. That is not an apple.'])# I- o5 ^6 J1 U& J2 r

    3 N- t* l! E* B" N; Es.str.find('apple')- @  k$ x( A+ v2 J# l6 ^# W* Q
    Out[62]:
    3 W' `' z: |1 b" ], q( g0    11
    - \4 r4 X- Q6 j0 ]7 M3 o7 wdtype: int64
    % g& j% P9 }5 L' Q# j4 a) V5 V* F& ?% i7 @+ v% F3 G0 K
    s.str.rfind('apple')# b. {% t! E2 Q
    Out[63]:
      D0 u5 \3 v3 K2 a3 h  ?6 X! M0    33) ^+ H" Z; C& ^4 z0 R
    dtype: int645 W5 q( v4 ?) Q7 p7 i6 b
    1/ v9 ]$ _% D$ K
    2
    , ~& r- s. k' M' {6 D$ `7 J# v7 ?34 G" q5 c# h  H) ?+ r" D
    4
    0 }' z3 M4 L) s3 n0 D2 y1 v5% m7 N+ q- e3 Q9 x( O; i$ C
    6) g; u0 z7 [' R0 Y( N, L
    7
    ( m! r( Z. Q: ?' ]$ g8; @- `! Q! x' x3 w' K
    99 X8 q8 {/ i2 r; k5 @
    10/ u5 o' I4 G/ {9 _9 W0 P
    11
    5 H1 }5 t! v- d9 C9 ^# `9 z2 p替换
    2 ~1 q+ H: t0 h9 _: c9 x: n# s  N+ F  kstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    - i# h8 V8 ]' ]- d9 q6 rs = pd.Series(['a_1_b','c_?'])
    % }/ k( j% K6 Q; u1 ^, a7 A' `3 S3 F# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
    1 i& ?5 S" `3 f  Ns.str.replace('\d|\?', 'new', regex=True) : \& e( z0 N" X8 Z& o6 J$ Z
    % w1 @/ U( u& x7 W
    0    a_new_b
    9 F0 O7 t* P1 X0 x1      c_new
    " R3 D% J) e, G$ |7 |7 t7 K: Udtype: object3 u4 _7 T4 J% s5 U$ \) ^
    1+ I7 p. B) |. F7 S. c+ q! P% D
    2' C# g0 k" H5 k8 c$ E3 |  F
    3
    * `% g+ }6 G2 p% G4
    ' _9 L3 o* ?( ^. f- _) m59 b* j, e7 d" v' w% X9 Q* r* R
    6
    1 |; ^4 ]6 t5 _3 b) @3 \+ L7  B" u0 b* ^. R% q
      当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):$ S( c/ B5 e6 k$ ]

    # R4 d0 ]1 c4 fs = pd.Series(['上海市黄浦区方浜中路249号',
    ' j8 }( `6 i" j) n* R7 g3 ^                '上海市宝山区密山路5号',
    1 Y) c6 L! i7 i4 m% h3 W                '北京市昌平区北农路2号'])( j, I" s: y. M
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'7 x$ S0 s& [/ z) w9 o9 S$ I3 p
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
    ( `: a( Z2 D- ndistrict = {'昌平区': 'CP District',! @7 ~4 [$ F$ z) d( S8 }' _+ V. D
                '黄浦区': 'HP District',
    % l; V' R, V" z) K6 j8 w: }            '宝山区': 'BS District'}% m: F) L4 g7 A# D2 X
    road = {'方浜中路': 'Mid Fangbin Road',
    8 E8 Q% M. c' L4 i. _8 P/ a        '密山路': 'Mishan Road',
    0 g! C3 X7 l9 O, Z* S( b9 e        '北农路': 'Beinong Road'}2 w; N) |  Y' A: r& q
    def my_func(m):
    ) `+ Q1 k# O+ U" d2 V  p* ~  H    str_city = city[m.group(1)]
    6 l: v/ \4 W  _8 B    str_district = district[m.group(2)]' H9 ^' ]$ q  [, B2 J
        str_road = road[m.group(3)]: O! `5 i3 V3 W: t% F1 p
        str_no = 'No. ' + m.group(4)[:-1]
    # u' h1 v% I! I& X0 ^* a. c    return ' '.join([str_city,) u8 U- Y7 l' f
                         str_district,
    + n! ]! @- R7 E# _! I* B! j                     str_road,
    ! P8 B% d: s0 G1 F# a4 n                     str_no])! P3 }5 h3 W5 F$ Z: R
    s.str.replace(pat, my_func, regex=True)
    / ^" d/ v, H0 H7 B, M) a# `2 I# X; J4 b# B0 c8 |
    1
    / \% V9 I! j- T9 H" Q6 Y2
    $ @% G9 ?5 P+ T' G9 E3) m# H8 O+ [+ x  X- @  H: r
    4
    " d' a: X6 Z- b+ g3 Y3 C& i7 q4 h; _5
    7 H8 X' P6 s  V7 T6 N6
    * O3 h$ O; e0 C. ~7
    ' D* E$ M" ], j8 q/ o8, n0 E8 k1 ^/ u/ \4 ^; X9 U
    9
    & K2 r) M+ ^  \& Y- y102 O+ s0 B& U6 P; Z& T
    11
    / o! E$ X: P6 y; m125 `/ f% b" A( v* {  E
    13
    " |6 R/ a. x2 g3 ~! e, d- I. B14$ I% W  M$ Y! A) O
    15
    % P5 c" f+ p: W, M% o) X8 G4 a, ~16, a- F, D) d/ t* D; ~/ D) U
    17
    ) H( y- m; n# {7 s& j18& K$ R3 |' G, C2 ^
    19
    0 J: C1 S) ]& p3 @4 h" l3 l20
    , T0 C! e6 G2 k219 Q" c  ?# x2 f9 `( y
    0    Shanghai HP District Mid Fangbin Road No. 249
    % H/ ]0 [$ Q- e5 v: l  ?+ E( h1           Shanghai BS District Mishan Road No. 55 G4 |0 A+ I$ e5 A' Z6 t
    2           Beijing CP District Beinong Road No. 2
    * i+ B1 y* U: w5 y3 l; ndtype: object! w9 b! F5 f) s: N! R1 L
    1, @# o+ p9 Z2 K9 P7 _$ S
    20 H5 N! ~: Y1 g9 t
    3/ i- j: E. Z) ]7 ^2 V  a9 C% q4 X
    4
    9 l. [7 x; K# b6 M+ U. V4 o这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
    1 n& b: H7 R8 s
    4 n$ P# v8 v) @1 \# 将各个子组进行命名, _+ P* Z8 f7 c5 {
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'( ^3 {$ l  k9 U9 J
    def my_func(m):0 w% x+ q2 @/ N: Z4 v7 H* N
        str_city = city[m.group('市名')]
    " n% o. f5 J* U! B  s" T4 S    str_district = district[m.group('区名')]
    * ~5 A2 ?; }! \5 O    str_road = road[m.group('路名')]
    ) Y  T: \: o2 F6 u6 w2 r+ F5 d    str_no = 'No. ' + m.group('编号')[:-1], x$ A+ D0 b) b' k0 C' G4 V
        return ' '.join([str_city,* b; G6 x. T$ ~6 @1 j
                         str_district,% w7 P# ?5 |$ G, O
                         str_road,
    8 n. h2 M1 D4 _7 @4 z                     str_no])1 v% N3 X" v, \, @+ R) v# U& i4 t
    s.str.replace(pat, my_func, regex=True)
    & B$ _3 t- ~7 S2 C% h1: E  y0 r5 v; h
    2
    " j" P3 V$ b. W9 d/ I- B+ v5 c' D3
    ! g3 Y5 b7 `; v' @4) f9 {! m7 E: \
    5' @. n6 y% Z5 i# q8 y
    60 w' d" L0 o" b: T
    7
    3 R% X: W, d% Y8
    0 [6 A% P5 s, z  ~9 u9
    9 o: ]& J  J% j: {5 z10, p7 S" s/ ?% f
    11* v/ ?6 A* L" f# k* ~
    12& Z6 b. S% o& v7 n: ?$ W, D6 G
    0    Shanghai HP District Mid Fangbin Road No. 2491 S1 Q# W$ N0 [
    1           Shanghai BS District Mishan Road No. 5; e+ w( Z2 b  n' m2 ]. b
    2           Beijing CP District Beinong Road No. 2
    7 |# f" j$ ]; Y& K0 Fdtype: object
    7 D+ M1 M& a9 X& x7 u/ `1' ~! _1 t/ e$ Q5 B+ J
    2) w+ g) \+ f: K9 w
    3
      _; C" s* f( _$ a+ \4% G6 X* c; e) d4 y: \0 w0 x2 V
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。5 |% r/ q5 N/ y3 R  o0 Z1 q1 N7 D, f: _

    : t8 o( O: C2 V- i: r( L8.3.5 提取
    5 S* [, l* d: h/ I; j9 _( W2 r4 y5 rstr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:2 e- `4 y+ F# F
    s.str.split('[市区路]')9 ^/ }  b' z; Z/ c
    Out[43]: 8 g, |' u. U4 m* o+ n; k
    0    [上海, 黄浦, 方浜中, 249号]5 O0 }2 o2 H3 R" z1 Z
    1       [上海, 宝山, 密山, 5号]
    ) h; ~6 z. U) }0 y& W* G4 K. q# idtype: object& W. S5 O( }, T7 ?
    ; u. Y( T; m- \6 `1 x1 R
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    - I6 g" r* n( v  M8 e  A6 b: ~s.str.extract(pat)
    2 c+ ^/ k: i( E) o/ Z; q: \. gOut[78]:3 B5 O: g5 [9 A; F
        0    1     2     31 j* H- c$ S* z* H5 o. V
    0  上海市  黄浦区  方浜中路  249号
    ) ]  l7 Q5 g9 m+ G. A' ~" \' h4 L1  上海市  宝山区   密山路    5号9 I0 l& Z$ ?/ ^; F7 @
    2  北京市  昌平区   北农路    2号
    , g$ e$ k2 e; J' g0 z0 F1
    4 R; Q2 U2 P! S4 e2
    + s6 x+ t6 D: q. @/ M3
    ' w# C; t3 v9 a3 Z/ y40 @( L8 |5 ~" X1 N  z0 R. N, f
    5  W8 s7 e& R5 @6 e- v
    6
    5 s! {  L2 z" q. R, Q. |5 y. V7+ o% }+ P; K0 A
    8) P5 {8 \- [) p. O; K4 h  Q8 O$ b
    98 o8 V- }/ t* T. X" |6 I' i
    10& ^5 \9 i& }, f1 E" H3 B% o
    11* H3 q, g6 e: b* T) J2 Z
    12
    ( J+ L, Y5 U+ }. f, ^% U1 [" i, Z13
    3 F0 L, L3 J6 h; `+ X通过子组的命名,可以直接对新生成DataFrame的列命名:
    1 d; U7 d% C" v) \; n& \0 i
    7 x+ {- o% O1 N- T$ ]pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'8 h( N4 I5 J1 a+ [; u0 X4 ]4 m
    s.str.extract(pat)
    % r3 M3 P9 x! |& x! zOut[79]:
    $ e( i$ p) L. |  f    市名   区名    路名    编号
    9 }" g, _' A- P6 j- g, b0 H8 `0  上海市  黄浦区  方浜中路  249号3 h0 U3 N$ F+ n  a+ }, s- U4 n! p2 x
    1  上海市  宝山区   密山路    5号
      O  w2 }% z5 x* C5 N2  北京市  昌平区   北农路    2号
    , h6 j5 y6 H3 j1
    6 v' T; l. F1 U/ ^2 o  e0 t% F8 M; G2# U) W# S, ?* P9 y( l" x
    3  j: X. H1 F' W
    4. T0 {' q6 J6 w" H% R5 C
    5
    ) C& f2 F" ]8 g% {% `6 a) A6
    ; _7 I  {! t! `: \7
    " Y1 H% ]  ?" F7 U8 \str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:0 w; ]- O8 z# ~! D) R
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
    " O  r# k  L& lpat = '[A|B](\d+)[T|S](\d+)'' M9 s7 x5 ^9 n* ^# A
    s.str.extractall(pat)
    8 \% r+ W" b$ m0 Q9 eOut[83]:
    3 J9 ^; e/ A* `0 S/ w& T  Q       0   16 Y2 Y4 |" D& I$ ]7 ?
         match         $ P  z( G( j) G9 F) ?
    my_A 0      135  15
    " R4 p9 p8 R: s% H) z; O7 C( M     1       26   50 Z  o. |% K7 S% m1 V1 R
    my_B 0      674   2
    6 J1 H" I6 x) E% |, V3 c) ~     1       25   6/ H, p& c4 p  o( k  e( v
    1( Z/ B0 U! G" ?6 a: I& `  `
    2
    , o, r' b# }% y" ~" t& ~3
    2 p% Y  l  T9 P2 E3 a, Q3 Z% o4
    7 u1 K9 L: ]6 @6 w% J( m5
    : c8 B0 v$ T% }8 H, s6' o# T; w6 @" i" C! L
    7
    0 {# d: o1 u0 X4 F& w. e$ ?8
    " N, Y7 M% y0 C5 [/ \; X. }9" w4 H( S( p/ R6 @. f. e
    10
    , {* Z/ d$ C) D& S: X5 J( U5 v! Zpat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    2 @7 X# s' z; R' `s.str.extractall(pat_with_name)0 N' Z- ^# F3 T0 E/ ]( z0 w
    Out[84]:
    . B9 h3 w3 t/ F9 x2 e0 G6 d1 @           name1 name2
    , p: v2 p: r4 w, W) y     match            / v' R5 q; S' J; ^/ X7 ?
    my_A 0       135    150 b/ c* i$ s' S* n
         1        26     5
    9 Y& E- t# a1 Mmy_B 0       674     2
    ' h! J) U4 g4 v" g2 b- J% u     1        25     6
    2 u7 N/ N7 s# u( G* X" G1
    5 H' j& N& `% L2
    & {5 u5 n# B9 G6 I  K" h3' p- k1 f& `: y
    4
    . g( E4 h1 v) u) P5 x5
    9 R) o8 i+ w$ N" u5 m6: c0 T( a- d8 j" O, e0 F: ]
    7
    % h& r( e& a4 |% `4 C8
    / D/ W  \% M( m  q* C9$ a8 j! z( p, Q
    str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
    ! C4 p0 |" A$ I3 n+ A3 ns.str.findall(pat)  m7 L' ~3 {3 W- B7 e
    1
    % @! @" {* B9 {8 H5 q: k6 q5 }my_A    [(135, 15), (26, 5)]8 ^) D; S" M1 C0 B
    my_B     [(674, 2), (25, 6)]
    0 a3 r4 k3 a8 _2 U& ?dtype: object
    , Z7 B7 z) Y: Z5 j5 s1
    3 A% O0 W: e; i% o6 g" ]2! ?# G0 L8 p4 ?5 U2 x% E4 f' Z
    3
    6 b8 k% n! X2 W8.4、常用字符串函数
    * K. c. Q' |  a0 r' j7 m  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    3 n( \6 E9 {1 }. M% ^- W& G! R$ e" }
    8.4.1 字母型函数
    ) y  ^. Q( @/ c" T1 z: b  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:7 z4 a( U; O/ }- u  q& |6 |

    , v4 G+ ^8 B  C# v# c1 J) D6 L7 qs = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    + L5 n8 }2 W4 Z
    9 D5 p8 Q- {2 B! Js.str.upper()
    + E% w5 X! Q3 a7 C  [Out[87]:
    / n8 V5 I, {& ?0                 LOWER; ]! s0 @9 Q0 u8 P. O) ^( n
    1              CAPITALS
    1 {, t$ O: N) R7 K$ u+ c  j1 t2    THIS IS A SENTENCE
    + P& _$ [! |+ \! ~3 j6 K3              SWAPCASE% C- R9 b0 _! ^; T% n
    dtype: object. G4 i2 W. a0 ]& H
    ) x* R; y) \: B  e) N- v
    s.str.lower()
    1 R5 L! M- q$ W& eOut[88]: 4 r* k% M) v6 @2 e* |4 o
    0                 lower4 l4 A; o; b1 q
    1              capitals- V7 L- e7 w1 ~4 U) m" R/ O
    2    this is a sentence) r1 U+ d4 p% d! d/ E
    3              swapcase! x$ V3 k+ I' P/ H
    dtype: object
    6 a2 d3 E- D) a" X) ^8 E
    ! X$ g1 n9 y" I$ v$ ^s.str.title()  # 首字母大写
    % |  K# l0 [9 Y" }; H; wOut[89]: 1 y+ `* L  D" V
    0                 Lower/ g: Q( S/ q- F4 O- p9 k5 ~9 g
    1              Capitals8 v5 q6 h$ W5 O! r) M  }
    2    This Is A Sentence
    - Y# Y1 m. }; W* C9 ?: @3              Swapcase/ d0 W6 v' A5 B4 }2 Z5 {+ F
    dtype: object
    % `# }- s; A/ A
    / @% S3 f( a* a/ z+ w% ds.str.capitalize()  # 句首大写
    ) Y7 {  o3 b0 {# q2 }/ D0 [Out[90]:   a! f$ l* C4 M& Y6 `/ d3 S
    0                 Lower1 Z7 E+ N) d' R' ?( R
    1              Capitals
    ! i( e! |7 [+ I7 d8 g9 P: U* w2    This is a sentence
    1 L1 ?9 n( W# D3              Swapcase7 k( n0 g5 a7 e, n/ v. R0 c
    dtype: object: a5 N8 Q" \) P/ c
    + p: @' Q, q6 K6 |$ [+ z
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
    1 {$ @7 x! m. U+ L5 ^- n- y9 IOut[91]: # R4 ?+ n/ u, N$ B6 j3 H3 O
    0                 LOWER6 O( N1 f) K. W8 `0 W
    1              capitals
    5 D- c5 m/ z/ w: |/ }0 k2    THIS IS A SENTENCE8 b% V9 D; E% T, v# ]) c
    3              sWaPcAsE  t( e' u0 T0 Q$ y* T# w
    dtype: object
    " m- B, c0 K+ @# g' y' E  Z
    + }# V- m5 k! Es.str.casefold()  # 去除字符串中所有大小写区别
    0 Y1 Y! c( X+ C  ~: ?, |( x
    1 W: G3 ^2 E9 ]' t0                 lower
    3 |5 \3 q5 {! u0 ?( V1              capitals
    ' ]# Y* d! H, ?1 X9 ~! t# `2    this is a sentence  W# R- s" ]* S! C( R0 V
    3              swapcase% i8 q2 U" @- |" R

    - Z1 a, r' [1 x. p: X0 r/ k1+ |/ V2 I+ j' b0 g/ {4 ]
    28 @% e) a, c# Q* e* i8 v+ t6 B/ U
    33 _7 q5 K' b5 G4 Q& p% K. D# p
    4# c# p8 v# w# j/ z) J
    5
    * i6 r( x- e7 T2 B& m6
    ' x; c0 _2 D8 U, B7
    1 J; K6 B' `6 V* k5 Y( h88 |, B0 d1 W. Z7 a+ ~: F2 \
    9
    " ]! f- \: q& K( n; Z10# a: ]9 F' @+ L1 d' p) Q4 `
    11( ~( Q) R2 x+ E6 B5 w1 R
    126 Z1 O+ K7 x' A9 Q+ J& Z5 y0 @
    139 s3 ?: E. ]+ u" E3 {+ T
    14; i, v3 V$ W$ c) N2 Q0 V( f& d
    15( J$ R3 f* E) b4 [4 q
    16% [- U& E# Q9 q7 ?( _) k: Q+ S
    17# O  r3 p8 q$ i+ |' Q
    18/ h% P8 h& T* c( B
    19  P5 Q0 B/ j! q7 J
    20
    1 l% L) M3 y1 T( u* H21
    ! D3 X/ N3 u" G/ z2 W$ u! K22
    0 h: p3 i4 f( i1 g9 _23) L7 S3 h* S) n, b
    24: r: T* \* f: \
    25- C* a& R* G3 ]8 P* ]. t, k- y
    26
    , \% _' E; b' R) C+ ?9 _27& L% w& f3 ]6 X- P" x. B* j2 Y
    28! }& S7 u# H$ k9 O1 i: g
    29
    0 f- y( }+ D) ~0 o30
    ; b7 w, B8 Y/ ]4 L) @31
    2 f' X. ?7 |4 q32/ W: O+ n+ b3 `
    33+ H% c; F! Y/ \1 f' \' q2 ^! B! x
    34: |5 Q3 ]. P0 Y1 f8 }
    35/ g# @# x! \+ t8 ~
    36
    & E. X  H: ]# p' S37
    5 I1 b! T' w  B38
    7 n5 O' y/ T3 R- S39
    # n- C+ b8 \6 {2 z( m6 I40% J9 K9 n. K, w* e1 W
    418 m$ G. }( t. Y+ {  K. {, h
    426 M: d2 {( l4 N* Z% K! f' N# [
    43
    ; `. m* i7 M' L7 `: c8 g# F44
    6 J+ d% j- Q% x9 R45) {. H5 ]/ w, @- Y( s
    460 K$ Q; u9 c+ B& G2 I1 e* R$ I# A& E9 M
    47- k6 S  N2 {# T. k3 U0 _
    48
    / x( N+ B& T# u1 T7 `3 ?; A8.4.2 数值型函数
    % O8 C+ M) l6 h9 {  这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
    , U+ [3 g$ B; q$ s
    * A& P6 m+ x. ?- S) [3 Ierrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:9 I4 R5 v) I' {4 H+ m
    raise:直接报错,默认选项
    : [- A7 Q* L  ^( m! Z4 t. Q: Rcoerce:设为缺失值
    ) p1 V& `, e4 ]8 g% Eignore:保持原来的字符串。
    7 ]8 L" X  \6 I5 ddowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
    0 F! D+ \+ w" c; ]3 x: ?* [s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    # m$ G$ o2 C# `8 T/ H9 W  G
    # _( V3 K: A+ t& s1 \6 Spd.to_numeric(s, errors='ignore')
    2 c+ g. m" K6 aOut[93]:
    ) k" a5 B* c1 Z- F2 \1 H0       1; K7 T9 f, s& s) x! K
    1     2.2
    $ n) T: z3 ~! V2      2e$ i8 N3 K' R# F2 y6 G
    3      ??
    : @  B- M1 N& k3 j8 I- u$ V6 `4 F4    -2.1
    % L- B) _, D- }- `: ~# `3 B5       0
    2 ^1 I5 I; d3 _4 s2 @! ~; i) y1 qdtype: object9 P- d0 x' }( I/ f2 \4 T% X( V

    : A( U) v, T3 Jpd.to_numeric(s, errors='coerce')+ K. X. H( ]( P
    Out[94]:
    4 p- H8 Z1 M: I0    1.0% j* Q+ Y! v0 Y' r  o
    1    2.2
    # O" U% n$ d& K! {! U# f2    NaN
    , T7 u# m2 v( }3    NaN1 E& A# P) Q9 O9 J& c) j
    4   -2.1/ I$ Q- [# F  F& ]# `! h' \
    5    0.0/ Y- O: t' M% ?+ d
    dtype: float64
    % V: \  ]* F' H1 F% g- Z+ X2 T6 }6 s. q8 j
    1
    1 H8 I" Q6 S0 O; O, x# P28 J3 [% Q) b! B0 Y6 I& K% M9 Q
    3
    & Y/ N& }' {) T, E4
    - c* Y0 o! a6 M  ~. }58 l; x( F$ t* W2 [' o
    6
    ' W. |* y+ w$ I5 o) d. d9 ~! c7
    * R  L) e6 L1 @) ?86 Q) I9 Q  @1 s' _
    9( Y* w/ t. ]  h0 Q. X- r
    10
    5 k* E- G; g+ K11
    0 z( I; e/ _# _0 u* [5 G12
    $ ]7 u8 B( a1 K) [3 g# E  l) g13' E; ]+ j2 G( s2 e! ?
    14
    . D: _$ i2 D+ s15+ D& H+ z/ i/ p
    164 \5 ~- y/ \& i. h: q
    17
    ; Y7 G1 T) F( E( I/ N' _8 B18
    6 O2 G8 j8 E( c4 r7 {6 v, z19
    . A/ h0 t6 B0 y+ R. z" I0 f/ T- G20
    5 u4 U% E5 T* v+ |' B# F21- I6 b- e1 g, h6 J' s0 F$ c3 S1 R
      在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:) M6 Y3 w4 A5 o7 C" t$ g' \  i+ g
    3 ]# T. b- Y) Y2 ?! d% U
    s[pd.to_numeric(s, errors='coerce').isna()]' Q- s, Y4 `. M& O. }$ z
    Out[95]: 1 @" L- k9 Q; `9 ^" Y# t) y
    2    2e
    9 d; H- H8 a3 }) {3    ??
    - z2 [2 e9 |6 j/ ddtype: object0 v: r& W; V7 n
    1
    0 f, @' T9 A/ P6 T& J; ~2
    - P8 ]/ g( [8 a3
    " |5 ]) X" O9 E; S5 r4  w4 ]% l# U. z. b8 D: v5 g! u9 T+ M& c0 U
    5
    " t( V/ d2 Z. y5 p' k! d0 B8.4.3 统计型函数
    . P- B7 p/ T$ D) ^& B3 A  count和len的作用分别是返回出现正则模式的次数和字符串的长度:" D9 [+ Y4 h3 }) h) D/ c" o) N
    9 _# x! {- }* _4 j; [, [
    s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
    ) M; t( B' N: k5 {5 x
    ; v4 H9 d9 s) Ts.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次  C% Y- t8 ?0 ?6 m
    Out[97]: $ A7 D/ j( ~& @. x; d
    0    2
    ( Z6 ?/ K$ i2 v/ p7 |1    2! e6 V& ?( d' [" P
    dtype: int64
    " F/ Z0 F4 t% v5 l* [& R& p) U- `) H# w% c$ y7 p
    s.str.len()
    ! E! F6 K$ I+ M' C- }Out[98]:
    1 g- o7 s' t: A2 B  r0    145 S" s9 [% h3 A
    1    19
      u% {# e0 Y% z- Ddtype: int64
    - x$ N( J3 |. ]& `; r( u2 b& F1% F  e+ Y7 q2 F  I' ]$ k1 ~
    2
    * a- `  Q! r: m/ U! m7 p) s3& d8 w: J1 f( Z
    4
    4 v# l4 v% }+ J& j- ]5: _7 h8 Q" u3 E% `& p9 Z
    6
    , K7 f" F1 s3 I0 A4 s# O' M7
    . E. {& ?* z' m) x5 M- Y  v: R8; o5 ~( k: {% R( o
    9' o! a; G- |& e( L2 F7 W; D
    10$ I2 o7 H( W7 E9 a
    11
    . Q2 H3 N& F; V% W12
    2 i7 n8 M( w" E; S13) ~- ]6 c: x2 t' N, x$ y
    8.4.4 格式型函数5 e" m4 b9 Q: ]+ l5 [* s+ W1 M5 H) |
      格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。6 Q  q9 z) X0 G; F5 F
    1 f3 |, @; N3 `+ d2 N1 k
    my_index = pd.Index([' col1', 'col2 ', ' col3 '])$ T- M" h) A$ U, b7 @/ V: C

    . _' w# S+ F: k# V6 Q1 \my_index.str.strip().str.len()! B' A5 m: V# m; L) ~
    Out[100]: Int64Index([4, 4, 4], dtype='int64')
    ) V$ Z3 G5 ^; o7 O7 ?
    3 [' z4 @; P- m0 z- T" R8 w  S5 Zmy_index.str.rstrip().str.len()' e' E$ [! I0 F3 i0 W2 b  v$ d5 V8 K
    Out[101]: Int64Index([5, 4, 5], dtype='int64')
    4 f$ z+ _' w- S* |, Z
    2 x: Y# V0 i6 m9 Z* \2 tmy_index.str.lstrip().str.len()
      k* c/ m) m! U! N2 v0 j  TOut[102]: Int64Index([4, 5, 5], dtype='int64')( i8 s9 `" `& B
    1
    . W- s9 D2 ?2 b* g: X. c2, Y0 i7 p, k  I; G4 i, T$ y
    3
    # I: t7 {7 N* w# |: P( e" M' ~4
    6 A0 Z+ r% t( L2 k" Y$ T5
    . F" l- S  \2 j2 }; Y# Q  f! [+ h& A6
      |) Q' f# [  |! B- y9 d6 l0 w9 c75 ^9 ^. q3 H4 s
    8  d4 H7 V" H/ e% F
    9
    2 [$ F% K( _& q9 e  W10
    2 u, n5 A0 B' n: D# v' |3 C! \  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
    2 b8 @1 R; F5 t1 r/ ?; ^- H( m; i  c, ~" c, I( b  s! s
    s = pd.Series(['a','b','c'])
    ) O/ P9 u, H9 c" e7 G: P( }& f0 s/ l% m
    s.str.pad(5,'left','*')
    - ~" L% [4 r3 L( f" YOut[104]: . q! l+ r$ I2 j: i8 L
    0    ****a
    0 T. D* |+ O7 Z; ?; W1    ****b  `9 w& b- F0 g- E9 U
    2    ****c
    0 b! X4 |& W+ e' }: B5 P( ydtype: object
    % J; u6 U- O2 D4 ~( y# H: `8 P2 u# J
    s.str.pad(5,'right','*')
    - X2 z- l' `3 @% l9 _" ?Out[105]: ' ^4 w& d( H/ Y$ W$ R
    0    a****
    % F1 h1 o' F" G2 L1    b****
    & L- T/ ]3 f& h2    c****0 T) |9 V; q6 _* }# [& r& ~
    dtype: object9 I- ^& }% Y& \
    3 r2 V& c2 E0 w; Z/ s; t, ]/ H9 J
    s.str.pad(5,'both','*')$ P% }  G! y" i0 D# D
    Out[106]:
    ; e8 D$ O' ^5 E3 `5 C+ d) s0    **a**7 c! L" y7 I& Q' o( A2 m& w
    1    **b**/ s/ F' t, p7 E, Y, K! G8 O
    2    **c**
    & v7 v2 k+ S4 n4 d! \! M' Ldtype: object
    ; d1 y# @/ o6 C! G9 T2 U; N& E
    , L! S# X5 U0 g+ v1
    - Q7 @  \; Y, R: Z% g# l6 R2
    ' i9 t  K+ F0 S  \1 b0 j& B3
    6 m* N  k; R- x: [4 K$ g) }4
    $ c# T- y7 r' ~4 V5
    ' R( ~1 ^, L' h0 ~0 e68 Z/ l$ a% i/ \! J. K) ]5 D
    7% }+ @& P4 }8 N' B" n2 z
    8
    6 R& Z8 K$ b: \9( t* n$ S) d' ]2 l9 p; ?
    10
    1 y" p; M' a3 E( M11; i8 @# t' x. f4 N
    12
    : J6 E. ^9 }$ t/ @% L9 b9 M13# e( A$ s( m# e/ p3 Z7 Z/ B- _
    14
    # }6 D! [2 I0 U( G: I0 Q7 r9 X4 n, I15# [1 Z0 E9 D* {' _2 T+ j
    16
    : t- B! J2 S* o5 [17
    1 g% ^$ l0 M8 t2 H/ a( q: p18
    6 h: Z2 P( e; M/ j. p! x% q) H19" h& g; L) b2 P; [
    20
    + S* T) ^7 B7 w215 m# ~7 ?# j: p2 }1 U% ?7 T4 z4 O
    22
    ; x& c8 \' i4 e, y8 W  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
    . u6 F4 g5 m; f# ~6 G3 o, E* B- C
    9 m' t! z2 C2 k  N' c, ms.str.rjust(5, '*')0 q+ `9 A$ I7 \0 _0 v6 `
    Out[107]: % i, Z- G# T( J  |2 M8 C3 n
    0    ****a3 l3 k$ s) G& o3 }5 e0 Y. X+ `
    1    ****b/ m( {/ _1 {# R# g& e9 R+ G
    2    ****c& Q8 j0 R- ~  m- I# `
    dtype: object
    6 h) Q1 v( n" W3 O' k  Z8 M# e0 f0 C" @  x
    s.str.ljust(5, '*'): f, D" E; J  S8 Q  @8 F6 X
    Out[108]: / r* u6 l$ \( i" Y/ K8 `
    0    a****
    ' p6 `9 I, Z6 a* c- J1    b****+ I6 t( X* s$ n9 m
    2    c****8 V+ u" |8 r, N/ j6 P1 h- _; O
    dtype: object
    ' V) J# k6 [6 s3 @# q1 P8 p% l) O% v# m; V, Z' B" K
    s.str.center(5, '*')
    ! h2 H8 ]; Z8 t$ x7 zOut[109]: 1 e5 ]- E5 t, X3 F; |' [5 O) f; Q
    0    **a**8 I& `. z0 F, c  S/ ]
    1    **b**; N/ N! k! V+ F1 k5 I7 \, |' {
    2    **c**
    ! J7 C8 F% ~/ G1 Z! L3 E4 b  idtype: object
    ( y8 Y" H. U  ]$ [& {2 w" ^) K0 s! }: [" K0 p2 O
    1; m0 N. _! r( @. L; B
    29 v- O- X- g; M9 j( F& S- n0 ^. T/ y
    3- a1 k; f# A  k9 W+ L, C
    4
    ' O4 J! j$ n5 C9 J7 N" F51 b8 f. R! H  @" u
    6. `+ ~! X) {' U! Q
    7+ d6 y& @3 k1 n5 w- z
    8" H4 }5 M2 Q$ x! j  F# d3 s
    9' r2 M/ Q$ M+ x1 o$ K; J3 D
    10: a. }  A- P* _' r6 Q6 X
    11
    5 T: v/ a, E/ C& h2 j" p: T127 n2 n( J( z* c4 U) z% |  R$ `
    13% \8 t' K/ U7 B7 g. a
    14# ]5 r( d7 q3 t8 a: M* f; x& b
    15
    - L2 G1 P# q" N6 Y: M16
    # u9 X7 T. C, Y  P17! j# r2 s' n8 r7 h) B0 i( Q4 Q, p
    18
    # y/ O% O; Y% R19
    ! [% _/ C. v* q( Y  b; h2 A20
    + Y  F; j, s. E$ ^/ N0 L  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。/ t& d+ h# z* A/ Q; h$ b

    5 K% Z. b' K  p) D" Js = pd.Series([7, 155, 303000]).astype('string')% U2 q' N& l( B& m" E" \
    ' F; N4 G: ^) I' d3 ^
    s.str.pad(6,'left','0')
    7 R% i  q) d- a6 yOut[111]: 2 _, |( `( Z% w1 q' V5 [7 _5 J  F$ d
    0    000007  H6 u$ {  O: X4 H
    1    000155
    , _" k6 w4 J& Q" A9 F% M+ Z  o2    303000
    + U$ w- M: F8 {4 h$ V- O6 C. gdtype: string
    ( F$ E$ Y- ]' X3 [0 T, K2 \' O6 e8 x; D
    s.str.rjust(6,'0')
    2 L" a0 o1 N8 DOut[112]: / v7 m+ f- C& \$ `' h
    0    000007/ I0 E" L7 T' d6 k
    1    000155
    ; D! R, E! d9 ^& y2    303000
    ( t- C# k, u! \8 f! tdtype: string
    " l. @% i& M, b) \  n( V' ]  S: i: R; Y- b$ n* T4 I6 K8 \
    s.str.zfill(6)
    3 @5 y1 F+ }5 }2 e& x7 J: z2 n# AOut[113]:
      B" e" @+ k0 N0    000007
    7 L& V5 D! h. x6 c' Q2 t+ [8 p/ N% e1    0001558 f% E  O! \8 Z
    2    303000. z% M0 a5 l9 k% V4 {" }
    dtype: string) e' b: J- I8 g, h9 K: }
    2 v9 ]8 A6 C( z5 ~) @6 g* i
    1
    " J) I0 v, E3 I- P2
    " {$ U6 m5 K! p6 R7 c3
    + A( R7 C6 x% b: B' x40 n! r/ Z, X# |& m3 K+ U
    5
    3 A  p- P  Q9 o64 v$ v) q# N* S  R6 [3 N
    7
    . i) i! f+ B$ B4 \% B6 H% [84 X! P' Y9 O& L7 F8 P4 r0 T. g- a
    9
    5 X! Q. N- @5 y1 g) Z+ `10, t' i* K0 P( Y7 r
    11
    + I5 \: B* Z6 ]( U12( i  h% \4 e$ K) T! E
    13- C/ I& {/ i! p; _7 J1 }
    14
    ' a) v" F& `7 Q# x+ D$ }15
    : `0 d' u; w# P& I+ G% P16
    ( q9 Z* w& u+ P" M; S* a$ k17% c: [$ ~5 ^8 n2 \: y6 k: V  D
    18' {* \9 V" \% D9 l( Y' `/ w7 T
    191 Z# j3 ]) f$ i% |! j" C# Y
    20
    8 d2 ^; {5 G) c1 ?! L5 U21
    # x+ V, p! I- B) k3 n' r22- i5 C% {% {2 v5 N6 F; c" b
    8.5 练习7 R9 X, s( d1 _9 X" O
    Ex1:房屋信息数据集5 w$ a# v# M$ |: d- I" N
    现有一份房屋信息数据集如下:" O+ h% S: u" @

    0 A3 }$ W. p% wdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price']). E/ j1 a3 b6 U  I6 d
    df.head(3). p/ P  L9 J6 W3 X7 I5 f( r4 X" s
    Out[115]:
    " I- j' x9 x' P/ F0 V      floor    year    area price  B5 I, I2 ^3 _
    0   高层(共6层)  1986年建  58.23㎡  155万
    , q6 e4 p( B8 {# d6 B1  中层(共20层)  2020年建     88㎡  155万
    % y- s) n- V( F& c( r0 a4 }2  低层(共28层)  2010年建  89.33㎡  365万+ j# T1 ?1 y5 V, z
    1/ v' K1 c* P. a& I7 W$ n( m+ f
    2; Y5 Q' k1 Z5 Q& ~' k
    3
    - l- y) n) v! J/ u. \- O  P48 s/ F4 t' }) L' ]: u, B9 C  Z
    5
    8 ]  u, b  K9 i% {0 X$ u" }' o6
    - U, e, x6 d( d7+ x/ @: ~9 V( J6 g8 S* @1 H- _: _
    将year列改为整数年份存储。6 ^+ @, D+ \3 h5 `0 S, q( |
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。) d9 K9 u4 o- R. x' Z
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数6 @8 C& p- g, ~: n9 W1 i: j
    将year列改为整数年份存储。
    . n+ x+ }+ d/ ~"""; @  \: V+ b/ d% t! X8 l& Q
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
    5 `8 U3 b! }" C- N8 Y; d/ j1 K3 {0 l注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
    2 ~4 S5 g" ~) i6 b转成int后,序列还有缺失值所以,还是变成了object。9 v: X2 h3 M- o* x6 L) J
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
    $ G' r' j! n% x% I9 }6 ["""
    3 b: B$ x; s  I! qdf = df.convert_dtypes()- V0 g% o; \" }# I: F# B# @
    df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
    ; U1 X) T+ D; @4 \: j# Edf.loc[df.year.notna()]['year'].head()
    / j$ G5 U7 g7 T! ]7 c
    # y9 N. m, z1 L8 F" `4 z% ^% r, m  d0        1986
    8 ?0 e* q, N5 q  W0 j. T1 Q1        2020
    5 V0 j9 ]9 y- [0 U1 V3 d6 x9 X2        2010
    8 m# b7 P. o3 f  M5 Z( K  ]3        2014
    ; m* B# T. V, s1 |. R4        2015
    / H. m) P- O9 B2 W6 zName: year, Length: 12850, dtype: Int64, M( t( q; x3 ^/ C  _. c0 E
    5 U9 A4 I- l  M2 j
    1
    - k: d3 V9 C' g1 z& ?+ m! C* `2
    ! Z( w; {5 g1 L$ f7 S' w, e3) O  W" _6 `+ {0 g: ?* [
    49 n$ j) o+ \5 J
    57 X! u3 b5 \% I+ r6 A+ l, K# D2 G
    6
    . z5 T7 R( `: ]* E0 Z76 V  E; Q. }9 U; f
    8
    % |/ r6 i7 ?7 P' B5 J0 g9
    1 w" Y6 j7 K; y10
    1 _9 P+ h4 o$ a11, w0 P. M: `7 |8 L6 h% W
    12
    $ j6 B- I" Z) y) I% r13: B: K# n2 a, Y% R; d# D
    14
    5 E2 V+ o% d4 z: w15
    # K+ d2 ~$ i& ~9 D, P( ^9 E16  H0 P* h1 Q; L, A( Y3 B4 q0 \0 \
    参考答案:
    : b2 w% {% r7 R5 ~
    ) A+ }: k' }* U7 C) j  q/ x不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
    ' G" `/ M- C2 P! Q1 o& v+ ?) Q/ _9 g
    df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    , l8 U2 q$ u! V4 Z6 udf.loc[df.year.notna()]['year']) W6 j+ ?( \8 a# J
    1+ J) p6 X. r3 X2 Y
    24 U1 G& b3 J. r0 w1 g
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    ) ]* X6 o6 k% U$ D/ X6 [9 Qpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'/ V* _' w) g( y* k
    df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
    8 @. d& R- r6 g/ W( p+ {df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型
    0 c" |" P! x) u6 Edf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              / Q" y6 T" V* ^! t) t
    df=df[['Level','Highest','year','area','price']]# D, ?: n& }  |
    df.head()
    # T9 `" \, }2 J
    - B( _5 K) S6 v4 x   Level  Highest        year        area        price
    * V+ T' ?' Q8 a0        高层                6                1986        58.23㎡        155万; I* w# ^+ k* D* u: ]& C
    1        中层                20                2020        88㎡        155万
    " n+ l& i$ }* k- D" U2        低层                28                2010        89.33㎡        365万
    : r( W: X( i7 j1 N* h% w3        低层                20                2014        82㎡        308万
    . }2 W; E, l* h$ W5 H3 W+ h4        高层                1                2015        98㎡        117万. S" z" Q/ |) X) B
    1
    ) t3 o& O% y7 d) w9 y2- Q/ I2 z. L1 k0 d& G
    30 B+ @& m" o9 D1 L" i
    4
    2 u3 }. c. g0 d7 }" F8 _4 Z59 R" L8 V! \) @$ g! W
    6( h' a! G7 W% E( p
    71 h% X: I7 D, k! z: j. ^
    8) o& Z3 }) ?$ y2 x+ P% v
    9' L) Q1 |# }: S9 F. s5 @, R
    10, F8 `( X! Z6 B9 X$ x
    11
    3 y9 l. E/ i: U8 P) R12
    ( E  j) K& P( ?% d. \* u! k13; `; K8 K/ y- i0 x$ ?* ?' K# w5 D
    # 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
    # e* ?, Y. j% d7 e' M7 Ypat = '(\w层)(共(\d+)层)'
    5 s: Q1 R/ \: i0 pnew_cols = df.floor.str.extract(pat).rename(7 J" }' Z" X6 O% S7 u( V
                        columns={0:'Level', 1:'Highest'})& K! @  U% y" W% C# i1 C+ g
    ' n9 h1 H) y7 b3 z, U
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
    6 x' i1 u3 n+ Tdf.head(3)
    6 Z6 \" ]9 u9 B- l5 e8 K6 Q7 E, q7 p$ `4 s" o, o& C
    Out[163]: : r' X9 C* U; R  {- Z3 U; _
       year    area price    Level Highest
    1 W) J* ~/ M. Q; L! z0  1986  58.23㎡  155万    高层       67 j: {9 h7 F" T) H4 k
    1  2020     88㎡  155万    中层      20
    # I- f/ V1 V  K. R5 r2  2010  89.33㎡  365万    低层      28# j5 `& x- ~7 h# l6 d& P& P
    14 Y  u) B8 H3 o# `
    2  n1 X2 w( I1 C' n$ I
    3+ w8 t. @+ h3 q- k" @
    46 V( J6 _& p; N' M
    5. i+ e. z$ l4 H0 r
    67 J& z/ P/ o9 G* @" Z: C! v9 T
    7
    7 p. d/ l+ u7 i8
    : w2 V2 F, z* x0 K/ ]9
    1 n/ D8 {, ?  M5 b10
    ) n( ]+ G' V: g% [11/ l8 w0 z; ]  J( z" q9 V6 X
    12$ d9 |# X0 {" }! d6 X
    13: l! o  B6 j- E9 M7 u9 S% b
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。/ D7 t% E4 X4 |) W. l
    """
    ; y- s( y- U! c. qstr.findall返回的结果都是列表,只能用apply取值去掉列表形式
    9 b1 ?4 R7 \* q9 W! b参考答案用pd.to_numeric(df.area.str[:-1])更简洁
    / i+ }% Z. X$ @. \1 _; V; @由于area和price都没有缺失值,所以可以直接转类型
    ! H1 V2 U' x& s"""# C' ~1 f  J" p% e/ Y$ e
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))( @+ P: r5 P, O5 N  D" t
    df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
    + ]5 X+ i* ]- Cdf.eval('avg_price=10000*new_price/new_area',inplace=True)
    3 A. J1 T7 [+ \2 Q) L5 k& v# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
    ( X+ v/ f# U$ l1 {# _( {# 最后数字+元/平米写法更简单
    : C$ _6 ?: g; a9 N3 r; [# }/ kdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
    6 t' w- W" m! J0 O2 _0 X% o# w$ \del df['new_area'],df['new_price']
    + f$ B. E. O! H! U" [+ idf.head()4 F2 o1 h" `% s- d* Q* ?  ^! M/ H/ z
    : B3 J% s! U4 N( V/ \! P0 a1 r
       Level        Highest        year        area        price        avg_price  l6 l3 ?( S5 ?
    0        高层                        6        1986        58.23㎡        155万        26618元/平米( f& ^, V: T& f( d$ Y0 ~
    1        中层                        20        2020        88㎡        155万        17613元/平米, N( r/ `* D% v7 r" W
    2        低层                        28        2010        89.33㎡        365万        40859元/平米$ g7 o5 U/ j5 M
    3        低层                        20        2014        82㎡        308万        37560元/平米
    % p; j, d2 I+ _% B5 z4        高层                        1        2015        98㎡        117万        11938元/平米! C7 b  v+ T: @6 J/ b

    * y7 K$ p- v1 \1 f+ o10 s/ M) V/ `! p2 D
    2
    % m( C3 q" }" e' V- C, X8 P' ^- z* Y3; m! l$ L; s$ T  _  J
    4
    & g6 c; A) c% ?58 z+ y6 ~- |9 L4 p
    6, I4 B( U) {: }9 F2 C4 n
    7
    & O3 G2 L9 ~1 h/ B+ i  |89 s* M) ^" Y: C8 ]
    9
    % `  F0 H5 g" X8 I( c' x10
    , q. W# _! o8 M% [11
    7 t( }7 z5 q. ~12  ]2 e. y7 }2 X/ f6 @
    13
    ; k! w: I% W# N! ]+ @14# H( F# w3 P; c8 Q
    15
    0 M: e& S% h  O7 l3 M2 \16. n. I; _% v) x1 [" m1 H
    17
    ' H) v0 F8 y, N18
    ( {# p3 {3 ?6 i8 t( h  i19
    . U5 z0 a( T  e: n9 s200 Y& v& W6 D$ k4 l
    # 参考答案
    + E- v; W) s  x. ~* fs_area = pd.to_numeric(df.area.str[:-1])
    ) p0 I1 F' a1 v! V: }s_price = pd.to_numeric(df.price.str[:-1])1 h; }/ s4 i- U( i. u- ^4 \
    df['avg_price'] = ((s_price/s_area)*10000).astype(
    * x( R2 V' S3 a; ]! i                    'int').astype('string') + '元/平米'
    ( O( m# m, v/ }5 M' T: {1 B
    % B) h/ h# c& L5 Q5 a6 zdf.head(3)  X$ X& d7 y& e" S* H
    Out[167]:
    " l# o8 r( d) k/ S4 F3 g& T   year    area   price   Level Highest  avg_price
    7 M% L% \* b9 `  O0  1986  58.23㎡  155万    高层     6          26618元/平米
      T  d! v7 `  g1  2020     88㎡  155万    中层     20          17613元/平米
    3 `% o" y! Q6 x  _. Z2  2010  89.33㎡  365万    低层     28          40859元/平米, ]: @( J) b3 R, G; g: X
    1: W2 n6 J6 i( Y/ @# e
    2% G( D' H1 T# ?% c
    3+ Q! ~- l" T8 J. D
    4
    ' p/ M$ j& k" h  ^% E5
    ' |: t2 E1 u; q' O9 [6 n0 X: L" v1 [62 E8 H1 ^. W+ }, v8 j% u
    7
    ! D7 p8 G: e) u) w8& p5 G& l/ [2 |& [- k
    9
    6 g* g: P) ^- B: Q10
    & h8 H1 b2 v  Y* d# l4 P114 ~& T/ o! ^; y$ t, ~1 m$ d
    12: ?8 x2 h# t8 j2 \. K/ [
    Ex2:《权力的游戏》剧本数据集+ N" j4 o0 E" t! l' Q
    现有一份权力的游戏剧本数据集如下:& ?) x6 E9 Z3 E! X& X/ ]. Q  A

    3 l: m! V. m1 o9 Q( Tdf = pd.read_csv('../data/script.csv')
    * I! }3 A, j, m. S/ o- gdf.head(3)
    # o& j$ r2 G  Q4 B3 A* s' f0 y; l7 L0 q4 D6 k8 l) U; ~
    Out[115]: " w! k$ k8 J% H# s4 U! j
    Out[117]: 2 I1 f* L- h2 F6 B, O9 W7 h  Q' j
      Release Date    Season   Episode      Episode Title          Name                                           Sentence3 n8 Y1 ]" ]# P
    0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...( g5 k1 t5 T5 `3 b
    1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...5 ~1 k' P3 p+ B, T3 l
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce . T0 S. r  V& b- u2 v( B9 u; a, o
    15 U# @: C! Z  W# @4 y" R
    2! W8 i& j+ R, f. ?; y! \# G  ]
    3: `, I1 F9 B7 }6 \; e( e# Y
    4
    ; a2 Z1 D4 R6 V* n% w5
    8 `4 ]1 g- u7 ~. R; p3 O1 r- y, V7 d$ D6
    & M. @2 D) ?' {% u+ d& p; X7& T! k, p0 D& D2 z/ s5 A0 O2 c
    8
    3 H9 F3 f( [9 T# n( F( t9
    : ]* B8 m- w' B/ l: @; u! }计算每一个Episode的台词条数。$ @6 D4 v# p1 O* Y; J5 [8 b) Q
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。- X* m/ {5 S' v3 f1 N# ?
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    " {; f; g* d, [) w* g& W计算每一个Episode的台词条数。
    + p: Z* Q! i9 v) A8 H/ |df.columns =df.columns.str.strip() #  列名中有空格
    % f7 ?- l4 w- h  jdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
    5 G, a0 \% n/ j2 |
    : z+ q- l( ^0 }season    Episode  1 Q) X* C$ x" N! J  B' E+ b
    Season 7  Episode 5    505
      S7 o7 A8 ~* T  B1 Y6 ?Season 3  Episode 2    480
    ! @! W% L- q7 y: \, m. a; FSeason 4  Episode 1    475
    / E) f+ l4 T3 z8 P4 \Season 3  Episode 5    440
    3 ~$ D6 |( n" OSeason 2  Episode 2    432* y, f, v6 B; J3 m  ^8 Y( A4 }
    15 ?! H+ Y+ s% ]$ b# A' l' M7 B
    2
    $ W0 \0 n" D& P' ~1 S3
    $ q6 m( ~( T8 ^* W2 \: N; D4$ G" J; I8 f6 ^' E! i+ y2 L. M
    5- j, E) }( X2 n" ~# }0 b0 a% `
    69 Q0 P' |+ G) J0 a" ?0 s  I  K/ X
    7
    : B, c7 \7 K. S; g& Y) Y8# V3 S+ S4 p# J& y
    9$ I6 @5 m/ ^4 G
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    8 U7 n+ M0 ]* `  Q6 B# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数% C" p+ O# }) Q& A$ r
    df['len_words']=df['Sentence'].str.count(r' ')+1" Y& b' G% x# j- F# X
    df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
    . j, I4 W) d7 A% i
    $ R8 d3 \2 T: p" V- c- W8 m* T% eName
    ; K$ t& [" ?$ |' [( P% }, p1 Qmale singer          109.0000006 }: R+ U2 @# j: x( s
    slave owner           77.000000! l) n6 e  N( l$ g
    manderly              62.000000( J4 K+ O/ O2 m) u0 F& P, I
    lollys stokeworth     62.0000009 x2 a4 f# B4 h2 \: l6 ^4 a
    dothraki matron       56.6666670 M$ B1 F" i2 w8 O
    Name: len_words, dtype: float64# Y/ c3 o7 N& O6 r; J! Q) d7 J
    1
    7 ]7 b8 a% p  |9 b, c5 `' j2+ [! ^& j; r8 B4 J7 c: x3 V
    3& t1 h  o1 ?) Y( J1 q! K. S) g6 G
    4$ t$ B0 a" P$ T
    51 l) k8 ]% u3 n$ G# S8 a
    6. X. v0 p5 I$ l' t7 k, p& v. m. v5 K
    76 B0 x: C' @9 s4 P+ I4 A/ g
    8
    % g- X$ l  {, k; g& a( \0 G/ L9+ \6 x% h. [: e! Q
    104 g4 D3 o2 s$ _) C4 x6 s
    11
    ( J, K6 x% E% Y$ i0 L, W9 G9 Z' S若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
    9 r: t) C, t' s  ^/ Vdf['Sentence'].str.count(r'\?') #  计算每人提问数
    # |0 Y7 g3 E4 I1 Fls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0! }# ]# G, ^8 }, q6 z1 G
    del ls[23911] # 末行删去3 T( G8 r1 N+ H% I+ l
    df['len_questions']=ls# R: J: ^% l% I9 Q
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()) b2 k1 P% d( z: e

    8 k6 I8 H) M+ z' Q7 x' r/ I5 yName" _: H* D$ s6 F  b! X% T2 C
    tyrion lannister    527
    ! k- n$ x5 H9 N' ljon snow            374; X8 p; H* X( }; L$ @
    jaime lannister     2836 U) g/ M# E6 P/ Y& M+ m) o
    arya stark          265* W- E" h7 ^1 C  ~6 \! B
    cersei lannister    246# `) p  \+ V( }
    Name: len_questions, dtype: int64" m, G1 |! A$ m$ c. x2 X8 Q
    ) L+ z# r/ R, w
    # 参考答案9 Q- r) X0 E, G0 G: u4 H1 F
    s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
    + a% v& l% Z! a$ ?s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()( L- z" Q6 u- i7 ]6 T/ z

    3 ^" L* q  t3 J5 g+ ?0 l- H4 \1
    " M* M  x! s3 s, W/ R2' P+ P8 Q2 \+ K0 l- S
    3
    " r5 ?. H% Q2 N0 f3 w4
    - o/ w! r/ {# c* B% l  m( d0 `2 ~* N5. H" j( p" k& o
    6
    ! T5 V3 j1 ?9 |& Q+ R7
    7 Q2 g0 i( @# R8
    9 l% h. p& _  }1 h& e6 m* I9
    2 f9 n% Y* k5 o) ^3 A. @6 Y) c10$ x" w( r5 F. c. o4 M( u( g8 x
    11' v. Y# p2 M  s: X4 @
    12
    # T0 c1 M1 [* C13# A# J, P' t( |  T
    14- ^0 w- B" K( W( }! k0 g3 x9 I0 N
    155 |, u& \5 K' S+ B3 K; O; ~* j7 L
    16; ~: n2 E; N5 X, E9 f3 A+ E( _
    17
      p3 w3 M& r1 ]' Y3 P7 q第九章 分类数据
    8 \2 Q- t6 I' K) W3 o, iimport numpy as np' C# N6 r- a( Z3 z. B
    import pandas as pd
    # \$ ^& F# o8 I3 s9 o# r! i3 W+ E1: x; _: D+ y; _; y
    2
    - @; X8 U; f( B2 c; [2 N4 v9.1 cat对象# v, Q$ a# p4 ]; ?9 v2 L
    9.1.1 cat对象的属性
    . p: Z0 K; E  m- L( k  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。: E( H  o  d3 b  G* m8 D( t
    & a. W7 u9 r) R' d: [/ z
    df = pd.read_csv('data/learn_pandas.csv',/ o8 z, D/ j% x
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    ) i/ {) t3 H% D8 m0 I6 ms = df.Grade.astype('category')
    5 M' H4 n& w" E# R9 O$ D$ K) g$ t7 b- w4 d; [% K8 f4 h
    s.head()$ r: g+ |; \0 h6 T. k# `, V
    Out[5]:
    ( w5 `8 X1 y3 s- O7 [. \0     Freshman
    6 ~( V$ r" D3 f$ X- j0 s1     Freshman1 ~; u3 \. z: I5 X3 ^
    2       Senior* ~1 v- e; ?- k2 U  }
    3    Sophomore
    " R: E7 o! V2 ]8 w) C& X4    Sophomore
    ' T: H! f6 M8 ~. |8 A" D/ c. M- LName: Grade, dtype: category5 O0 p* G5 u, z8 z4 G6 @1 q. I. g* e% {
    Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']5 o& h% s: D# B, i2 F0 K) i
    1% }2 [. s+ g2 Y1 X
    2/ \% b1 E3 l' I4 s7 p4 c
    3% [6 n! n1 {' Y' e; O  U7 q
    4$ H+ e" g' y! b
    5( C$ ^2 J) Z$ b7 M% S  _# X
    6* H* n# h! m# @( g+ S# }
    7# }7 O- ^" N# |& H# v$ A* R
    84 T! L- W3 b# N" A5 E8 U* D
    9. S8 z  m4 L5 a
    10
    & i5 R+ w: i) `# V119 g3 l/ A$ g$ O
    12
    6 C& Z' g) m# b. S2 [( f# M13$ b. z7 |: j0 `6 q& X0 _( ^' q
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
    3 i: |  k) i9 f5 [; d+ J$ E, \
    8 S  V5 y: P6 G1 P: f. m& z0 C( c& ]s.cat
    7 e6 x9 K/ d. R7 c3 _6 gOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>0 D, I8 j0 h7 e
    1
    6 G( _$ y, C& L7 p/ _" {& p. J2. |# V1 l: Y# |5 N# ~
    cat的属性:
    " K; S3 S$ _4 Y8 L6 |! w1 ^# G* y
    cat.categories:查看类别的本身,它以Index类型存储2 l& [9 X5 w' @' @+ i; Z
    cat.ordered:类别是否有序
    $ B3 F# s  ?' f' j& l( U4 N7 ~- Acat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
    & K4 v0 P0 Z# ~+ r$ W& rs.cat.categories3 O% H$ |: K/ L' ]8 P
    Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')( ^9 w+ I7 a4 N
    . W. V1 M9 o9 L
    s.cat.ordered
    1 Y- M. W3 }4 }$ {! TOut[8]: False
    3 c( f, t/ o- F
    + d8 e; t$ y( \  w! [s.cat.codes.head(): ^9 M/ q- y8 ^4 L
    Out[9]: + x2 B( ?2 E9 v8 v. O# _( m9 j) m7 ~: |7 C
    0    02 B: U0 M* \; V
    1    07 I* A# M7 f8 P0 C1 v
    2    2, T& v! C; A! |1 u- S% a
    3    3+ x% d9 L8 Y; S2 L  c' @
    4    3, Y! m$ N. e+ n, s; z1 Q  j- H
    dtype: int8( h9 n( P/ j: u7 J3 B" o7 O
    1
    # `6 q8 J4 ]/ d( j: T- D24 F1 O7 K% S& O$ d9 Q' E1 F
    35 ]: z+ T; r$ Y' G3 B9 K
    4! @( w$ C7 w9 c0 \! w# h7 F, c0 J
    5) f. [0 h: h; Q7 E9 c
    6
    " e- `: d* k+ j  D7 z: F7
      ^% W4 @1 i/ [* V6 p6 W8) D5 f/ L  u* X+ d; Y
    9& @* O( j; Z# m9 ?' u# f# q+ y
    10) Y+ W2 J% u# H6 o$ j3 q) N( [
    11
    ; _6 z. D% U5 ^- l9 I12
    , l9 [$ T- p9 e7 n0 j( _& g2 |13
    2 K% v* g8 _$ u6 A3 `0 a14
    + n. m* a5 ^2 e+ d- e9.1.2 类别的增加、删除和修改
    & |" C9 M7 T$ m6 q% U  通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?2 X9 x$ @4 z* `7 G: S

    7 |% l) j7 }# s【NOTE】类别不得直接修改" |1 x! m$ a6 q. N0 h& _5 R
    在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
    ; X4 F6 f& x( U  S. b+ t: L7 X6 a+ p- J! Z
    add_categories:增加类别
    0 r3 u; M2 r0 F% M  S  I8 i' _s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
    8 K, j5 u4 R+ O, F: B4 W* ds.cat.categories( M: `+ W* W% {* f4 e2 M1 |
    $ c- v: ~3 C7 G$ x' S
    Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    1 S" O; ?+ b: O  e17 o! t/ o% [* b0 W# ?- y7 ?
    29 T# r# F" |8 f4 U4 U0 P
    3
    $ j2 ?4 u5 R& Z; d' \45 h/ l8 r' J) \+ Q1 i* S2 p- y
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。% C( Z5 d% @" [# H; F, i0 X
    s = s.cat.remove_categories('Freshman')
    3 K% ]8 l& t) N* ~+ A  [+ c; T0 E- U, {7 e# C6 u! V- P
    s.cat.categories6 Y: ~( t( U) {1 ]' h9 t5 d
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')+ C4 @) n7 E. k  S: E( ]% Y1 B

    , p- N9 k! J4 z7 k! |6 ^# X+ Hs.head()
    0 `- a( |: x6 IOut[14]:   ^6 T) o) x5 w+ C! F  n
    0          NaN6 h+ z9 A" K9 B# W4 M0 K
    1          NaN9 F1 }* {. Q; K5 ?1 m
    2       Senior+ O$ I. F5 x4 ~0 P8 o" F* C( ^
    3    Sophomore2 ?2 c/ ^6 E2 L$ q9 z9 Q, S
    4    Sophomore
    - c8 p" F7 p* [! T* `Name: Grade, dtype: category9 N9 l; u, U: m+ k# C8 @) p
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']1 U+ n% ^% v% ]3 ]
    1
    / X5 q9 q% r) Y% I- o2, p( m8 Q3 ?' B) A, G/ a- S6 t8 Z; X) D
    3* a1 F8 f1 D  ?) F
    4
    / h6 j+ ^4 O6 s54 V- p1 N" s& C5 p3 h" ?
    68 C3 n. }: g% B' ~
    75 t- {3 [7 l3 l5 Y
    8
    0 j6 r  O; t  t" j! i7 A9( s, ^) ~& f0 o* m( t" S
    10
    4 z4 e. H2 M7 q# s# t% o11, h3 e- h. e* G8 u9 S
    12
    + [# q+ Z$ W6 w! @4 [- @13
    8 b7 j- D! B6 z5 @14
    4 M. B1 k6 Q% S4 Y  h# Rset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。) H9 E9 }2 ]( ^4 g, w- o- f
    s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士* j2 M0 z. |7 ?9 s% X. S* Y7 S
    s.cat.categories' E5 l( c8 Q- C
    Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
    / c' s# a0 h9 S: v4 T, p: z# n$ [0 `' ]; F# z  z
    s.head()1 m4 b5 V4 K0 Y2 x5 |) L
    Out[17]:
    * L/ C1 a+ s0 d) G1 Y) l0          NaN
    , p( q& I+ T3 A. ~' K1          NaN
    4 i) b( B( H( |1 k1 P4 b* i, O2          NaN
    ; r5 K, n, e( U3    Sophomore
    ! ~2 @% H0 L# d* [. Q4    Sophomore1 Q& M/ F& o. C
    Name: Grade, dtype: category
    ' o# t# @* L  g8 }; x( A  gCategories (2, object): ['Sophomore', 'PhD']$ H3 S6 F/ e! K8 n, S2 V5 y0 C
    1" r# D; m% i3 C; Y
    2
      e2 m$ f+ |( Z" E37 O  M8 q$ _1 \( e) q
    4
    " L( _# @2 b: @/ X5
    ' T9 o. z' q* T4 ^1 j8 [) L# Y6+ e+ g) e( \5 @& Y2 V
    7- M6 C* b; i% ~' G7 B) p  w2 k  W- h* L0 _
    8
    : Q9 q1 b4 \. D" N& q9' \( D- Z4 ?& p7 X$ {* u' }
    10' c$ i! l) t  x( ]9 Y* \# H; ~
    119 d6 F/ J& P6 v) C: x& f/ W& w1 f
    12
    7 d' W8 d+ \# U+ G, i5 s13
    ) r: f9 t! L4 B7 W: v2 cremove_unused_categories:删除未出现在序列中的类别
    ; T4 P- K) l- k' j0 v5 qs = s.cat.remove_unused_categories() # 移除了未出现的博士生类别  ]4 J  E. y) f3 ?  A$ Q
    s.cat.categories
    * C+ V8 b3 |  _9 Z
    2 f# `1 s4 c; h+ B8 y, H, bIndex(['Sophomore'], dtype='object')- A. g: @$ G+ m0 P4 `
    1
      I* E4 U& l; T% V2
      ]8 a0 L9 y) ~3" f( ]9 q2 C$ G! Z7 b2 K; G' f
    4
    , Z4 z7 D) O  X, Arename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
    . `* s* p4 \  h: t6 \$ L# \4 es = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
    / E* b+ x. |2 T  Y; l$ Z. _s.head()
      C' y1 Y8 g- ^: c
    0 |6 J; r! }: t( C4 o5 I0        NaN  r0 s6 |6 P) ^, k- l  E
    1        NaN
    / P0 Y9 X; J$ |2        NaN
    8 l5 }8 e  [+ I" Y0 C) U3    本科二年级学生
    0 _! [5 G( v3 `$ M4 e4    本科二年级学生. k3 D2 ]0 J" C. _- c) a
    Name: Grade, dtype: category) c, i  ]+ S. q9 K' a% `
    Categories (1, object): ['本科二年级学生']+ Z& d! g. h9 X9 A3 H& i
    1
    . L8 P: J/ u; j# E; V4 B8 V+ W. w* l2
    + ?: Q0 b8 s$ L$ Q; X5 j' @3" G& D; V8 `$ [
    4
    ( g8 Y1 o/ @8 {' x$ A5& ^  I8 A$ `7 R2 z) c
    60 a3 u( z) Q% P3 N8 o. Q
    7
    # x" C4 |  W: D6 U, ~81 C5 L, B6 b9 ?% F3 J& l: J
    9; i3 u, Z+ Q+ P8 @3 v
    108 R% J8 j# r3 e$ `& @0 G! x& g
    9.2 有序分类
    9 r/ {  W% }! s9.2.1 序的建立
    9 \" g2 l7 H& V$ B  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:8 i8 K% o) A  A' _! f8 j) o
    4 e$ o. E3 K3 W- T9 L) H2 X
    s = df.Grade.astype('category')
    , _& O4 L+ J! P$ |) {, As = s.cat.reorder_categories(['Freshman', 'Sophomore',2 g$ ^6 m5 z' U
                                  'Junior', 'Senior'],ordered=True)8 T0 d) e% g1 E2 ~4 R
    s.head()
    4 V" v5 l& ]5 z5 K& e, u  mOut[24]:
    . I% P- @) q: l" y2 G+ n0     Freshman9 h9 J7 c; I' ]1 b
    1     Freshman
    , p  j" W; Q) S* o7 S4 P2       Senior
    7 {6 D, i4 x9 V( M2 ~& z0 M3    Sophomore( d& B4 E4 w; c, r
    4    Sophomore: ]! j3 W! t% C0 p
    Name: Grade, dtype: category( u* v2 E2 T% N1 v6 d1 X
    Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']' d( m: P/ e6 D# E1 R

    3 W9 N) Z9 ]" k' j1 s% z7 l* h. s7 e4 qs.cat.as_unordered().head()3 q$ z6 @, P9 [0 k
    Out[25]: : W  z: j: T6 M" r" S
    0     Freshman( X! q% h4 O) D6 C9 L  V
    1     Freshman
    9 C' j1 v) j5 T( D2       Senior
    0 p- j. P# M/ g3    Sophomore
    ( `& O; t% Q9 Z$ C$ V# W6 x5 I4    Sophomore6 V1 Z" Z' O+ ]" }- x9 H
    Name: Grade, dtype: category
    * a$ E) T: C. S: w- _: Y; |Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']% U6 y" ?5 l6 J; Q% a

    , i6 ]* d1 u( L1
    7 X  P9 R& F: m. x/ T- q: c21 p0 V" r; o! Q$ p
    3' H8 ^0 U/ p- S5 t: j- N
    45 x2 m  G1 J  Q3 _" [5 c
    5
    " i2 n3 M% @4 K, R6
    2 z& _3 @5 `  U3 S( B7
    8 O' g1 u6 \5 D0 U8
    $ e. h& }. q0 L; E9& v1 O$ R- R0 O+ J6 g6 c
    10
    6 d8 {0 p+ Q! [. T11# \0 T) O! y; \# e! [5 w( o
    12
    , u+ [' @1 L) `. u* V130 q+ ~0 s6 z0 I1 M
    14
    : ^4 D1 J. H% l% Y5 W9 [, w15
    $ t/ N# |) r$ H16; z  c: W5 a. R% P
    17; R1 [! u% Y+ u0 C' B7 Z* G5 O
    18
    + w8 j1 X! a/ \: S  K. R, p19
    , m8 c8 g  S3 l4 L) V0 Y20
    / ?0 W, F# i" c5 P. V5 E21
    4 e- k# @) a. u22
    5 J. S; W% i. r  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
    0 |# S0 ^) W1 A/ X1 ]: I5 m% H6 I) w7 N, z
    9.2.2 排序和比较0 i6 n+ I( m5 Y8 p* _
    在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。. U2 X/ Y9 t' K; C" Q2 V2 G; q8 M' j% G
    % R' c; _4 Y8 e4 U" q2 Z
      分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
    ! `% e' i. x3 [  c2 {, E" @5 ?- ?" n. U9 g1 V9 }
    df.Grade = df.Grade.astype('category')* G4 D. U: F. x* W
    df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    + {) {$ D7 x2 Q. Y7 l0 H7 |df.sort_values('Grade').head() # 值排序. Q1 p( W; ]( ?$ s% j( p! W
    Out[28]:
    . _+ I) ~) n# ^2 V        Grade           Name  Gender  Height  Weight
    4 p% F+ D. T1 \. _( x0    Freshman   Gaopeng Yang  Female   158.9    46.0
    3 Q3 h. ]4 Z  {! i. _9 m105  Freshman      Qiang Shi  Female   164.5    52.0! C9 f  F( t, o% H$ f
    96   Freshman  Changmei Feng  Female   163.8    56.0
    ! f. R0 Y# o# }, x88   Freshman   Xiaopeng Han  Female   164.1    53.0
    0 O! S% B, Q$ N# U4 `* S( g81   Freshman    Yanli Zhang  Female   165.1    52.0% T* U0 Q( _% _0 y7 s
    % r- m: B- P0 ]* s0 K- O- n$ ?5 z# q
    df.set_index('Grade').sort_index().head() # 索引排序
    ! |0 U( j; {( q4 zOut[29]: : K6 `3 c( f. V! W( j* n
                       Name  Gender  Height  Weight
    " M9 m: ~  _7 E2 _* JGrade                                          
    " B$ M' D. H8 _; S% _/ Q0 XFreshman   Gaopeng Yang  Female   158.9    46.0! f6 K1 b/ K2 B1 B" D
    Freshman      Qiang Shi  Female   164.5    52.0
    # C, x) ^% V( v; ZFreshman  Changmei Feng  Female   163.8    56.0
    9 Q+ {: C3 }" V  TFreshman   Xiaopeng Han  Female   164.1    53.0
    2 |9 ?4 ^1 m9 I  N( e. a9 @Freshman    Yanli Zhang  Female   165.1    52.0
    ! d$ }* n( s* J
    " t# I+ }1 \: W- K) l( N2 j& M1& t. `( F/ A+ u5 }
    2, o6 u  K  h2 _
    37 `& ~$ Z0 L. S7 _
    4/ M5 }6 e  C6 `* _; s9 M' N3 G
    5
    ) @: r8 S4 m: S2 z+ Q- q! z* J- W' X6
    4 \3 `+ R/ \9 v0 n7! `2 b6 x# s& D" j
    8% S: M" |# s( v1 J* G2 t
    96 |4 `! o- N* e3 n% l5 c
    10% P+ s8 G5 f1 o! M1 l
    11
    . e  D  C# _& X2 c" z$ I8 h0 B8 C12
    % Z3 Q. \1 ^1 N% g' j; s- e13; D5 f) e* x& j+ ^1 J$ D
    14. z6 l& W: o+ b; t; A
    15( b6 e1 v/ k4 @9 b! S  Y* l
    16" G7 |# g# H: d3 M2 q
    17
    5 @& c# }/ I; v$ B( m& I18
    ; y$ C$ ~) R/ s199 R* B. E1 w( J$ L% q
    20
    7 W" j4 B' X1 a. `, t0 S1 u/ _: V  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
    . L$ g& A8 D+ B! w/ b0 d3 c+ G3 @) t/ s! Y" Q$ b& e
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
    ! ?0 H3 s% T% E! [! e9 z9 r5 [>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    9 b5 K+ N: s6 e$ Zres1 = df.Grade == 'Sophomore'0 o& u2 `+ W5 {: j3 y2 E
    1 u& F2 w7 F* H1 |, X0 E8 E) ^1 j
    res1.head()
    $ y" w" R* f, _2 lOut[31]:   J( O/ k. O) D9 e( M% ~" M
    0    False
    " G# w8 r) \& |1    False4 S# h, ~+ D! a' k- {- ?
    2    False
    & X' b( [* i( H9 V" x- B3     True8 i; a" o' z+ N/ X8 w* B- _9 R( X
    4     True8 ^, N  E+ M- n7 l
    Name: Grade, dtype: bool4 @7 E6 ?9 `# h& _  _6 M, K$ z2 s

    " T$ j9 s& a% |% y' [) {" Hres2 = df.Grade == ['PhD']*df.shape[0]
    + `; W- f, `5 d! W6 k; E( q8 A% \
    ) I+ \+ ]' ?5 l  n% _" `# h6 m' t  fres2.head()0 o8 a) o- A, F- t2 t4 \9 I
    Out[33]: ' v  t# M2 n  a
    0    False
    $ M$ |; n) n" L1 Z( p; e1    False: ?" E* \& k1 m1 m( B9 Z, e
    2    False
    9 ^2 W9 P3 Q, v" y3    False
    . Q) R( k4 O9 X2 M4    False1 @. O2 E% ^: N: V! k7 X' U; [
    Name: Grade, dtype: bool
    + h% a7 F. N, H" H. ]% R& T9 X# D( m) z
    res3 = df.Grade <= 'Sophomore'. p5 `% Z: l9 R) R3 [; |
    3 C! |0 d+ I; d# X" Z) q! E
    res3.head(); I; i( Q' v- l9 f2 ^, Y- {
    Out[35]: 4 S3 w$ D- |9 \9 ]- H
    0     True8 P5 J9 W9 m2 E1 l; Q
    1     True; Q3 \8 \! I) W3 j9 ~' I
    2    False
    # @1 N( f3 e8 k. F# c9 a4 n, S* A3     True/ ^- R' {9 Z% H, {) m$ ~
    4     True. Y$ _& j2 J- O& j0 Z9 a+ M
    Name: Grade, dtype: bool* P$ r/ J# c' B% `/ T: R- e' J

    ; K' i' c/ d$ S8 A$ I/ _# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。" c% E" D& h) h; a$ d
    res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
    & J* b$ g7 @# b% o3 g0 `
    2 g5 ~. w: q2 w9 C9 j" tres4.head()8 p+ Z3 @" C- K4 r" z# t5 o% Z
    Out[37]:
    6 \0 ^  H& a6 P9 S0     True
    / h' W0 W) f; r1 g% d9 C& @: J, I1     True% A, L* _+ ~, |* A2 {) q
    2    False
    4 N; V+ s7 N6 g) l6 u+ j2 y8 \3     True- g. q  |' W, P% v1 [
    4     True# c/ O  B# M1 @* [1 m
    Name: Grade, dtype: bool
    : P' T% |3 g1 J  B( [4 _$ ?% ?! M9 n" M' m
    1
    5 v& p. W: u0 a& A2; U1 E4 h- o& p# E
    3
    0 P+ G. U; g( l, F41 v. G7 V. T) G  H! f' ~# {
    5% u% O- \" b' ?1 M
    6! }7 t) X/ \4 p0 W2 y
    7
    : K! M! |* D5 g3 v& T( d) a8
    + W* _9 R, N! v. }- R9
    $ z) Q% d% l, H! k- s/ R3 {100 Z9 X% ]/ ^9 [! f+ y, M( X  `
    11  g' t+ f7 V9 u8 y
    12( s9 V* c1 P( O! S8 z( d
    13
    ! a( l3 m* B; D/ k' R' ]4 F3 E2 |# ~140 Z  E- w9 ]3 W8 L, Z
    15
    6 P- f4 @% s& m) J! ^7 [3 B7 f168 E  b& K+ i" m" v, r8 P% `0 W2 s
    17$ l( |  j* }# w, F+ V
    18  C/ r1 @: r/ G2 u" |+ V
    19
    9 s7 U% m3 O2 c- A; A205 T0 {( h2 T3 z0 n" `. Y) T
    21+ N9 |7 q* n8 w
    22) ?! o: S8 t9 q+ \9 \9 e8 M6 C
    23
    # }: j5 [' E$ x1 ^24
    ' a0 }' h* P5 y6 Z: A25
    5 y) P! n7 q5 f; u% v0 P4 r26
    $ B; P* B9 g; g- g: v9 }27) z$ C, ^+ Q8 q8 U: T" d
    28
    3 q2 P% x* R5 F. s8 B: |* Y29
    # u# f) Z) S% [/ A; q% n5 u30
    ! x" W# C8 s( d. r% I. S( D, q31
    9 A  j& K6 h) k/ I9 X32% v* b) C2 O$ o1 S" O
    33
    3 [" o' E& b6 @4 w" o. B* I34
    * a" E( l+ R3 k6 L35
    4 M5 X. c! Y# n5 h8 c1 Z9 ]36
    / t. @) A3 j! u# i5 A37# [2 p/ A! k0 x* g- @" G
    386 {% \7 j' K! V+ ^
    39
    . a" c7 Q5 {( _; Y# i40
    * R; ]% ?9 o* u1 @7 h, D1 }41
    & x4 e9 S8 \/ w! c426 M) r4 \+ r. G! x  [
    43# T- _# G. h  u" X0 d+ S3 E+ }: a
    44
    : o& w8 N2 A0 e$ h8 O9.3 区间类别
    3 @1 \8 `# `# u  j5 A9.3.1 利用cut和qcut进行区间构造
    : N# H% x& ?( l  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。  s) Y0 |9 z6 I" h* c
    3 v$ {8 s- w$ n8 w
    cut函数常用参数有:
    * C' P- n% q+ jbins:最重要的参数。$ S" \- P, w' D) H7 j
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
      B+ h9 G% A  q$ |: [/ f/ Z/ q也可以传入列表,表示按指定区间分割点分割。8 {1 t2 ^6 f, D6 h) t
      如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。: g6 d) }, X7 Y+ x2 P' t8 M
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
    2 F+ |' ^( q3 \6 x3 |4 ^# j- i" `! n
    & Z5 p6 N5 d+ k- i% Js = pd.Series([1,2])2 {" y. R% |/ b* v
    # bin传入整数
    8 \3 f! Q5 d, ?4 U3 ?' f0 v9 \; K. D" z; ]/ V" Q! {, s) q# b/ |' P. o
    pd.cut(s, bins=2)
    ; t4 C' W+ z' F0 xOut[39]:
    . V, j: [# p9 A, Y- e0    (0.999, 1.5]
    ; ]5 S/ N* K6 Y! u8 Y1      (1.5, 2.0]% o. v9 Z' ?1 J4 V( t& ^3 G/ H
    dtype: category% m' `9 ]* X8 [4 k
    Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    # p5 W  m/ \2 m1 L: Y
    2 d* d: W+ ~3 @. d/ w3 epd.cut(s, bins=2, right=False)* l& z: ]1 }, _0 A: k8 S, P
    Out[40]: " l+ w) x4 B( J: L+ U/ o
    0      [1.0, 1.5)
    , J7 }( n: k+ \7 b, K0 L- ]1 Z1    [1.5, 2.001)5 t7 C# R4 x! l7 S0 R7 h
    dtype: category6 q  Z. z+ V  D: I4 p
    Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
    ! P1 \3 m& X% E5 H1 @# Z. Z$ J! v5 V" g

    + b+ d& B+ {1 t4 N; D( u% Y( N# bin传入分割点列表(使用`np.infty`可以表示无穷大):
    ; @% y& V" L. y7 k" cpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])1 B0 i) i1 K$ P
    Out[41]:
    $ K* ?. N; W7 R2 X0    (-inf, 1.2]
    ' J6 D8 g" l& D/ m0 e' C1     (1.8, 2.2]' S9 y+ g) g3 d1 ~9 Z1 l3 {
    dtype: category
    . M$ b/ s& C$ @) HCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
    3 \- ?9 t0 k  d# S" d
    4 u- y0 }' }$ k, G7 a6 a1
    , U: ~( J9 X/ }0 G: P; s$ M2# V- L( Z9 r4 O: X9 q) K
    3' f8 l6 i) Q' I) {5 B8 K
    4% \2 b  H8 V, T/ \7 W& \
    5
    6 ]0 D6 ]! O2 }7 `$ c+ ?67 e7 w7 L2 ^0 s( r6 |
    7
    # o& ^1 l& D0 F5 y! B# R. D% Y8
    * F- P3 M7 E) h# t1 R$ E9
    8 [0 E: d( z  F* B10- A$ |9 ~: i+ |$ U
    11+ u8 A9 u+ F) G) N
    12
    + M6 G( l) e. `) v. W. @13/ x6 n* h: ]6 i  p5 {' O
    14
    ; o. t/ o4 I  A, B. @+ N15
    7 O( f8 {7 x& T7 T  \! E4 S168 L/ T  r# i- ?+ O
    17
    % _% C7 c) W; }2 @- ]18
    7 w% ~$ x+ b0 o. k6 D/ J/ {191 m2 i$ G* a, m" k% e$ ]4 i
    20
    6 u  \: q6 x$ b0 y3 Y0 k6 y21
    $ N. O; U3 p0 i0 D9 w22
    ; t+ W! K+ _- L+ Y2 k232 l; H' S7 s+ H6 i1 _3 Q7 q2 ?, t
    24
    ! F9 }6 q' e! c; \25
    ) M6 F5 T' I& Slabels:区间的名字$ H, V/ F9 |: i& @. x6 g) p% i# b
    retbins:是否返回分割点(默认不返回)) I2 j9 V% e1 h+ I: A
    默认retbins=Flase时,返回每个元素所属区间的列表
    . m$ {7 D9 E9 e$ l, Wretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
    5 M; G* v+ U7 s3 [2 d. h) V8 K0 Z4 A5 z4 I, g1 H# b
    s = df.Weight
    / }% c" o5 P: p9 n2 q* V" Jres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)* J1 f0 W9 G) K# J+ u! `$ O
    res[0][:2]
    9 U) [" n* k4 J- ~
    $ q0 x6 M! |6 qOut[44]: + d5 V7 H* d; J+ |8 {: H: S" t
    0    small5 b, P5 X0 v: m. k) B0 ]
    1      big
    3 c1 [  c. O/ G" \dtype: category
    * @: `# B" L/ q; @  xCategories (2, object): ['small' < 'big']" A) M% c& v' S+ t+ ]

    " x9 d3 s! V& {5 @- `res[1] # 该元素为返回的分割点* R& P# [; _  C
    Out[45]: array([0.999, 1.5  , 2.   ])$ i) W9 ^, w- J' V% x( W
    1/ b' n& V8 A& |- l; [6 G% [" ]0 `* K
    20 t0 B* a$ P! L' r# H& Y
    3+ \/ f, w$ P3 n$ [- ]  l
    4
    1 V% ~( Y/ B9 i8 {6 m3 S4 c  W56 _/ n( {" o7 _2 a. G
    6
    ' Z) U7 Q) b' i5 ~" J0 {7 t- B. g# R7" c: v+ ]; ?3 T8 r; M
    8
    ' Y2 E( j2 y" c) M8 d& P, a+ E, d- L9% m4 N3 z3 a) U
    10
    / ?# L% z: ?( q3 {5 O11. b+ z0 A6 ]6 s7 B6 y
    12, f* C" `( R9 X
    qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
    5 O( _( z/ C: \# l1 vq为整数n时,指按照n等分位数把数据分箱7 P& H5 T2 A2 i8 q* d" v  i
    q为浮点列表时,表示相应的分位数分割点。
    ; Q# ~3 v1 x  [2 ^" J& @2 L3 As = df.Weight
    : E; N+ {; Z8 |# U1 i: z' G1 F3 P; r  u' x0 o) w; L2 u
    pd.qcut(s, q=3).head()
    : l: `! a: N+ V9 e2 l9 H. }, ^/ |" COut[47]: $ M$ K2 o, C! l/ t
    0    (33.999, 48.0]
    1 H6 D( x; N& d! N1      (55.0, 89.0]
    : j% D* X  U: C0 G1 ~" [2      (55.0, 89.0]
    # }$ I6 x/ w; X1 W% h3 S$ Q3    (33.999, 48.0]7 \7 Z3 Z1 M$ W+ {
    4      (55.0, 89.0]% g' |  L# s  }  D5 C) C
    Name: Weight, dtype: category
    , j3 ~- U* m5 }, ?+ gCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]  n, u5 T) T  I  c" _# X
    6 Y& A) O% N0 r" B
    pd.qcut(s, q=[0,0.2,0.8,1]).head()
    ! N9 Q/ I0 }$ K# b# J' f% LOut[48]: ' i8 }% j( ]$ b& q( Q0 @$ L# z; ]
    0      (44.0, 69.4]" i! m: a( W; y4 j
    1      (69.4, 89.0]
    & }) U8 v- g0 T! u2 U- }5 b) E0 C& i2      (69.4, 89.0]
    $ f5 a; j2 V; T3    (33.999, 44.0]8 B: ^; @/ O- L8 _8 p* f
    4      (69.4, 89.0]! b. B7 D# O- T# X( ~
    Name: Weight, dtype: category" w/ _1 L# `1 t
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]# k* C6 ^! f2 J5 W5 c/ h- P

    4 r/ z0 w1 @8 U; O/ x! j2 @1
    ! Y0 ]' z) s% C; W/ |( |+ ?# \2
    : N* }- v" E1 s3 ]" F/ g+ V9 [3/ ^/ `' |! f! U" r% I
    48 ], ?4 ^2 F& ^7 G% ]9 K9 L
    5* D' P0 I- h1 R
    64 ^; |7 `: s' @; h5 e: \0 V
    7
    # y' c# {! k+ }( t+ C+ k* M89 f6 z2 e# T* W( Z7 a
    9
    1 G6 l6 X: D8 [- J) D& f  Q10" T3 z2 j5 G% j1 F' k
    11
    0 z& H4 n4 o! L) k2 Z! \5 D120 }7 w2 c- F4 X6 n" K/ H0 T# B
    13
    9 x1 i3 {9 Y+ U5 c+ O14
    4 T( \7 E9 t  D15
    ; c5 B' \* @# D; x* F  a  E% r16
    . C, O" B6 s9 l" O/ g% \7 a! @  @. N, |' o17
    : W) {( L  b' G. x5 G+ j183 u- ]0 L8 i. K& \1 J% Y
    199 c0 r! n) c  k- d% [8 M/ Y
    20" H5 f4 C9 @1 C# \
    212 [0 Y4 C: u1 }2 F7 r
    9.3.2 一般区间的构造! y2 c$ V  `6 Y
      pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
    , ^  d8 M6 t. b' A2 ]( V* c( `" O2 E# O6 y6 R" o; B
    开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
    / A" S4 R4 r# m2 }my_interval = pd.Interval(0, 1, 'right')0 [1 b) q$ n7 t3 E) r1 x

    4 n) h7 v" z$ ~, wmy_interval  J4 d% |) r% D5 N
    Out[50]: Interval(0, 1, closed='right')
    - B, g6 Q6 r6 g4 c9 w# \0 {1
    , D3 q( N& a. b# @0 N4 S2
    * Q% g3 \- h/ {4 s3) m. q- s3 H" U- h- F$ w  X
    42 d3 f) U7 K) t( \+ U- s. H1 g
    区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。+ k' E9 o8 U* t* B* ~% [
    使用in可以判断元素是否属于区间
    7 E5 u! S4 I# m- @/ V7 e用overlaps可以判断两个区间是否有交集:- {& [5 [2 o$ d; {
    0.5 in my_interval
    & a1 i* h  k5 @3 s3 S! Z" H
    1 a' v# q& y' f8 R9 tTrue4 F: K) w. @4 k" N7 Z8 ]5 G& W8 u
    1
    8 Q, i1 i% N) Y$ t$ }" h. R2
      m: B. A: O: i& @, A3
    ! S' [7 B9 ^# d6 f5 hmy_interval_2 = pd.Interval(0.5, 1.5, 'left')0 ?0 ]# F' G. r( F
    my_interval.overlaps(my_interval_2)
    7 ^! I% t7 @+ t6 n# I0 a8 }/ G& _# ~! E2 E1 o
    True
    7 q, \, m/ R' i8 X3 e1# S: n, K! O* K, w
    2
    8 l( n. v: d6 F+ V+ K; m3
    ; U# L. y  p. y3 Y4
    / |# m6 F' m9 ^1 y2 Z  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:( r; m9 E8 O3 b1 x% @

    & N' n; ^/ p+ o/ Yfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    ! H5 T  d. [! l5 v+ Vpd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
    ; n3 q% A$ s3 m# f2 |3 ]8 d5 X" r/ \) }& o6 n3 F+ B! }2 p
    IntervalIndex([[1, 3], [3, 6], [6, 10]],
    ) }5 k2 v" m8 Z, c& R7 g9 L1 w               closed='both',
    8 d- l% m- t" v8 [! n% E" V" j8 |               dtype='interval[int64]')
    : v. w: H; N: b11 @$ S+ K. d- e6 H8 U' S
    2/ Z! o" E3 ?) D# g7 A! z
    3
    ! v5 m- d1 i8 N- N4 m) t( n4% |$ |4 d) I) ^+ s, `0 H3 o
    5
    & x/ }' b$ E& x6 E. d) q, {from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
    1 v, \% G$ s! G, ipd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')- w& ~- O; g$ M$ z& ~& Z$ _

    9 X6 J' H- Z1 sIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    0 ^2 @7 g, B2 K  p) N8 _9 R. E                  closed='neither',
    * X9 ?' I9 e8 G/ P9 F4 H5 q                  dtype='interval[int64]')
    - g) K' f+ ]# i" T2 H- n14 D4 P# p' ~6 |% a# Y, {( n' S! r
    2
    : u0 r) O- k1 J1 q! j3$ p6 `$ z& s4 u! ~; A# v
    45 y- o. b$ f. K9 F
    58 I- Z* X& V( ?( b1 S4 r
    from_tuples:传入起点和终点元组构成的列表:
    / a4 N( q7 k4 I& z. R% bpd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    $ j1 o* Q( S! g% p3 \: D5 ~4 }3 z6 ?9 m- V" e7 e
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    ! a2 z  y4 p: b, C              closed='neither',
    : {7 C# ?+ P7 j: p1 K              dtype='interval[int64]')+ x$ z* \( ~0 Q, @) g
    1, X) e+ T; F! T. e5 h
    2
    . ?* v5 F, x, M" r4 R3
    $ `/ @& E2 h5 y# x. ^4) v  h$ [( W% v* k
    5  _4 u* [6 T$ r3 ?* R9 T2 |) A
    interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:' C2 ~! M: y& P
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数, e6 J; m0 p: g
    Out[57]: $ h0 _1 I  u+ l: Z" @7 N; I
    IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
    7 j  A/ R; V! R& ^: r" W              closed='right',
    7 o5 r' a' m! I, `; E, \              dtype='interval[float64]')
    2 Y3 X, n4 P% ^1 e1 U
    1 `% C, l& z0 U: Y1 K6 Apd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
    + i; ]1 K* \/ p# hOut[58]: + m. Q  I' [/ \0 X) ]  q# j
    IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],6 j* |9 y* h' d" _, ~
                  closed='right',6 h8 q" E6 |% N* N, Q: P) P; |
                  dtype='interval[float64]')
    5 S9 k  ~( W  |5 X, C10 @( z# c; s; ]' ~! t+ W& n3 U
    26 Z! o2 Q2 c- s( |! ?, S
    38 p( l, E& V3 \7 `
    49 G$ v; _( a- Q( A2 w1 V
    5
    7 Z0 @2 v/ h8 w65 j5 ]4 C, h$ C3 _7 }. R
    75 G$ ]) q2 v& P
    80 D* d; G+ }1 [4 X
    98 s' c* C& H; _  }6 i
    10
    & v8 K0 e; X/ V/ l8 F119 K' y  Q6 C9 P3 |& M+ |
    【练一练】
    6 t1 Z, z( i2 t' S. r7 |5 l8 h  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。, m" D% _" W3 I

    & P- g/ ^" v, R7 a6 o5 d  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。7 o# S2 L  z! [$ d8 B) J

    ( z" D1 T- L2 e! F! W  j( Emy_interval
    % L, h- |  c0 {# v  o2 dOut[59]: Interval(0, 1, closed='right')# n0 C0 x5 s7 H& d  h/ i% ]' Q

    & r, w( b# ]2 h: W  l( c" z" K+ |my_interval_2) N' h# b* `& Q, H6 N
    Out[60]: Interval(0.5, 1.5, closed='left')9 d0 M) t, p. ~

    5 z: [7 \, C# b+ gpd.IntervalIndex([my_interval, my_interval_2], closed='left')
    4 ^) k- e, _: WOut[61]: 4 k6 H. ^& n) c8 |/ }2 _& q7 S
    IntervalIndex([[0.0, 1.0), [0.5, 1.5)],, D1 }% [/ w& f1 N! z; Y
                  closed='left',1 X( K. z' n0 B! _5 {
                  dtype='interval[float64]')" {9 I# U9 W1 F9 |# M
    1, F/ v; `5 ]$ A. t: a
    2
    ( \1 z+ I3 f" f4 W8 x1 I3; F- c. O  G% w8 k% B. t: W
    4
    / V# f" a: |" }! v5/ M/ S# ^4 }7 n
    67 b9 ?2 u+ c8 u9 Z& W$ A
    7
    . ^" s6 H" q  E. ~1 B% S$ h$ U8
    # l+ F6 Q+ g* E# h: {* U& ?' U* }9  d% S$ M% r0 C& I8 a7 S& T. v# ~
    107 O) N: C$ J4 d/ ~; e  e
    11% v2 i" u& g: Z% t3 m
    9.3.3 区间的属性与方法
    0 u% m6 Y) ?7 [& f4 G  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:; y% {) d+ I* t$ p1 \. K
    # z- {* ^- B( L0 |- B( m# @
    s=df.Weight, O; U( j1 b  l4 y$ x8 ^2 k
    id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
    ! L4 h+ y% b* g1 u0 T& nid_interval[:3]
    7 X% ~0 T- X, A' e; P
    3 R2 F; b% {. u4 YIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],# p9 ]6 M$ J; F0 D
                     closed='right',9 M5 w: h' X* M3 G% d5 E
                     name='Weight',5 j' L; t9 ^! }5 M9 i& B
                     dtype='interval[float64]')
    3 A# k$ y4 s+ n3 ]8 Z) C* K6 \1
    & L' y5 A9 N6 H; q  ], }2# h. E& U+ F: x4 f+ z( ]' u
    3
    % R5 D$ N1 ?/ `2 S. h# E4
    & J5 X/ C+ b9 [2 @# u5$ y! k4 |6 n- _1 ]) x4 Y
    6" Z$ @2 x( a: L- T5 R) E/ w% \
    7; \4 i$ I6 b0 F& S' X
    8& D2 p  _2 D( g8 p4 W% q
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
    " d: Q" m  `. c% B. V  Xid_demo = id_interval[:5] # 选出前5个展示3 s3 F4 W4 B& J! R

    ) v" F# \. Q& G) |) k8 C, u& Eid_demo
    , `5 S# |9 K, ?$ ]9 ?Out[64]:
    ! c  x4 J( m$ E9 y) x- n* tIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    & E0 j  k0 i+ Z: N) s3 L              closed='right',2 L9 i- E! ]; w0 }9 }, d; X: B
                  name='Weight',
    - R3 o" q% O. _' G              dtype='interval[float64]')( s0 n7 z8 C! Z6 R

    , Y# G# e! H+ R- R" Eid_demo.left # 获取这五个区间的左端点0 I' X+ `4 t  k( [! `( M
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')8 }  J0 s" n$ A% Y% R1 x

    * t- X- H% o5 W2 g1 Y' ]" {& Uid_demo.right # 获取这五个区间的右端点
    + z8 d9 @0 d( ?6 ?* xOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')8 ~0 h8 ~9 a( p. P5 i3 z5 d

    5 t, ^) u8 Q3 P. G/ i3 Pid_demo.mid: x9 {0 M! x1 x" s) |
    Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64'). r3 {) [$ `/ v* r, B( Z
    6 S4 H( T6 j1 j/ C" Z% l
    id_demo.length
    ) n1 Q& z- [2 N) m1 Z# S' uOut[68]: 4 D1 }* E/ s, A1 z
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,0 X1 J7 {' o- c" Z4 @" f
                  18.387999999999998, 18.333],) s* u$ G2 G% X5 j" K0 |5 r1 X
                 dtype='float64')
    1 \( X; O* z. I! ]
    ) v- f" J2 r5 W' m  R1& Q! @3 T- m2 |9 }+ c
    2
    * |- w" W3 C3 O, a. b' E" {3: Y# _: c3 N  @+ @
    44 Y, p# v) E" p1 d# m7 v9 F* N5 ]
    52 K+ S3 K+ k3 @  H
    6
      s; Q* t3 D6 f# k7
    ) @7 v, c: R; I8 k8
    2 `1 ^, l3 v, |/ E$ R7 p" T9, \% t  G1 w3 _0 ?
    10
    3 A2 F+ @: Y4 i% s" q4 D+ O11" l; R* h$ U! W3 R1 y9 Z
    12$ s3 v0 z# D& D3 W" ?
    135 [! i. @- Y7 R1 F9 e( Q0 [. _
    14) B4 m3 ^" a* A
    15
    % b' ?0 h, h9 V16
    : m  h/ w7 F" h9 F; `17+ A" l  S( G" N( d6 y3 m: z
    18
    # ?3 C! F" C1 X- S- S19+ S4 I' }& f4 Q. P* {) |
    20
    + D* F0 j/ K9 }: o  z; C4 q21! W8 m1 Y8 d& r, a/ ~
    22
    4 w5 l% g( N% p% l23
    ! X. V) x3 n) k& K& x+ oIntervalIndex还有两个常用方法:
    5 I) A0 w' N- |  v: d" Kcontains:逐个判断每个区间是否包含某元素
    , e% ]2 \% p0 ^  q* k5 toverlaps:是否和一个pd.Interval对象有交集。8 ?; o8 V* Z$ B( Z1 [
    id_demo.contains(50); a: K$ o  ^3 G' n% z8 t  `: {
    Out[69]: array([ True, False, False,  True, False])6 Z  d/ f1 {: t/ X
    ' w. h1 g" R! h
    id_demo.overlaps(pd.Interval(40,60))
    3 e5 J* y8 k* g9 }0 a1 R5 F- M' d% H% NOut[70]: array([ True,  True, False,  True, False])+ E# n4 g- y) N3 J, \  i9 h& d
    1
    0 Q# t2 k4 @" V2 a! t2
    7 D' J+ E0 \  ^; v3
    3 d( L. d6 L1 F# z5 N! m4) M% y( B8 T( |& g1 l9 ]" ]7 q: R0 l
    5" r6 i- m1 y* ^+ h* v" D, Z) E2 }! N
    9.4 练习, [& ?9 L0 y. Z9 Y
    Ex1: 统计未出现的类别2 D% H$ f5 H* A: R+ U: Q8 J. _+ S
      在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:! L2 H  I7 V/ t
      P6 z) J) E/ J( L5 ?! m0 b9 ]
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
    & p- d  O) Q( |4 hpd.crosstab(df.A, df.B), `9 T( C$ k* E2 i; Y" Z

    4 E6 g3 w. P+ F0 o7 V7 iOut[72]:
    ( M2 O/ \; g8 F& @) |B  cat  dog
    * x, i5 V* n9 t# wA          4 `' z2 P( S0 L( I0 E- \
    a    2    0/ u! t! q$ F# @5 q& Z" Y; _
    b    1    0
    0 X! p& M$ r! I) D, ]* lc    0    1
    # y1 g4 w( O$ w% u1
    ; o+ X% ~% j* e9 M% u2 `23 F8 B7 v7 R+ s/ p# H' l4 x9 }
    3; x0 p3 z+ m; \; I, d& o/ g
    43 u! M* E0 E: a- `0 j1 Z1 \  \; F
    5
    ; T3 G8 d' k7 r2 U4 Z9 t: C6# n# z6 q7 i$ b6 W. K- Z
    7: d3 ?& A. R$ I% n
    8( o8 ~0 k6 M+ F& K6 u5 V: u
    9" e( D5 R, q: E5 C, j# X
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:' c0 m4 q6 A3 S; ?# N

    ; p: n+ P) a& O6 N" u: c) adf.B = df.B.astype('category').cat.add_categories('sheep')' i$ t, D: W$ h2 R/ l- x
    pd.crosstab(df.A, df.B, dropna=False): e' s& Q3 E& `

    $ o" m2 D) z1 F# F2 `) EOut[74]:
    ( ]7 e8 O% w$ {. v4 e% nB  cat  dog  sheep
    % G  Y- }3 c% e7 q6 ^A                 
    % W4 O: U1 e; I7 Fa    2    0      0/ {/ l7 P9 I( P; E" q, |$ Q; L0 _
    b    1    0      0
    % _) A; H7 l# ac    0    1      0
    8 `& }$ V0 z  [5 E: ~1: q7 p# P" x( i
    2, i+ M% W2 ]# |' k' I: }1 R) M: r
    3+ c/ b+ }- Y7 l' V( ^) j) W
    4
    2 x7 n' V4 \5 H8 v; b+ p) N7 V# U5- N* ]- E. o+ S( \1 d# Z& ^
    6
    / z% j3 Z9 r3 k* B7
    ' p6 {5 K1 v0 p1 [' K8
    $ D) X( }; y2 u4 g- Q2 p2 E% P9
    0 R' z; `" G& Z- @请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
    . c; f  ~. x6 V6 D  q) t, C
    & G* k4 m! q2 ?2 i. pEx2: 钻石数据集
    - z9 [) M  i0 }% T& r  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
    ! ]6 @( i* W" E" Y4 `; N
    6 ^8 w4 `. H1 `' Q( Ydf = pd.read_csv('../data/diamonds.csv')
    ; Q( P( L( O. Gdf.head(3)0 a' K) |5 _3 J; k

    - b$ q5 S+ ?) a, L2 tOut[76]: % d  t& Z3 E* ?: z; C
       carat      cut    clarity  price9 [) O- F7 F/ j. m3 N- ]) X0 ~
    0   0.23     Ideal     SI2     3263 J7 ]- Z& x# {
    1   0.21    Premium    SI1     326
    % w6 R- ~0 v6 ^! k8 y2 f' ~2   0.23     Good      VS1     327: o% ]' Z# Y" R( H
    1
    6 W$ ^: \8 c) C- e* C/ c0 @2
    . d: |8 O7 A' N" y3
    0 p* `! _  V! [# ~" V1 J4
    4 p0 w, K$ m" d& A7 Z5
    , O7 i6 F1 R; ^, t9 E) O0 x  P68 z" q$ D$ _/ I+ k1 p
    7# f4 e/ v! k( m! C: `# K
    8' q( x0 h; Q- q* a
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。% |" l* M4 I7 _, t5 ]
    钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。/ M" u4 f5 H. c) a' z' |
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    7 ]7 f, @2 h% P$ y" H: m1 s% M' q对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。( T' F: ^" Z( D) f2 e) G
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    & M0 x' U8 C3 a% }, E) I% O& _对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    9 B) ]; W  b3 x3 D先看看数据结构:0 x5 V: C# C! t' W5 r

    9 g# a% P$ S- c) l1 l2 Z. ?8 k, m& ldf.info()
    ( }) [9 L2 B- r: u  ?+ JData columns (total 4 columns):
    1 s" v$ z' [$ r, r5 { #   Column   Non-Null Count  Dtype  : d! U- D5 c4 k2 A+ W- {! B
    ---  ------   --------------  -----  - d, [7 Y4 S' e/ @- L
    0   carat    53940 non-null  float64' `) ^. E$ W: v& }& n& H& c
    1   cut      53940 non-null  object & g( ?1 s* R2 N, \& o! k6 v9 T- _  L0 `
    2   clarity  53940 non-null  object 0 i( q& R2 V! t. v/ m( r! {
    3   price    53940 non-null  int64  6 _" P6 o0 P, S3 n, j
    dtypes: float64(1), int64(1), object(2)" \$ V0 Q; H  k5 q
    18 D0 n6 y4 H8 ~8 u5 x6 s
    27 Y6 _9 }6 V, r! v; U1 g
    3
    # A5 Q+ L8 J; f; |* p4
    $ A+ u- o* R8 Y( f5
    : q: v8 W3 O# Q6
    ! u3 f7 D: _7 f! d. x7 t0 j( E: J71 w) T0 h* M+ ~! U! d* z9 s
    83 I1 o2 o, T1 S5 l* y8 q
    9
    " n, c' b3 J  N% A0 |+ A比较两种操作的性能" L; L0 q( M2 w, m
    %time df.cut.unique()! B& _+ P; X% A
      F- n- ]$ D  N7 V
    Wall time: 5.98 ms
    ( ~  r$ i' q6 f7 Z9 r* x. V4 z; R' rarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
    7 o5 W- V4 ]: L6 J$ r1
    & l2 _4 g% g/ l8 U2* i: u, l3 M$ O& m2 K3 z
    37 V# `* |& r; y* y, ?. B& ?
    4
    # A! _% {3 V4 S* k' R$ h1 b% y$ l%time df.cut.astype('category').unique()9 u. }' K- M& `6 F
    ; E) T2 e/ |: ~! x! Y: ~- U( Z4 O
    Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms
    : q2 D; ?& g: p: m['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    , @: B. c2 G; T- f2 s% }Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']3 s( \) }+ u% z# V
    1
    8 F, W' u2 W) }7 e0 c! ]4 o2
    + E6 F: E* e; ]) s+ Z, A3
    . u4 R. G+ O/ h7 N4
    ' t) S, a1 L; c& e' l5- o% c) g; c; m' h( N. ~8 c
    df.cut=df.cut.astype('category')
    ( e) _" L+ s; H9 t) ]%time df.cut.unique() # 类别属性统计,2ms
    6 R/ C6 h5 f. d- X+ R' X5 |) [$ T% S( O
    / U1 n! i0 ]+ d' j! |Wall time: 2 ms
    : k- P5 G8 E5 h1 m& {# X['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    , G" B2 O2 |/ s* D3 RCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    7 m, g6 N2 C1 o+ k( B1
    8 I3 U: o4 ~4 D( B$ d2 S2
    " H3 L1 S8 t( i: w9 g- ^" J, K3& G; R/ k0 n% I2 h; G+ [1 ^+ C
    4
    * |! J  w" K3 X! ]( h5
    8 h; _6 B( M/ n& v% v7 Z! b6$ a2 i6 {+ P; ]
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。/ L) @' t4 K' r4 {; ~% m) f- v
    ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']$ L2 e8 N$ z8 m/ n4 ]7 b. O/ A* m/ ?, R- Y
    ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']. G' q! E$ p8 _! ^. I# [2 Z
    df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    % h4 ]! ?- F! x2 f, M# a$ v& d8 Z! wdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
    9 T$ A9 I1 J; G+ b/ \
    " H9 V) z( {8 {2 H( h4 Mdf.sort_values(['cut','clarity'],ascending=[False,True]).head(3)8 W) b2 ^* u1 x/ B3 M4 s

    - [2 e" H- T9 {        carat         cut        clarity        price
    , Y( v( r+ U) N% C! @, F/ A' X  J315        0.96        Ideal          I1        2801& `2 e; e/ h7 E9 Z% |( W
    535        0.96        Ideal          I1        2826  A7 E3 J. v7 w' T* c0 I2 u, p( ]& Q
    551        0.97        Ideal          I1        2830
    7 ^" I2 }9 t$ Z1
    * ]) ?% h, f8 W1 x1 E+ c2
    ; X/ C5 }; ^& t' f3/ C1 y! g* }0 ?9 X
    4
    ' }% v3 G. S9 t, ]5
    ; l* `3 z- T* Y/ D  p6. T4 v4 d8 S* d' Y1 N
    79 w& Z5 Q' ]$ ^
    8: D$ L6 Z) L: h! z  j
    9
    - |: Q. {% f: w* w3 B10
    9 ^' A$ H. H( @11/ g! i3 Y% m/ K1 ?2 T. n' a" |
    分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。  P0 n& v" w$ n' h# L5 [% p
    # 第一种是将类别重命名为整数" u( }1 E) ^1 b  W/ z$ }. o' c
    dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)])); r, l5 m& Z3 t3 [/ B
    dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
    * g1 ^( d- |8 y  j) E( V, G3 L) ?+ ^* l
    df.cut=df.cut.cat.rename_categories(dict1)
    2 t8 j$ s$ F- R! ]) S, kdf.clarity=df.clarity.cat.rename_categories(dict2)8 I9 r# e# _1 p2 i; G% H
    df.head(3)2 ^) L" |9 M3 n$ h3 P; t" {
    9 t' {5 M& O9 `# N" ]% a6 m
            carat        cut        clarity        price2 s1 h. |  G  S- Z$ I
    0        0.23        0          6                326
    + {6 @1 V; O: h2 W- {1        0.21        1          5                326
    8 b2 o& M' u) S4 Z# l2        0.23        3          3                327/ y8 @0 b9 K) p; l& }$ m' n0 R% K
    1/ E* u7 C- Q8 i. h/ o
    2
    # D( J; y4 n+ h4 U, z3/ S1 B, W, K/ ^/ _% h  _0 G
    4; P: R+ f+ P: X% ]# v4 ^# g* ?
    57 n0 _: V5 w- \* |
    6  x  F9 F) M. W6 e# m# t
    7
    8 Z$ N- w$ L5 v8 i' w8# t( V( C8 X; t3 e  g( X% P
    9
    5 {0 T2 j# R$ t1 `4 x. n10
    $ l* A/ W1 w1 o0 c% [: r8 X- ?11' w9 _8 @# w  n8 l2 |/ m3 y/ S
    12
    / [, Y1 c+ f; K  G6 A" K3 i8 F* J# 第二种应该是报错object属性,然后直接进行替换
    9 \7 D3 p3 O  A/ x8 Edf = pd.read_csv('data/diamonds.csv')6 O; W  m' R5 s+ |' `
    for i,j in enumerate(ls_cut[::-1]):
    2 L  X  B1 m# X6 u    df.loc[df.cut==j,'cut']=i
    4 g; ?$ i5 q* V+ |2 E2 _* q' {; l! [) @' L4 H* b8 P
    for k,l in enumerate(ls_clarity[::-1]):
    " D: y9 z+ \( I. c    df.loc[df.clarity==l,'clarity']=k
    ' H/ G- S* u7 F3 ]+ |df.head(3)! {0 O/ R8 d3 f4 d+ x* L
    # a9 |% F$ c. H7 Y
            carat        cut        clarity        price* b2 Z' C/ G! ?6 w
    0        0.23        0          6                326
    1 Q2 [3 u7 W- h3 w1 B5 Z6 e1        0.21        1          5                3262 x$ w, m3 Q& w9 y- c6 J
    2        0.23        3          3                327
    % j% [5 `8 M; Y) q+ W- \) U4 ~1
    5 a, C# M  Q/ }2
    ( Q; b, a9 R0 M2 S  ^1 z  u0 w, l3
    % x# c2 _& K1 e3 K3 \: j  Y4 F7 }4
    + H8 ]% w; a4 [9 L: i55 J. F. P* Q$ C% M! [) m3 g
    6
    ' y1 y) h8 a/ g' y* t; n. h' S7
    : H( W/ [1 d! N: Y7 b8# z) p' a* G; s+ V& S& l
    9# I6 ?; Q$ r  P0 r) M( J+ @
    103 C3 i4 q5 p# u8 r) u- E3 U
    11
    + {' U! d! t' t0 S& w# b, G( ]. J12& H/ p  l* m. w3 b- b; `
    131 y( F. R6 u7 t- x
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。6 e+ S, d/ I! J6 P
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
    & D) n: G& h: L; F; Mavg=df.price/df.carat! e7 b7 ~* |) v' M2 S( a, M
    ! t9 F/ U8 w% A" v4 i, `2 _
    df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],! k. O  c. T5 o$ f
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]- R1 b# x/ F: `) a" y, G# U6 }

    . ]8 |1 F; L  v1 X' x- _$ ^df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],9 i! _  {7 P0 s0 i. T* `. Y
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]$ [* G* ~' v/ Q- _
    df.head()
    6 D" d$ Z) O4 v* \% w, q. A# \" D1 A) q; w' y
            carat        cut         clarity        price        price_quantile        price_list: q  S: q+ y# I0 a7 g( E. @8 B
    0        0.23        0                6                326                        Very Low                Low+ A7 I6 x6 ]; e. S8 @
    1        0.21        1                5                326                        Very Low                Low' o9 M8 `+ E' G
    2        0.23        3                3                327                        Very Low                Low; i. d. f, A) C( I
    3        0.29        1                4                334                        Very Low                Low' K' c' H2 V& L5 R& f  L
    4        0.31        3                6                335                        Very Low                Low                                       
    ( u( w* m2 r  H; H% q% Z
      w/ h. Z% [( Y4 v$ ?1; {1 ^5 G' K+ x7 ~
    29 `  L! O1 w, J1 Y, M; [
    3( W1 y" `. g1 E9 Y' Y
    4! m+ Y6 N: R( Z0 Q& E6 G3 X2 r, J
    5
    % g) g1 ]* s+ ~( @# r2 v6
    , `! `( t* }8 v2 n. H0 ^/ V7 |7
    2 i$ r3 t2 O" L: V" j  Y3 m6 s" a80 T+ o/ B& J: p4 M4 |% f9 q5 F, U- h: E
    9; ]0 P) \+ n" ]- a
    10" P4 t7 S8 c- i* ^/ l" p& R
    11. f* b, B1 m8 ~, b" B
    12
    9 x* M. _! m. O+ o' Y( ]! h- Z' H13
    0 W+ N- E; L, n" i1 C" k$ E6 K14
    * m" m8 |! t0 i; w8 q7 C# P( U0 P15
    : @% y$ W' K6 Z. B0 f3 ^* j+ f9 F16% W7 t: F! N6 p
    分割点分别是:
    9 A% r- W: H9 \& c
    , e7 \5 i) z: ~# Q, C; Jarray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84]); _3 G' H* R/ ]! o0 ]3 R5 p: U
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])( f" q2 j& x$ o, p
    1& J4 b; p- k+ E: e
    2
    . E( E5 w- G& \- m5 U1 R7 S第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。! a& f" z0 H- w: V$ Z; I0 B6 t
    df['price_list'].cat.categories # 原先设定的类别数, W% K/ ?; ^( f# J
    Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object'); f4 V0 H3 @" f0 Z

    7 t5 Y9 q! @4 h# X: I1 ^df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别  N9 d5 x  w9 A3 Z. u
    Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    . l5 X' i& A) r, |7 F1
    # R' q* Q, q1 P) E21 A" C# A7 `4 B6 p; T9 ?
    3, b8 A1 k! M# T  Y# S' f1 w
    4
    5 i, V  m3 t: u- W52 i1 Q2 r$ f& O, a" t) ~' ^
    avg.sort_values() # 可见首尾区间确实是没有的
    - U+ S5 X9 j  p. j. R31962     1051.162791  O- m- i" T0 N1 j
    15        1078.125000
      _5 G9 X) i6 j& d4 J4         1080.645161
    / V  T' U( d* g0 |. B# M8 T28285     1109.090909
    / |! M8 E, L- Y- r- M13        1109.6774194 W. T) W/ V' O% u: V) H
                 ...     2 U) o$ v- G: K" q
    26998    16764.705882" p; _2 ^1 C1 n  `! K- J6 W
    27457    16928.971963
    $ B( P, N% ]6 N: K, I7 B2 f27226    17077.669903
    & q9 W1 q0 P4 s, ~* k! A27530    17083.177570! W. P5 w! d2 ?. A
    27635    17828.846154) I+ _. C: T1 `% ^( Y& g9 ?  T; U' J( d
    15 h8 H& j; _, ]7 D" g" q, p
    2
    0 \; K8 |3 `- a2 z9 c3' R5 `1 Y. I8 [' ^4 B$ G8 i- P
    4
    ' O0 \- V/ w# Q6 l& w, E5, {/ s' a" T. \% B' {
    6" m: J; H$ i( A/ {9 C7 G
    7: u/ |3 H" t, N. q
    8
    % I. Y& G: K" c# T% U' H5 ?  a9
    * ^( k( A% o/ f8 A  S10- |" h; d! C# F2 O
    11
    0 z2 f5 l9 O9 N1 y5 r. ]& e12& w5 G# q: p$ I* k! p
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。% {" S) n) H9 T: `: v" B+ h1 o
    # 分割时区间不能有命名,否则字符串传入错误。
    ) z1 }4 A4 B1 T$ s' ]id_interval=pd.IntervalIndex(
    + g0 {, J7 J4 f0 E    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
    8 Z- d/ ^5 p& j6 X' J                            )
    2 P- g! w: Y0 T# [( i' ?& ^! uid_interval.left
    " X& r4 ?2 B9 c8 L- O8 ^2 iid_interval.right% k" h8 p% d* M
    id_interval.length                            + e- C" [! n0 p- r9 a3 J5 L
    1
    6 z) H) s; X  Y# ~2  E. F8 U# t2 Y5 u4 `
    3! M8 Q2 S5 l! {
    4, M; I" r3 Q- H+ c5 b
    5
    5 o7 H2 @$ `/ q% d0 P6
    2 C5 Z4 N0 u% k+ o5 b7 i7
    7 `$ k( _$ I" j第十章 时序数据; ^& Q  K+ V9 `( z
    import numpy as np6 V* {  V6 w: m% G# ^. c
    import pandas as pd* k% ~8 h' Z5 p, w: ^& S
    1
    2 v8 m5 Q* M8 C/ S' \2
    0 G1 e2 m2 s: Z# O1 ~; y( G# f/ K4 t. h! i8 B, @  T

    1 d6 H- D4 I' y10.1 时序中的基本对象+ n7 |/ n* L, z: V3 |1 O  O1 F
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
    7 D( }3 P3 R$ R/ w) V9 f8 Y. z$ a* t+ P1 Y! O
    会出现时间戳(Date times)的概念,即’2020-9-7 08:00:00’和’2020-9-7 10:00:00’这两个时间点分别代表了上课和下课的时刻,在pandas中称为Timestamp。同时,一系列的时间戳可以组成DatetimeIndex,而将它放到Series中后,Series的类型就变为了datetime64[ns],如果有涉及时区则为datetime64[ns, tz],其中tz是timezone的简写。
    4 w( d3 O! o) o
    + f, c+ i6 B/ m0 A$ Z会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    : b% |( J3 |, b: [
    . Z1 ]! C/ _6 p8 B, q& r会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。- Z4 X( Y, @% b7 }3 Q

    3 N2 T! N1 u- p" O+ Q" J会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    ' S9 _" U; u6 p7 ?9 V. [, ^7 t" H
    - x* S4 @/ k2 _  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:& a4 a" s( `4 m. Q

    3 ]3 ^" f9 v: L, _  Q! ]. s1 X概念        单元素类型        数组类型        pandas数据类型) M1 B0 t1 W9 p* A
    Date times        Timestamp        DatetimeIndex        datetime64[ns]+ c0 N" Y$ t. [7 e* O
    Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]% p' a7 _& M  ?+ u. q
    Time spans        Period        PeriodIndex        period[freq]
    4 U" U' M4 N( a0 }# O) H- jDate offsets        DateOffset        None        None
    ; W6 Q' n1 t7 t/ U  p) o9 w+ p7 @  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    " `/ n. K8 i; s) O+ X* z. I5 b  l- N) L' `# X
    10.2 时间戳
    $ F/ m" A# j, @0 ?! Z: N9 n10.2.1 Timestamp的构造与属性
    7 {! D+ R" Q) W& L- X2 z单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
    ! b$ O' r0 j! W. v. s0 `& T
    7 Z/ e% a2 `2 Y9 c& Bts = pd.Timestamp('2020/1/1')
    " G0 p& `& Z' y/ V$ ]6 J. i- y. T, z% p. E
    ts# c6 q+ E$ s: i" |, W9 `
    Out[4]: Timestamp('2020-01-01 00:00:00')
    ( q, b# B' s6 T( x8 ]$ M/ o) ~" O# `  D. X, }$ l* ]
    ts = pd.Timestamp('2020-1-1 08:10:30')& B& Z! B: t" Y+ _' l7 R

    : T8 L3 @0 }5 |- h  }  dts( E% O! Y( {% t6 f0 X$ p9 s
    Out[6]: Timestamp('2020-01-01 08:10:30')2 A( ]- m! s# ^5 M  P
    1+ }6 H" H! a6 C6 A
    29 V3 o; E$ Q1 v
    3. J  n, ^, P/ U" J( o$ S0 \6 r
    4/ I; t4 C, Y8 t. J: f8 f9 s; o
    5, R. R+ X! d9 [2 P
    6+ B: A% j: K7 ?$ }3 z
    71 n  _' p8 K4 ~: {$ D3 v
    88 T6 e9 C9 L+ h# @8 g
    94 S0 M& l1 H) D$ S7 K5 v0 D
    通过year, month, day, hour, min, second可以获取具体的数值:. _# \  d! `1 T) P2 }

    5 _9 q0 x; o/ l1 z: j" vts.year
    7 i& C* m. f# S( d% G5 }; @8 POut[7]: 2020
    ( i2 N4 v2 l/ J9 i; t) W/ g- u6 Q
    0 i$ X8 y* d& C: B& k0 zts.month
    & }' C+ P9 [3 ~" m! X! C9 uOut[8]: 1
    ! y% C/ z  H' {
    ! J( f- h3 k/ q4 y' A  w# \ts.day; d/ Z1 y5 K" H$ \2 G& L
    Out[9]: 16 d, t, H: f5 {7 @% n8 W  |, j  T/ D

    ; C9 Q6 n2 ~5 v  t1 c# kts.hour/ Y" j4 T0 J9 O# T+ a* ^4 q
    Out[10]: 8
    7 r5 p) W2 F0 i" |% r0 y6 P, f' ]* w. s7 ]5 s$ ?7 [
    ts.minute
    8 V  ^) S* k  m( AOut[11]: 10; Q/ N  L2 L* I3 l, m

    $ X6 O& [. X4 _ts.second7 I3 L  ?7 K" r2 K4 q
    Out[12]: 30
    0 I/ ^  W5 p; A9 t# F) p
    4 W# n. N  o# r7 A2 h2 S; z/ W1
    " k) G* y* Z+ h2" i0 t2 `5 d/ t7 E! u
    3( F) {9 h3 a$ M' d- Q+ a6 L  O
    4
    # R' P+ {! T1 d- \+ m5$ O6 `0 L% |* e' F+ ~, d: T+ K
    6
      o) ~) r1 V! z9 ?7. @" }+ r5 z/ n: d
    8
    : ~. w3 M  P; P$ ^9# G+ q! U5 O' e( Q: Y
    10
    , }+ {7 Z- F9 n% ^8 d11: {1 @; U& }0 `, X
    12! d, g4 l1 i) h) w; Y
    13
    - a4 U$ B$ b8 n14' L4 C6 T1 z7 f
    15
    - J' {, ]- G& _' ~: c& ?5 f5 \16  n. v# \  G. [* }
    17
    ' \$ c7 Y' t# B. S3 ~# 获取当前时间$ j% X9 }$ N* g7 P
    now=pd.Timestamp.now()" b: T$ N, I2 W* x! z
    1$ ?2 C2 _! e' E" |: h
    2
    , ^# `2 J. q; J, D% j在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:" l3 g: S7 E/ c1 H8 Q7 J/ y: t
    T i m e   R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years); q# X' @0 a9 Q( ]# O) B
    TimeRange= ! H1 `9 C7 ?! K& Z4 M
    10 ) N- t1 g, a: n# C) e
    95 y* A( U5 r$ c/ s7 B( }6 G
    ×60×60×24×365
    7 P* y! h$ S; T2 M0 }9 v+ _6 e- d! [2
    * f8 Z7 j* w' M64: d; Z' Z1 k4 C& J- D

    ; c+ f0 e2 p1 F3 g
    1 B' ?! ^1 l+ p) b ≈585(Years)
    ' l+ ]# l2 [6 u' x2 O6 {; K/ G( R5 w) C1 t4 D
    通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    ! T$ ?# c9 v2 @  |1 Z2 Z6 \
    % J1 u  f3 r. \pd.Timestamp.max- d3 g  B, x$ e  |7 M( e
    Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
    ( {; P2 I& A% a' t$ K" d! |( C/ ^; Y( d, g' i* P
    pd.Timestamp.min+ [: a% D/ N' b! Z
    Out[14]: Timestamp('1677-09-21 00:12:43.145225'): _% N  t& o$ G7 z. v" W3 n6 }

    ! d, q# Q( R. O+ `/ d: w! B" spd.Timestamp.max.year - pd.Timestamp.min.year# p1 i6 l4 D+ r4 f; B
    Out[15]: 585. l. r7 F8 F# Q9 Y/ T8 d
    1
    9 ?! I) }- _. C: u2
    2 }5 |; V; a7 m4 q3
    - l- k% s7 q) \4
    : w2 _5 ]; I; M% y, h0 G55 r+ N- p* v; e: {8 m5 `( U+ D; a
    6. y% X# R! @  S2 }+ Q( o0 ?1 i
    7
    ' o+ A: `3 ?5 a2 E82 `6 f: P4 A$ F. @5 d" g
    10.2.2 Datetime序列的生成* |, a6 H. l5 J; Q
    pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
    " f+ D3 @2 r1 V) m# P                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
    # s. k5 G' @4 O% L( `# t/ L7 d; _1; p& E$ y, {6 |9 j# l. ~, D4 X4 M
    2
    8 y6 ~; g( p; |8 p6 \( u2 fpandas.to_datetime将arg转换为日期时间。9 V, E3 Y) t  ?

    + S; a$ _) V) V9 M4 @arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。* G3 S8 u0 g/ Z; _8 k
    errors:+ o1 q# I6 n7 N6 c0 u" y" S# U. s3 t
    - ‘raise’:默认值,无效解析将引发异常
    * ~& g* ]! i4 ]' t+ M$ t0 z" @4 z- ‘raise’:无效解析将返回输入& X/ \, P6 x2 R! }  {5 W7 B
    - ‘coerce’:无效解析将被设置为NaT
    ! K! _+ l( F8 h9 L, cdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。1 E( R3 F" K; }+ k
    yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)* N  |( b8 ^0 ?9 }: S& E2 Y
    utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
    3 |/ n6 M! ?6 W, F" eformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。! u: _/ T8 A* u+ R
    unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。% Y6 r5 c+ d% o# y+ p8 ]* w
    to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
    2 B' W, {- J& f9 ~7 Upd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])% ^4 Z0 ~+ t+ W# M) Q5 U
    , T' Q& u1 z" S1 M" [$ T
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
    9 p6 X5 P% O5 ]& M& E1
    9 V. J% Q% J0 \& U" K- {23 a8 Q2 Z7 e0 O. e6 [
    3
    / h4 Y% C# C. \5 Q4 w- D$ u在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    " c1 W% e  {( @0 n8 x' I) N  Y9 D+ W
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
    5 _% S( p7 u+ y# Q/ a" wtemp
    4 H6 P7 W: l" ?8 v! g4 V8 s! m7 H. K  W
    ! b( C' r7 U% v: C/ SDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)! C0 N& v* M$ v( ?1 q+ y$ }
    1
    ! S: }; P4 o' m( j: e2/ s% X3 s, K4 F$ `5 a- N, w1 r; C/ l9 I
    3
    * M8 U( a* _  {: B4
    . }' Q; f! S3 P0 `- F& e5 \, a  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:! X! ^# s! E& c; A

    8 U" [! A* ]% mpd.Series(temp).head()& J: G. _  P" A/ E
    , a' l& z4 R0 z; P3 t% ?/ T$ o
    0   2020-01-016 m  {) P# @# \
    1   2020-01-03
    ! B+ c: j! A) p8 A8 J* \dtype: datetime64[ns]
    6 a4 S7 Z$ A# {2 `* K0 K1
    ! j' F* b2 {# @; \2
    & l6 A8 g6 _9 B, o& `6 R) E37 @4 d; p2 I3 `1 B$ C
    4+ w# ?+ Y8 K/ d8 h
    54 i- y2 T" |) t. J) V
    下面的序列本身就是Series,所以不需要再转化。
    0 r( r+ p5 g4 _9 G% A' S5 }, ?1 F* H8 F1 S
    df = pd.read_csv('../data/learn_pandas.csv')
    ) P: A) x5 B  @s = pd.to_datetime(df.Test_Date)/ K& f. j: k9 P
    s.head()3 A+ n% ]( f" J0 @, m
    : y& J! z+ j/ H" _3 K4 q
    0   2019-10-05. L, l. i& s3 ]  z5 Q$ L3 m
    1   2019-09-04
    # K$ K. d! Y; J' V8 v# T  e2   2019-09-12
    & E: y+ N  V  q8 ?  h2 n3   2020-01-03
    & x* h8 ^; E+ [$ t( a. W- E, I) |% c4   2019-11-062 T! f$ ]( X6 C) R% h( D" S9 I7 `
    Name: Test_Date, dtype: datetime64[ns]  P3 f* i: R& N8 l3 ~4 {6 _
    1
    9 {% _% T6 d) v: o8 e& J8 z8 y27 L# Q% [+ d  p% C  |
    3
    . w- e5 v/ O/ s4
    3 l+ m* ^3 b: o4 T2 \! C+ I5
    5 y7 ]) d* Y( n" d6
    6 ~9 d* U3 ?8 ~3 u! F7" v! a* H' a/ v& G) b$ o
    8; E7 M- D% Z8 c( E- d& l/ I3 H# \- ~
    9" R; m% f! O7 E% p% W
    10
    2 C  }- w6 k$ b8 e$ p把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
    - F; p( @4 L; p3 d6 tdf_date_cols = pd.DataFrame({'year': [2020, 2020],# h' [  ~6 I) I
                                 'month': [1, 1],& I1 {0 a) ^1 Z3 k" d
                                 'day': [1, 2],$ R, |' ~3 j+ z' u5 M) h
                                 'hour': [10, 20],) N0 n  B2 B$ U- W: H7 w1 ~; D
                                 'minute': [30, 50],
    ! ?) z' o/ K4 _1 G                             'second': [20, 40]})
    ! z2 }. ?/ P3 }6 j) ]1 n% K% Epd.to_datetime(df_date_cols)
    3 L/ y3 ?* t, G: H. [8 K5 ^4 T
      }. y2 N) _* H8 i0   2020-01-01 10:30:200 H+ G7 d$ {2 b+ E% T% C) j# M
    1   2020-01-02 20:50:40
    . p& F6 U- T8 h: o8 T7 }- b1 ?dtype: datetime64[ns]" I" t$ ]  V3 d2 r) @: Z
    1
      L2 z% |" x2 g2 N26 Q. v% ]- Q( [8 J* H5 t( w
    3& j5 E4 \3 X5 j# W
    4# ?. s1 a- c/ X4 J1 C3 G
    5
    7 k' L/ R$ f3 ]; x4 Y$ }+ n! R6- }, b5 G1 [/ I, `% x
    7
      w  n, d# z1 q- s( b8# E/ i- D1 M+ T6 O* Y0 ~  L- g5 _
    96 I: t1 T, ~, a& h" A8 Z5 m
    10
    6 G  P9 |8 ~- |. ~" E7 P/ d+ Q8 [1 |11, |+ r- V' v) e5 O
    date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    , A: e. e5 ?$ a0 o, Spd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含5 I& v# T, M. P$ [& X2 J* f
    Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')3 k# L, h1 _3 ^2 V5 ?# {2 l

    ! ?9 z6 F, G( ]4 P5 N. Mpd.date_range('2020-1-1','2020-2-28', freq='10D')! W$ ]1 `# O. a# Z
    Out[26]:
    4 }3 }- V) q+ G$ y  eDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',) L7 A4 [, q! L- Z8 ^
                   '2020-02-10', '2020-02-20'],; O, I. l) F4 m5 u
                  dtype='datetime64[ns]', freq='10D')3 l0 m" U6 }  L1 ]3 x
      f: K: c, q9 z* K& n7 g! i, t5 S# ^% V
    pd.date_range('2020-1-1',
    1 T& R3 i* u/ v8 w" o              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天+ s1 X. z* |' G4 z; K* t
    & Q2 {) H$ l) s9 f
    Out[27]:
    4 u- w2 T" t- q4 }6 l1 tDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',- y5 T8 }8 L+ k+ |( u. b
                   '2020-01-24 04:48:00', '2020-02-04 19:12:00',$ ?$ f6 h" ~6 e+ X% f! Y" W' X
                   '2020-02-16 09:36:00', '2020-02-28 00:00:00'],
    / g; H3 ]/ ^% Q# s              dtype='datetime64[ns]', freq=None)
    ) E+ o6 \. D# Y1 {% w$ E. U
    9 w4 P1 T! p" I" |  _! B, j3 ^1( |! p3 s6 J/ k: t9 F
    2
    ; C/ j. z; @: S5 x6 w4 _: [# s3
    - S+ q- n4 S. d. E5 R$ q5 ?' U4
    ' S: S9 q  A3 y- h51 ^/ I  G* c2 x# |. z
    65 t0 F8 f) q1 v3 N7 J5 e# q
    76 G# |- B* {& B* d5 }) n  e
    8
    ) I) s9 I! i" L, T+ x9
    . w! K$ i) I2 C* c/ ~' y) D( A10, h5 i* Z' y7 b
    11
      x! ~# N' E# A% L12
    . b; w1 Q0 Q. \- J13
    ; t% S4 Q. ^. P$ I; ?- @: q! t14" \4 d" F" L! S7 t$ L) y* c* `
    15/ A8 m8 y% Z  l+ U, Z$ e3 V4 g
    16
    6 t+ C- f5 P/ x/ D+ x) `173 \8 A& _- O9 K  `' w
    这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。4 a) T  v2 W4 y. T/ v3 x  t
    0 c0 }5 `! p6 k  L1 m
    【练一练】
    ) j7 j; ]3 s) z% `* r' vTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
    7 G" G3 D2 A6 D% m" j0 C7 N
    8 j# u2 A( b) X8 R: O) Fls=['2020-01-01','2020-02-20']
    * o/ s6 Y8 r3 _1 Ydef dates(ls,n):* |( f* S( z. V5 I+ B
        min=pd.Timestamp(ls[0]).value/10**9+ K0 r0 m8 h: ?1 L
        max=pd.Timestamp(ls[1]).value/10**9
    0 H1 p/ q! Z$ {& p1 ]4 ^3 s    times=np.random.randint(min,max+1,n)& \9 r" j/ h+ ~+ ?; K% `  i; y
        return  pd.to_datetime(times,unit='s')
    2 A* Z- c9 g$ k) O1 Edates(ls,10) 2 W: [0 v. K1 M1 h+ S( C5 E- q  }
    & s( Y2 x$ N8 O( ^
    DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',/ B7 }3 Y. o) K; r
                   '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    " c; h. |  G7 d, ^/ }3 K' V* v               '2020-02-15 00:18:33', '2020-02-11 02:18:07',2 q4 G* e* J5 x0 r# A  A6 S5 W
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',' T# \1 a& P' D# f/ _. w
                   '2020-02-14 20:55:20', '2020-01-26 15:44:13'],! r& y+ U, X1 H0 F1 Z3 ?
                  dtype='datetime64[ns]', freq=None)5 }/ S) ]  B9 ~9 B2 ^+ t
    11 n2 f* Q, V! }2 h1 Z& Y
    2
    ! G7 t* e0 Y  z- u3* k+ ?3 _" i; }' W
    4
    ( M* R" A# B) Q- O/ K- C* P5; K- ]6 R. P* W  f
    6$ p$ _% e5 V. N$ W
    73 Q- l6 O5 }6 p4 t$ V; b
    8
    3 Y1 P1 k( U5 A& ^, k. `. ]98 B6 l# y) @+ e: d
    10: X3 u" U  P& }3 L, W5 v0 q' F# S
    11& b, t* u+ b* x
    12
    6 P  p) G/ Z9 `+ d0 w6 P+ K6 u13
    ' Q; s; B( h/ `/ e6 t) n( R7 U/ b14
    * `1 ~1 o  S3 G' S& Q6 Z) Kasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:6 |9 \5 O# c0 i* x; R# Q8 l$ x( V
    s = pd.Series(np.random.rand(5),
    " y! h  M; m2 ^5 d/ ~5 G3 g            index=pd.to_datetime([
    ( l# P& g; Z, y! g$ E! }                '2020-1-%d'%i for i in range(1,10,2)]))" L3 ]) g7 X+ `, [9 s
    ' K0 v: \" @0 ?! Y

    / ]& W: v5 J1 P  }6 gs.head()
    % j" M8 `" ?3 ?, R$ f: f) iOut[29]:
    6 ^" Z* E! H! B1 m2020-01-01    0.836578
    , k$ c0 P4 ~+ e) `6 Y& U2 S- \) q2020-01-03    0.6784193 V# `0 @* E" v) @, I
    2020-01-05    0.7118973 p5 l0 |$ R" ]' T4 Y1 t0 r
    2020-01-07    0.4874295 n6 z8 g9 R8 w- P' k( y+ {$ i
    2020-01-09    0.604705: |" W& p9 X1 s% y% a: l2 B
    dtype: float64+ X- ^5 H- P3 Y* ^1 B
    ( h6 V" C! x3 E% ]) h
    s.asfreq('D').head()
    # r6 F. c& f  O, E/ TOut[30]: 7 l! l) V8 b7 ?, {& s3 b0 L3 n
    2020-01-01    0.836578
    % p& e  e/ x4 {5 g' m4 ?2020-01-02         NaN
    6 O  [' G% _! R, q  d$ S2020-01-03    0.678419! V- A" X' A- ~. C% P
    2020-01-04         NaN( q# `* f3 M  c
    2020-01-05    0.7118974 ^1 L' a3 q8 g  _- J
    Freq: D, dtype: float64
    ! j/ X7 \/ l& t' f
    % Y8 E+ I8 z2 B6 ~' u) N$ Es.asfreq('12H').head()
    , `+ M/ _" q- V- M% P0 YOut[31]:
    8 l6 z- Q; B& [* p- y2020-01-01 00:00:00    0.836578
    ! {: {. F1 f6 g2020-01-01 12:00:00         NaN+ \, i4 B, h: m6 q. }
    2020-01-02 00:00:00         NaN2 I/ }# G/ x7 o! W4 @+ `4 c- L- B
    2020-01-02 12:00:00         NaN
    - Z; y' F% \/ v& N" b! K2020-01-03 00:00:00    0.678419
    $ a# h6 {7 t! [/ L4 gFreq: 12H, dtype: float649 S! M& {2 F# Z' [1 U

    / b) ^2 B' ?* [$ B) f/ P9 A1) ?" q' [$ e( N% T: l0 A+ l. e) K
    2
    2 }- X& n5 Q- V+ u3% U# K% }" m2 |+ n2 [- f: z
    4
    1 R/ t( q  J5 u; s! U& U5# B3 r; e& u( A1 t$ C  O+ ~; s2 o
    6
    & x6 h) n9 X/ ^7! n0 g/ N4 x: N( R8 E
    8
    ; w1 T, c0 @0 ]# d0 M93 o0 r, m2 F6 [; _# v
    10& i6 c% Q, ]  I& v+ G! e: h
    11
    ) H9 v" }3 M: Q12
    7 q2 v. j# I" U1 _+ C13
    3 ^/ V: V% m; M0 K  \& C9 x14- V8 B* m  _( K- i3 p
    15' T' `  l5 ]% y# z" g8 s8 k! q3 O8 L
    16- x* t! y( L  Q5 R4 t
    17. A6 z# s% w& F* c. h7 @( i
    18) |+ ], S' g. c( e  d, K+ F" V
    19
    " P' n7 }" ^$ Y3 J. M1 q20  E( R2 w: N: g- M! C
    21% g4 J& M3 g8 Q+ V$ a& [
    22, Z4 l: h0 D9 T2 S
    23; f) k3 C8 Z% M. T% U
    24
    0 ?3 O  z: G; h, d; ~( a5 M253 y& q5 d6 Y2 G  f8 \' h
    26' V* ]9 G: Y* O7 d5 N
    27) K% U% ~/ e$ K0 `
    28' f. S2 K; h( A% @1 ^6 c$ P! I# o
    29' S6 v8 ^" H. d, r5 ]5 U
    30
    ! O! t/ o' h$ ]31! F2 y/ ^' J; t9 e' o
    【NOTE】datetime64[ns] 序列的极值与均值3 H# E1 m$ a  p4 N# A7 R- Q
      前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。5 l* S8 r8 Q6 l* D% w% q8 Q
    : \$ b+ B! s1 Q) S1 \
    10.2.3 dt对象
    ( S/ P3 B: j7 _  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
    ; ]4 s$ a$ ?" U* z9 q4 l/ L3 a
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。; N# M9 g% }3 n" ~! X+ c" N
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))3 `. x  _5 }1 {$ G+ m; }5 m/ T

    8 K4 S; _9 g9 j: S, bs.dt.date" r$ ^7 a# ]$ n, R8 v
    Out[33]: $ t, P% r6 @* {. V: q- O$ o  u
    0    2020-01-01
    % i$ \0 R6 S# [- @% h% i1    2020-01-02
    : z, D+ p2 O/ O9 w. w7 L2    2020-01-03
    , c. o) q  T7 e  A6 Bdtype: object* ]% H$ K  v& x9 N

    # f  r2 V# T, e- |s.dt.time
    ' G, U- X7 b+ b. y/ F6 H5 cOut[34]:
    9 s& Z& P; l. h& c4 q' h! ~- i0    00:00:00
    0 v# Z6 Y  b/ d: x% Y1 t1    00:00:00, R6 S. n  q. R" z9 ^( E3 M
    2    00:00:00& N# ~9 ^/ \0 J. c. O; T* J1 ^
    dtype: object
    & Q& {; D( h8 A- }' s6 z1 S" ]% M. C8 S" U9 `
    s.dt.day
    1 Q: |- b4 ~& OOut[35]:
    ! c  ^. }3 F9 f% O+ N! {6 R0    16 b8 \% S9 C  H1 E
    1    2
      m% m' r& {; n" o+ s- `6 }" ]3 _2    3# x8 l# e$ }: d5 q
    dtype: int64
    " ?5 Z) u6 j4 V. ~1 P4 }( ~% m. J  L8 z) y* P8 a, T' |2 v* R, u, P& ^' _
    s.dt.daysinmonth6 j& c& a) {2 `% f8 y
    Out[36]: 8 x- s( I# }" p& r# J4 e! D
    0    31: ?. T; I8 k, m5 {5 Z3 g# X
    1    31
    ) ]0 X& U1 y4 F7 K2    31
    5 X- ^9 A1 u! S2 H7 T5 Edtype: int64
    $ |4 I3 M; E% T# b- b+ q; R
    % [  @& I4 K) c# U7 s. ~12 r. |: f- H7 i- E& B# t* O" u3 g# ?
    2& W' [6 s. C9 b! ^8 x
    3
    - l: p% ]  ~' `( H) G2 _43 }2 M" L2 S& X( k) r9 W7 V6 M
    5
    : [  N1 L3 i) V# i# F6
    1 n8 V2 W- K4 `3 L$ g  M76 d- y6 w0 ]1 d" h. \
    8
    * q: |: i8 i7 j: A8 d$ g9
    * c- ]" P; x7 C4 }/ f( n) q10/ M6 y; [$ G8 r, u. G, \2 |- T
    11' |- G! g1 v+ k9 E$ ^( [4 ?0 k
    12
    - Y8 S4 k6 y2 _' t; S13
    $ d- _; i2 y' Z14
    * i' v" r9 Z$ ?/ u15
    8 ^9 b( ~+ V9 t. ^16/ J! V& a0 i3 C, G5 i
    17) R; L6 G5 ~" a7 W- h2 r  y5 e3 W
    18" K6 H+ g, y' L$ W) ?
    19% n3 A# A9 U  V4 V/ q' O
    20% l! V6 `5 R! I( e9 c0 n
    21# e* `  e1 |+ M% q
    22) @- F5 }& a0 c8 D
    23
    1 `6 n5 V. Y: L1 L  H  N' o24+ n6 V9 L$ M2 o, z( A1 ]: w' B
    25
    & O/ e5 W3 d: b, o26  Y' H" _" J" t/ b5 `  H; ?
    273 ?$ i% J" H  ^& x
    28
    % p' {0 d7 ]4 V; x  Q: ~! H29
    ; d8 [6 V) w# F5 e. @  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
    ! B4 ~4 T( `9 U  g. p# c4 {
    4 t' [8 B- h5 J4 p1 Js.dt.dayofweek% f' f- m! S- k
    Out[37]: / L( F  r7 N- A4 f
    0    2
    ; X$ ^9 @4 Q# L: ^1    3
    * r0 [/ Q# R: Q# X6 e9 N2    4
    9 u6 g$ g; _0 D$ b; Fdtype: int64
      U5 Y' ?, T$ j. x2 H4 K
    ; c6 T* R) o7 Es.dt.month_name()
    * U& B. M& |: `( f/ xOut[38]: ( ]$ V" k: T. W. G, }6 I6 R% N
    0    January
    % x( t$ q; p2 j( k% q. A' v/ e1    January
    # Q3 J+ ^, o9 @- ^, A: G: l2    January: o( _: B/ V# c* I! V6 c
    dtype: object6 H; c+ r1 [& X4 d% k' z
    8 q7 X. N1 b  t- N$ C. H3 G! R
    s.dt.day_name()
    7 Y; u# F4 u' B& n0 ~Out[39]:   F  q; h/ I- G9 J
    0    Wednesday
    1 P6 J7 B& I7 q! X9 [1     Thursday
    7 t% l- c, i7 G6 F; j2       Friday7 w. E6 t/ f& U* d; h' _9 o2 J
    dtype: object: e/ s1 u9 z4 z6 N* {( A/ i1 T
    3 ^1 V$ ^/ x1 I" ]$ H  H' R& o, z
    18 g. p7 r% ^5 z$ r- B) l
    2% s1 r! q0 ]* w
    3
    6 w/ s& y1 i) ^' |6 A% w% r% ?4# ^) t$ E1 `  r7 J' b
    5! u! }1 g0 H7 ^+ E  `4 A1 q
    6
    / S+ I" m' h" _  Y4 j$ ^7; G- D$ H4 s0 H; S0 |, ?, b
    8' J  j! h$ j- H! ~* G. [8 l
    9
    ! F# ^4 q7 b8 e" e8 g: H' Y106 }, ^6 g4 B+ W- e: Y
    11
    + ?5 h3 v" h$ s12
    3 v% v% T& {* R8 b13
    , M1 v' @- ^1 p+ S3 G14# T/ I3 G5 A! K9 @$ e
    15
    9 j) t( S! x( S' @9 L- E3 @16
      ?5 B7 G- C* j8 b+ m17
    5 N8 {/ n3 A* C( n4 R18
    3 @$ E$ g3 t9 a: q( t" I19) S6 U, c" k9 \' b
    20
    0 k4 }! G' i; N+ k. Y* N3 _第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:* w7 B; o& e, I$ q) h% |
    s.dt.is_year_start # 还可选 is_quarter/month_start3 z3 j% D  s% F9 P5 X2 r8 d' z7 K% ]; l
    Out[40]: 0 j6 J/ q2 @* T" Z3 H
    0     True
    4 `& T, w6 m: k8 W) X1    False1 H; Q, ~* D& o' W2 x" J
    2    False
    ) V) ?7 e  u8 W0 u8 v( ~: f+ [dtype: bool
    , T+ E) O0 u( k' @1 t- U+ P5 T* Q4 Q+ K, F* B+ `
    s.dt.is_year_end # 还可选 is_quarter/month_end% a; p" V# B% Z! X6 N% ^( [
    Out[41]: ' F& S3 `9 T1 X! ^9 z; Y! d" E
    0    False
    7 u! P- X. D0 r: O8 c1    False
    - j# Q, t) k5 t# Z8 v7 `2    False: M7 E9 G; |6 f6 D3 F* X
    dtype: bool
    8 |9 ]5 D. Z" f/ C# E1
    ( _  g7 W1 h5 P# c9 p2
    ; m* v. G* J! T6 C) G! f/ }3
    ' O) ]4 e- t* P0 _7 ]. W4
    7 H7 S/ |! \% Z/ X" u5
    $ L0 K- a) z. c3 b8 L( e" u6
    & [# x2 s- d8 l7
    - a8 _! {: v. D( d$ w* o8( V) z: F" t. A6 a
    9
    : s+ U1 C% U2 I6 @+ w1 }! }10% d: l* A9 k! u# a
    11
    9 F' M/ [" j2 m4 P0 g! n8 @12/ F( ~; |( m. R) b
    13
    + m5 p$ f7 m2 j) J# V第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
    ' {! l) X8 p7 a0 y2 C' ks = pd.Series(pd.date_range('2020-1-1 20:35:00',6 g! r# ]% ~$ C8 A, ^
                                '2020-1-1 22:35:00',
    * G$ ^  U: O4 S( f' N                            freq='45min'))
    # v# ?/ K' A9 f1 B* d/ Z- d. H6 ?2 D8 b8 C2 |: L
    ( N; P1 `9 r6 H& [5 P
    s
    & u. H+ I6 ^, L, t4 x* [Out[43]:
    . ^5 O8 [2 ?$ X9 A5 t0   2020-01-01 20:35:00/ p3 _/ ]% g; w  R) u$ z, J  S
    1   2020-01-01 21:20:00
    $ X5 ]0 I' y* k) o2   2020-01-01 22:05:00! Y8 C  z2 a, L1 j) X
    dtype: datetime64[ns]
    " P! E8 ]- E/ b! e- X0 F. Y! L' [1 b* N. W- P* n; D) B
    s.dt.round('1H')8 g+ y0 v/ `5 `3 W# t% r1 R
    Out[44]: ! V3 T7 }$ m; U7 m7 [/ K: m
    0   2020-01-01 21:00:009 y( j8 C* \9 K$ u) F
    1   2020-01-01 21:00:009 E( G: K4 M' U9 }; z: N4 G
    2   2020-01-01 22:00:005 v& }* [0 I( j& P
    dtype: datetime64[ns]+ _4 W* W+ \/ _2 l9 o: I" w3 c4 X

    1 f$ {% m$ G+ W" ]5 Y: cs.dt.ceil('1H')/ b! F2 S0 W  H1 C4 U
    Out[45]:
    / g' U3 R) W4 K  F0 t0   2020-01-01 21:00:00
    5 Q, Z8 G7 y$ m) h) Q1 y" |1   2020-01-01 22:00:00
    , U* o' w8 m! `; y- x+ j; o2   2020-01-01 23:00:00
    1 Q; Y$ S0 p, X: u' Idtype: datetime64[ns]0 f$ L+ ?& r, |2 h8 Q
    ; b) I. p: ^& }; a& h
    s.dt.floor('1H')
    " p2 a9 |! w$ A% ]/ ^0 POut[46]:
    ! X$ x- C4 B2 ~5 N6 y, g0   2020-01-01 20:00:00$ k7 W( \$ g6 w9 s1 {; O" X0 A
    1   2020-01-01 21:00:00
    7 R2 T! h0 B. q6 A- i/ D2   2020-01-01 22:00:00
    5 v" y$ |( d; z6 z- q' z" m' }dtype: datetime64[ns]
    1 @* p4 y9 V, I" K$ b+ u' y# `, [4 q
    19 m, T' o' K' f' r/ V
    2) e" }6 e1 z0 b9 F- ~
    35 W# [4 o1 o! W6 U
    40 Z% y& ^8 ]2 S# h# B
    5& A# \8 H5 R3 z" x7 j% ], A! A
    6
    ) Q, j" p  R, g8 F7$ I4 h, K3 ^( k* Y' V) k
    8
    6 n7 b: [* s) I9
    ' w" h, J. `, X9 _, u& S10
    ! l: j; h' @5 w3 L11
    8 G0 [/ ]; \+ o  z) p12
    ' p( L/ d! I# Z  G' X. T( \13* `5 q- p% A8 U! @7 I
    14
    % X4 [2 a5 G! j0 J15" n3 `; b9 I1 J% N- \3 w
    163 c* m4 E% ?; v4 n) v
    17
    ' u7 `; J) z& O! D9 d- l18
    # Y% k. @+ e- L. \& d9 d8 u19
    $ |+ M: e' v$ u  `3 |- x20
    & S, @6 Q7 n1 w# z% V21# y! |3 \7 |! B- ]0 Z7 r( _; x" }4 \
    225 F3 D" I" B: W; R! g
    23; a3 _& @. e6 N
    24$ V8 K9 z/ x2 x; L, P; ^
    25
    5 ^- }; C8 F6 k4 D# b1 I9 B8 l26& s5 [! Z: h7 c
    27! D  ]* A( @  \
    28
    + o. d+ C8 d6 |7 z: B* x291 e# [: L7 M/ _" ]
    30
    6 x" l5 \. i4 F1 O317 M) D  u8 c4 a- P, B
    32! k  n7 S6 H4 Q& R8 p3 ?
    10.2.4 时间戳的切片与索引" u6 f$ q' D4 R9 t. R
      一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:6 D1 d- U9 s+ N# l, A# @9 ~; i

    * j/ b& z8 |4 a3 n利用dt对象和布尔条件联合使用
    5 p3 B$ Z7 P. c( k2 Q% j+ T利用切片,后者常用于连续时间戳。
    1 T& l! Q1 d) |. K2 {s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
    1 ]. H- \' ~; U0 e' y: `7 bidx = pd.Series(s.index).dt0 z- C( t9 w) \0 B0 q
    s.head()
    6 c- K4 v# I( f5 L: f% V$ m2 d, e& K" A+ J
    2020-01-01    0% H7 a7 J% x2 _5 {2 `; l  I
    2020-01-02    1( N5 \7 h/ E; G& K: U
    2020-01-03    1
    1 W% g- e" N4 v2020-01-04    0) D) q( I: C) a4 E" e8 T4 V( Q
    2020-01-05    0
    " G8 r, m5 @7 r0 L! ?- N; p6 H5 tFreq: D, dtype: int32& m5 Z( G% }5 g3 M6 v% ~" d1 D& @
    1
    % r9 }+ ~  p& ~: K( W: m- L( O2
    8 W& Z' P( H4 m3
    4 l7 L, g9 _. Q. _4
    0 o+ W4 A/ P6 @+ w) J5& ~: z1 q4 e" R8 l; o2 \2 J9 f0 P
    6
    5 k! }1 }6 T9 d3 Z5 O. D74 G! I! r: }4 q. X- Z6 c$ b. V
    8
    1 C) }1 g& j$ f9) z$ p* s) V- g/ y- d
    10
    6 C7 U: c& s$ |# qExample1:每月的第一天或者最后一天4 ~! x' ~7 P3 S  K

    - S- W4 S1 W1 W9 Js[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values$ H& n  A' V  ~% k& K2 `! j
    Out[50]:
    , |9 G# y. t- Z# o. H  [+ |2020-01-01    18 p* H% W1 Y, r$ h, ~
    2020-01-31    0
    ! u2 r3 |) z6 O; z4 l) `+ m$ c2020-02-01    1! A! X5 h0 E' C* S
    2020-02-29    12 Q9 X5 _& F% W- a2 c: V
    2020-03-01    0( K  C- {) w0 ^. n% o; L
    dtype: int32# g; B2 V: X* ?. T2 b
    1! m* H# Y) _$ W7 i' `3 G! J
    2
    # |; L1 v: H! P  `0 W7 T& [( b3' F5 I2 S) ^% \/ W& d/ |" S4 @7 {
    4
    3 o0 K* Y' W3 @! |* T/ ^5
    6 N1 V. I' E2 R/ t6
    % L; E' X/ i3 O7
      _3 [) U4 q. [8
    4 I3 H4 \8 \4 C- j" U( hExample2:双休日1 `; _6 e% J  c8 A! Y

      j8 h; l# R. y0 Z: \s[idx.dayofweek.isin([5,6]).values].head()
    + D+ V* C, M) |, ^8 G) a) OOut[51]: . |1 ]9 J! \8 L& \6 a; L
    2020-01-04    1
    6 x. e8 k: h0 ~. p- l7 L) K2020-01-05    0
    ) f* e9 @0 v2 C  R" V) a2020-01-11    02 r2 z/ q# J+ Q+ g3 ]5 H
    2020-01-12    1/ B0 q7 F/ }$ c
    2020-01-18    1; V3 P7 l" o/ W) C
    dtype: int32% Q6 Y: x9 Z! z) G5 ]
    1
    ' i7 y* a9 r% X$ \8 i2; i: z0 h! U. ~+ F# b
    3% S. @3 M8 q3 \" l
    47 l9 o& ]) @% ^0 o6 y! v- v
    51 e7 b5 D4 `  `" [' C% r( J8 V
    6
    ! `+ \3 ?- K( ^+ A7
    8 d  p1 j6 {( i7 `% n8
    2 b& ^- |. \  N9 qExample3:取出单日值! Z5 \. m1 s! c' k* i# n8 q% U% v$ ^

    6 s; O9 L( P! L: i9 q. Z1 ys['2020-01-01'], s* l: z; u, _
    Out[52]: 1
    $ R/ w) d8 }5 _8 F* x& x; N
    ' a! c+ y  _# B) S) i: G! t! ms['20200101'] # 自动转换标准格式
    - h" J& D0 j3 gOut[53]: 1
    2 O! o# U8 @; N) f& i1
    ' K) _9 @0 B& |+ z; X, t. V2
    5 j* j7 K/ K, n9 X; G" C$ j. f( H36 J# j2 W4 L: O1 W" X$ h
    4+ t& h5 a! ~  T6 _  y" |
    5& z. o# A# R  c1 P" _
    Example4:取出七月4 u9 K8 C& ]% W: Y8 o- D- C# p# o% f
    ; ~  P) A/ `# ~5 ~. c  R3 U
    s['2020-07'].head()
    5 z% F' [% ]* u$ S. Q5 ?7 Y, t# G8 KOut[54]: / I! d1 O; d$ I! M9 W
    2020-07-01    0
    1 L  [+ e1 [6 h. c$ n2020-07-02    1
    : P( B8 Y5 D# X$ w; o- A9 n2020-07-03    0
    " `1 y* c9 W% r- E0 \2020-07-04    0
    + ^/ ]6 d( [9 ?& L' @8 R2020-07-05    0- l5 F% S8 p" w. m. w# A3 O) ?& ^
    Freq: D, dtype: int32& C% r6 G$ Q9 P7 r+ v. ~' N9 }
    1
    & ~8 L  H2 }; q8 D+ C' x6 y2# e1 @  N6 ], H
    3( g, ?8 u- A) z1 Z
    4
    1 S$ e3 Q# J6 Z4 k/ g: P9 R2 T  k5
    $ B. _7 K9 X, m9 _8 @4 w60 N% |' P8 m2 I# s! G* i. n
    7
    2 J9 c) e. g: U# i: t1 Y$ Q5 m8* Z4 F+ s& e7 m% B% v; O+ G' w
    Example5:取出5月初至7月15日
    2 @) e. E/ e. q1 P, K8 r7 S/ ~. T2 R) j$ b& F. p
    s['2020-05':'2020-7-15'].head(), [2 W/ O& y- W. l$ z
    Out[55]: , K* Q7 u' x, Q
    2020-05-01    0
    ) N4 Y. X9 _. h/ s. `2020-05-02    1
    7 Y' ~- n8 e6 o# o7 e. s2020-05-03    0, ?! Z# |( ^/ S  G9 c
    2020-05-04    17 W1 A5 M2 ?, ?, N, X: u7 V
    2020-05-05    1
      a7 }$ [: C! b9 ^' ?  ?. nFreq: D, dtype: int32
    6 O6 R& F; n& k3 [8 b4 C
    5 W4 ^7 g0 f* j# S6 |! @s['2020-05':'2020-7-15'].tail()
    % K3 q" R2 L3 T/ u: n7 _3 ^0 ~Out[56]: , x! G1 g/ q( p( A2 E; p
    2020-07-11    06 u, A* G* ]: w% ?, u
    2020-07-12    0
    ; L# {2 |  I/ q: ?: m1 @5 B2020-07-13    1$ [2 ~) i. q2 c4 @2 ^6 l
    2020-07-14    0
    ( O/ s" O% E* j) ?% C* y+ [, @2020-07-15    13 S1 w1 k' d# z
    Freq: D, dtype: int329 o/ l( v% v% ~, @2 X3 p' d2 J& _+ H
    ; C! ?8 x2 S- F. D
    13 F* A: m, z6 B+ b( {
    29 Z. y$ d6 \: W/ u6 f
    3
    7 s: e/ d* [, d7 s41 v$ o- e- O& q. l2 v
    5( [5 Q! d& a8 `5 ~
    65 X4 i# h$ ^! e
    7" J$ t& i  E. d7 S
    8# z5 u' S4 X5 b) ?( h* S% i+ `" |
    9
    ( K7 ~* y7 V3 o; w: |, f+ `) f10
    % R3 ?$ a1 `" k11' R: x9 w  U7 ~, t
    12; g, C* {3 n* M' A
    13
    2 x" H* ~1 f- P4 a2 S, f  d14
    3 u' {6 b5 S+ \$ D/ A2 y& f7 m15
      c% _" j) V: k3 `$ y1 U( r16
    4 `9 t' c6 O# {  g17
    9 R) l& a1 l4 y/ H$ Y6 s  {/ |2 G10.3 时间差* y& ]& I+ o+ D& z3 ~+ F- f
    10.3.1 Timedelta的生成
    5 H5 ?) Y, z4 ~5 I' k6 Cpandas.Timedelta(value=<object object>, unit=None, **kwargs)% X( ^5 T& C. f# u- H
      unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
    8 k* ?  z# E8 a' c7 F+ H6 [  可能的值有:6 H% P7 h  L3 F6 [9 o! Z) ^% v4 P9 c
    ( O0 U+ L( m9 F7 L
    ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’; j6 M+ E3 B, t4 y. O9 k
    ‘days’ or ‘day’6 l& B+ X; [& U9 g7 \* S
    ‘hours’, ‘hour’, ‘hr’, or ‘h’2 ^$ Y& C1 v) U9 f6 i2 O
    ‘minutes’, ‘minute’, ‘min’, or ‘m’
    7 @' H7 |) ?: J‘seconds’, ‘second’, or ‘sec’7 ^7 g4 D8 K0 N
    毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’, p0 s8 B+ H/ ~# z, a6 d3 W
    微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
      ~' r4 Z" ^- g. r7 B纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
      |; b3 q/ E; S! l, Q- ]时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    / T) d! ^; @. j* j8 b; e2 P9 n" J+ A+ wpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
    7 d8 I5 p7 {# d- M% f; VOut[57]: Timedelta('1 days 00:25:00')
    ' I& ?5 e; ^8 |! k6 g" w
    9 T. P0 y! G9 Q/ v, y' gpd.Timedelta(days=1, minutes=25) # 需要注意加s
    6 K; O4 \5 t- v4 WOut[58]: Timedelta('1 days 00:25:00')
    0 u1 m9 q8 I6 ~( j/ g! R, K$ {( i' w, h' O, L( R/ S
    pd.Timedelta('1 days 25 minutes') # 字符串生成
    8 y# _( ]( _: L% V. ?$ NOut[59]: Timedelta('1 days 00:25:00')- `. |  I+ }3 E6 @
    8 ^7 E* W6 _  f, S
    pd.Timedelta(1, "d")
    / m) m  c  h& @0 [8 y$ D9 iOut[58]: Timedelta('1 days 00:00:00')
    / I: ^3 Y/ L* x) y9 d1* J3 |4 J# F) D' t7 W+ g
    2. v' Z7 x/ c, E
    33 p' M  D* I4 O& q1 v+ Y! ^7 W
    4: V* [  K, D! F/ O
    51 F' S4 X! ?* T; f& l) l
    6& g$ U7 [, [5 C& A, v8 F  d' Z
    75 B: n8 N+ R3 [5 r3 `
    8: `5 R. W; x# g: m% |
    97 O6 n3 \# ^: J7 c' q4 M+ X; a
    10' C1 I# J. m9 |
    11" U5 r8 d4 {. q: ~- E/ ?7 ~2 B5 h
    生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    3 ~, k1 U" |, s5 ?/ Zs = pd.to_timedelta(df.Time_Record). h: r( Q' B/ ?7 Z& F( A9 q

      Q3 f! L  v6 Vs.head()
    0 O( u, [/ X# `) s& A; O4 u( B7 x# VOut[61]:
    ; P5 K/ Y- g- J! N5 h! L/ f: N0   0 days 00:04:34
    ! A& F; }; H, }: r1   0 days 00:04:20! h( }/ A' Y, q7 `+ ^
    2   0 days 00:05:22
    0 c7 b7 {& v) G4 i& ~) B6 e3   0 days 00:04:08
    . A! g+ t2 u% Z3 s" H  u4   0 days 00:05:227 p' k% U2 Y: c( k; t/ E- d
    Name: Time_Record, dtype: timedelta64[ns]' `; p) Q6 O6 B  x
    1
    ! d& Y/ T) f7 x. Y9 `! r& w2, W: h1 h1 b+ U3 r8 V% p
    3
    * o6 r! y3 g6 c# H4
    * ~% |9 S! S. f# n( G" H" b" x: D5  A* r# p; _% g: W
    6
    6 z; M3 l, `5 p. U. L7& t- ~8 g: q! E' h
    8% V' v3 ^$ _3 Q% r. k( U
    90 I2 M, R  h$ k& r5 x
    10
    ) a% k  n1 ~: |) Z5 Z" b2 D' p* x与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:3 J1 F. O: A9 f7 h7 ]( _& N
    pd.timedelta_range('0s', '1000s', freq='6min')! v8 [$ [1 {7 x& K/ s* G" ^
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
    - c2 z  j# V" g3 p. b1 G, y& b
    ' }: ^! ~- O$ T% dpd.timedelta_range('0s', '1000s', periods=3)8 e6 I, Y0 E4 R
    Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)9 f6 k2 W3 K6 U" o" }
    14 O; T! ~1 j( s. R3 Q
    24 \" e) \2 O; N: e, |& L
    3
    ; H  \9 g0 Q9 \4
    6 _7 O/ l# H' f8 k1 O9 m2 T) w1 A5
      e+ }4 D* j' b* g! t* B& O对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    / ?/ t0 R- N: w- J1 gs.dt.seconds.head()- {% `; L# x3 Z# o3 m$ S+ c3 F! i
    Out[64]:
    6 l7 ]" {1 h. w0    274
    " k/ A) b8 d. N0 o1    260/ q; D5 \- x, B: b/ I
    2    322- D# A5 L1 T2 h# ~. h$ x8 S. \
    3    248
    3 l* Q( u; I2 m4    322
    0 @4 J7 a  ~6 mName: Time_Record, dtype: int64
    / s6 C9 `& @1 {$ S6 P! t. y* Q1
    , j$ a  D& v! B& O2
    ) R' o# i0 b' J. R3
    5 w7 x4 b% _0 q( Q5 |6 l4
    ) `, f/ J+ G+ K) i: e! e5' T% c. Z7 q" E/ A6 s$ T, p
    6
    ; J5 i( G, A# K9 s7 H0 b7! P* _* l& f4 L4 u4 |1 ?$ ]  \
    8
    : W. {8 K' h: H8 i如果不想对天数取余而直接对应秒数,可以使用total_seconds
    . b& Q, K; H9 |  s) A: g, X$ ?% w2 Q2 n) g& X4 ]; i; t
    s.dt.total_seconds().head()
    . G6 {8 b  W. T$ G. H+ OOut[65]:
    3 Y& ?2 _+ U- E" k6 G0    274.0
    ! d" z- F8 D; w2 B: k% C. J  s1    260.0
    7 g+ x! x( V6 b2    322.0
      |6 t2 ^* Q8 i' r' L2 N3    248.08 a- O9 T* h" x
    4    322.08 z9 `5 f4 Q, e2 o" k, T, y7 o
    Name: Time_Record, dtype: float64
    7 d) h) g% [- C. F% Q1, C; ?! \2 H8 e. d
    22 ?5 R; d" h3 u4 K# d2 k8 K
    3# Y6 {# \" h7 l0 G' }) H6 |# [
    4
    - J8 I! @' ~% n+ @* T5
    0 G' z6 v- O. w6+ T* v+ y+ j9 y7 m- I' x$ P
    7. M6 g/ O& [5 z- w) z# T
    8
    ; M% m0 T5 X" J: J  U与时间戳序列类似,取整函数也是可以在dt对象上使用的:
    4 b, N% h% F$ Y/ J" x! _- s+ t  V: H1 X' d
    pd.to_timedelta(df.Time_Record).dt.round('min').head()
    ' H8 q+ x' S* C) n4 bOut[66]:
    4 {! h9 t  w, U; R0 b; r0   0 days 00:05:00( P6 Y& ?9 \! M& W
    1   0 days 00:04:00% c2 G. w* K# a
    2   0 days 00:05:00
    6 d6 e  w/ A. d$ H3   0 days 00:04:00- k9 T" q% {+ v8 J* M: l
    4   0 days 00:05:00
    % b; J! x2 k: L! T$ p" v7 q" `3 [' DName: Time_Record, dtype: timedelta64[ns]$ f4 \% ^/ r$ O
    1
      a6 D3 M% d5 r, _2
    ) b" S' x) j1 C2 Q: ?3* p1 G4 V* \) M+ `* V+ ~0 R3 j
    4
    8 P! T; Q, D! q4 c! v0 H5" e( ]8 T4 o) U
    67 S3 K+ |- Q( y4 V+ r
    7
    . |6 ~+ q8 D8 m% f) g5 I% s5 \8
    ) x6 x1 K$ o1 I1 r9 r10.2.2 Timedelta的运算
    ( J+ z7 _. V9 L7 e3 V) ]0 ~3 {单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
    : b3 I; K) u. L0 vtd1 = pd.Timedelta(days=1)
    ) f. c7 @7 l8 b" z" Q- vtd2 = pd.Timedelta(days=3)
    " \8 k% U% j" Q6 w( Dts = pd.Timestamp('20200101')
    * ~' b- o0 K1 F" ?4 z
    ) ^: n, c9 I0 W8 ktd1 * 2" S4 A" y$ S6 h% a' w1 T4 c  ^/ f( ^
    Out[70]: Timedelta('2 days 00:00:00')/ V. p0 A: m# J0 n  ^8 c
    ! o' `, [3 Z2 A0 ]# z* G
    td2 - td1
    9 \& Z- `; a3 |Out[71]: Timedelta('2 days 00:00:00')
    9 x% }9 d  \: F$ B8 @! D7 c
    . z) o, [) A6 ]2 m1 its + td1
    ; y( [. H# _* x# c1 v& L- e5 AOut[72]: Timestamp('2020-01-02 00:00:00')
    ; `- G' I0 h6 W# B
    % }! b. o2 ^" x8 E' F3 xts - td1- f( ^- V, `9 o
    Out[73]: Timestamp('2019-12-31 00:00:00')$ m; C% w& r$ m
    17 H1 ?/ Q. E3 ^$ U* o1 m* a
    2
    0 ?. x  N! Z" |. N" p8 N- k+ h3; E# X7 |: t, U$ H
    4
    $ `# _7 Y  `9 ]4 M) o9 ^# N  ]5& e. b0 z7 F! K" p$ @, X( Q
    6$ Z, I2 \3 _0 L  z
    7  s. V( K$ B0 F& ?
    88 Y0 |  Q3 Y8 M. g2 O7 d
    9
    9 m5 O  ^/ d, Z' R10
    4 V! X1 f4 L& b& T11
      G9 w. x. k) ]' b. r- Z12$ ?) r3 u6 y! h8 d
    13
    * a% {( _0 u- L: I14
    8 b9 H7 s# C! I- J# S# y. n15
    * ^0 }" H! n9 L( L7 ]  c时间差的序列的运算,和上面方法相同:
    4 E+ H" Z% n+ @td1 = pd.timedelta_range(start='1 days', periods=5)0 a1 L; s# b" t7 n
    td2 = pd.timedelta_range(start='12 hours',
    ! P$ N# y  D( U" {" t0 j+ \                         freq='2H',% u* _* W. N; p; Y5 N) v7 X
                             periods=5)
    ) J) `8 h7 R3 C$ Vts = pd.date_range('20200101', '20200105')
    7 X9 V, c6 @& X9 K& H* dtd1,td2,ts
    ) W' k; m, b6 H- M6 g8 F) {6 }- Q- Y' H: h
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
    ' K) D+ D! {5 _  cTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',( b' m1 l- U# o  V9 |( J
                    '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')& r2 d/ \  A& b! |2 ^: c
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',2 S6 F2 L  q) h; V9 B9 i: k1 o0 v$ E
                   '2020-01-05'],# `2 R- ^) C( a! k
                  dtype='datetime64[ns]', freq='D')- I& e' H$ d) M9 n; [* p. q0 m" `
    1
    9 s6 q# }  S0 M( ~8 P2$ @' |, M2 |# K. v8 ?' [
    3
    & J0 g5 a. m# t! y) j4
    : N' S  o3 P1 r7 g5
    ; g9 d# b0 ^7 f4 A8 s  x6
    ' n4 `- s4 u% ~% F# W; u( X7
    9 ~: G8 C$ A  D2 Q& s3 X8* I- o' u% y* z$ ]. V
    9
    2 e. A9 _. I7 _" p, l6 ~) {10
    ! P9 p8 r1 o9 c6 x3 ^# h% G) q11) ]& [4 T* I7 \4 ?% ~" _8 k6 q
    12, b( k2 V- p2 X8 O
    135 q* Q# z- n  _7 \% `! Q8 t
    td1 * 57 n) o/ {+ o6 b) x
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')" R- o0 i$ @" ]9 Z% J9 `: B
    ; X+ H' V( m+ Y8 I. O: c0 B$ g3 {( E9 a
    td1 * pd.Series(list(range(5))) # 逐个相乘- j- a4 Z- X4 m  l
    Out[78]: 3 I. t# r, p: _" R
    0    0 days
    * b- n4 r. H5 h" Y. T1    2 days* g/ e" u& W& ?: T" q4 V' f: A
    2    6 days
    * P, d2 z$ v3 t$ A, m0 e' ^3   12 days, w& ]' M! p7 ?; g: @4 O
    4   20 days
    9 ^% J" l, G9 m& H4 K& t) Bdtype: timedelta64[ns]
    . D  b8 Z  p& p  X, [# P! f
    ; A8 b( U3 |- s: g4 rtd1 - td2. s' m, j! z, c- U$ Q
    Out[79]:
    " [8 J! c7 C/ H9 J7 o/ I; ?TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',; E7 m; ?. n; |: t6 R* G
                    '3 days 06:00:00', '4 days 04:00:00'],
    . k2 M1 E9 P  Y( W' p" W               dtype='timedelta64[ns]', freq=None)& i7 p9 ]% Y' |* E. q
    ( V3 d  J+ y1 m0 r
    td1 + pd.Timestamp('20200101')$ t  O( @$ A6 C! s, Z6 ?" c# o
    Out[80]: 8 L# @, ?6 B5 W) @& ~! m6 h
    DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    ( D6 ^- O, E# R5 A9 g               '2020-01-06'],dtype='datetime64[ns]', freq='D')
    $ @5 Y8 R8 l# F( h% N8 Y" {$ l- h: x6 d+ ~2 N) V
    td1 + ts # 逐个相加
    1 l! ^, g5 O. [% j4 }" ROut[81]:
    , K3 A+ x$ R( e5 x7 ^* zDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
    1 {" n5 ^) @- O7 j) I7 }               '2020-01-10'],: B1 o. b1 {* Q; S/ w5 I9 P
                  dtype='datetime64[ns]', freq=None)
    7 h. j- t' h5 e: X- `' p& W
    " H/ R% R, ]7 ~. Q0 L& B: |! Y1
    : t8 E$ M: z( q6 F( x7 y7 o2# u* |! d: ~3 E6 Q! T: I
    3
    & S: V$ P) q. [) v! w4
    9 f( B5 S5 a" W& T5" K4 Z* v- y( ]6 ^8 \
    6
    ! Q: O% f9 m5 i+ s) g5 t7/ o/ Z' V5 S5 R# G& O$ v# y' J
    8% T" z) ]1 N- v. w4 k7 G& t2 w
    9
    : U% n& i! M% \) U10
    " V$ O' d: P7 r6 A$ H6 P1 Z+ ]. {6 c11. a0 a1 A0 j1 X/ g+ q0 x
    12
    : p( B* ^+ S- y: {. Y2 G3 @13
    ( r  c7 u) V$ b& Q14
    & s& f4 L3 K. Y5 H7 S  K1 X( L: Z15
    % W$ F8 _  |2 I7 x% L8 g- Y16/ N4 a" j7 k: y
    171 Y" y( ^" j) J) W9 t9 }: p
    18
    " B, ~/ \! u5 n& }3 U2 D: V19$ g0 |6 M! k- W: m6 t
    209 F7 M- ^" j) h' Q) a  H* A/ L/ M
    21* b& ?/ Y! z! R# [9 `
    22
    # s1 V0 L9 P' b! t23
    , e1 `& N4 n# A( w" q24# c3 `4 i- R: }6 l- _
    25
    , \. E5 e2 ~7 c7 d. X7 Q264 U) c0 c- y" O" a9 x, ~0 Y! H
    27( t2 e" y7 K9 V6 V7 y5 D7 N# l
    28
    + {, U4 H! V: x9 l3 k9 f' k10.4 日期偏置( H( N6 b3 P2 f7 _8 x% s, G' T
    10.4.1 Offset对象+ z. D. v6 `/ e7 `) g
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
    6 r, s3 m# T$ ]) _6 D9 u$ P2 |. h( X9 h/ t, V! T% |& S# a
    DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
    5 {. h9 X* t7 m0 l1 q: ~
    # F, c9 r2 t- s4 M3 ts.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本, {) _: b6 U; j% ]. C% a) z, t
    s.kwds:{‘week’: 0, ‘weekday’: 0}. m3 A9 K9 z; W7 G/ M4 K) t
    s.wek/s.weekday:顾名思义
    ! L) W; Y3 r4 v2 x有14个方法,包括:; ?6 {9 A8 [' d8 `3 r
    & @2 u) ]* o  L
    DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    3 Q) Y" k$ g0 lpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。) |  r5 f! B8 p$ A# l

    " ^: V9 ^% X8 e2 S5 d: d/ b! u有两个参数:
    ; j2 l8 o' J; Q; Iweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
    % x7 V2 e2 K* n7 V, [4 ~weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
    ; x1 h+ y9 i  c! D8 Apandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。* k$ y: D. B8 v9 C
    * _, g6 `% `# y& ]% b* A
    pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)4 Z) e; |: J& ]( ?6 c+ W
    Out[82]: Timestamp('2020-09-07 00:00:00')/ Q- r$ B6 c% ^6 u1 ~7 K
    - C) q; l7 t& [8 _# N; U
    pd.Timestamp('20200907') + pd.offsets.BDay(30)
    6 w/ L9 `+ j: y) m( X2 K  h6 ]Out[83]: Timestamp('2020-10-19 00:00:00')8 l) w) R/ x  b8 ]( l: B# D9 l+ X
    1
    4 C: a, @& J1 @2
    7 D1 A) c8 g% U! e& p3! E6 o6 `% y! V+ n$ e
    48 [8 e. {$ f+ H. o# k
    5$ c: G/ e  R4 Y: n" p0 c
      从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:5 P0 ?' f# F  y  k

    8 j7 Z: G1 g+ @pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
    ( t# {) w( Y) w1 rOut[84]: Timestamp('2020-08-03 00:00:00')( f) w" e- S- S7 m) |
    . K4 I; r: Z. w# T9 I" X4 N/ ?
    pd.Timestamp('20200907') - pd.offsets.BDay(30)0 X& `3 g& V' h- Q' f" z5 ~
    Out[85]: Timestamp('2020-07-27 00:00:00')
    0 i3 _0 `$ w7 Q4 H4 N) D/ }. I+ @
    pd.Timestamp('20200907') + pd.offsets.MonthEnd()4 h% M. s7 `$ p" H" k. g2 u
    Out[86]: Timestamp('2020-09-30 00:00:00')6 s7 i; ^& h+ g6 D2 {8 p
    1+ D* k9 I8 ~/ r7 H3 N
    2( c- R1 o1 w1 O$ K0 |3 X3 I! A8 C
    3' |$ K- I! p3 }, h5 c( r( p
    4- |" }1 y" r$ z1 h& b7 Q
    5
    # \3 |6 M1 f  M2 A, N; s, E6
    1 \, r1 C+ Y& H5 @7 k3 W/ T7
    5 V. d& E; S' p5 y9 k5 u- A! N9 p8+ ]- x% O# E3 b# ^
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
    6 X: i2 g4 @* [  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:- x# x; n6 F' j, ?6 Q& r
    9 G: t7 t2 J1 ~
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    3 |3 i8 D+ H. Ddr = pd.date_range('20200108', '20200111'), M( _: d$ Q) W1 X# P* ~  h* G

    8 W- \" o/ q$ s4 udr.to_series().dt.dayofweek' ]; f+ H5 }! q* i/ T
    Out[89]:
    4 [2 o" ^" ]; A. F. J9 u7 I2020-01-08    2
    5 `( I/ b8 R  w; j) m! c& a. j2020-01-09    3
    0 \4 F; ^' M" A2 |! c+ q2020-01-10    4
    , O6 }& m' C' ]0 d  p2020-01-11    5
    7 Q" h# H& Q$ L) JFreq: D, dtype: int64
    $ E2 G2 P3 f/ S; F9 P0 }3 O
    ! A" }; w3 V# q[i + my_filter for i in dr]. {- K2 y1 G# I" d5 E
    Out[90]: # ?6 r, W0 G4 X/ {# j$ v
    [Timestamp('2020-01-10 00:00:00'),
    + A" G$ J0 P6 ^: w5 ^ Timestamp('2020-01-10 00:00:00'),
    7 a5 G, M: ^7 X( J; l Timestamp('2020-01-15 00:00:00'),( W& W7 E  A/ e  A- e
    Timestamp('2020-01-15 00:00:00')]) Y+ M9 T- n3 }& M* r& ?& s0 `
    - _' m, W4 J9 e1 j
    1
    6 w" v  \: s4 T5 W& Z! k6 x0 Z24 z. i+ S7 c3 t3 P! |  u) o
    3/ m6 q/ t% g) z1 E7 Q6 _. U8 C/ K
    4
    / q* @5 @8 a2 p/ l5 ?2 {7 H5
    " H# ]/ X& X5 e6 r$ I9 P! l6
    1 W3 W; U0 E% v/ m$ U7
    , R/ O9 Y8 L0 f' R) \8: w1 n! [0 L# Z- Z6 |% s
    97 D( q5 b4 p1 k$ v
    10
    ( q8 @8 d/ _1 V11
    0 B& D3 _" `% [# T% S: \121 Q) F) Y! C9 \' c9 G
    13
    , P4 p- ~7 J4 J3 {+ N14
    ! H( Z8 I4 B; t9 H9 q8 E- k15
    ! ^- n7 P# U6 O% g* z16
    4 C4 K0 l" L; Q1 [) s8 m17
    5 X# S* g9 h7 N7 i" D) m  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。5 Y) \& \  j9 y/ ?) f! ]

    0 u" R* m: J* n) W8 I【CAUTION】不要使用部分Offset
    & A2 n1 R, L3 O+ U. e/ Y; j4 k在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
    ; r8 `6 P/ h5 p) u; n! T
    % @6 o) W, L! r! [4 e% Y8 E10.4.2 偏置字符串
    7 M" K3 W  @. d. t' m% l$ Y  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。& v% s9 i+ I, |, F! A" k8 q

    8 B2 t3 j2 z* e# s9 U1 l* ]2 l0 K  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。( d# p/ @4 |! r. E8 k: }
    / i1 z3 b8 `. |  r& ^, ^
    pd.date_range('20200101','20200331', freq='MS') # 月初
    + Z' r: J# c0 g9 UOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    + G1 v- r4 H2 \- }
    9 ~+ b( \+ u( k/ W/ _! Qpd.date_range('20200101','20200331', freq='M') # 月末
    3 S1 ]* @, }& s! b- U1 ^Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    , j1 t3 e5 n" V" C  d' f: ~; k' m0 ]& a& A4 v; ]2 M" |* o3 f) c9 \0 ^! j
    pd.date_range('20200101','20200110', freq='B') # 工作日9 C! r- Y! @# b& W
    Out[93]:
    , S  a) |2 U' RDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06'," w1 k$ b, [, {
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    , [# |$ H6 T/ g" `+ }+ m: y              dtype='datetime64[ns]', freq='B')
    2 s# M! R6 L$ f; ^" I1 }
    $ X8 r' X0 Q, n  b* [1 Ypd.date_range('20200101','20200201', freq='W-MON') # 周一
    8 h4 y4 F/ s6 O5 LOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')/ w6 P8 S" J/ Q5 ?9 x. J: i. y
    7 q0 D/ n3 Q8 B
    pd.date_range('20200101','20200201',
    % |$ y% M# u9 E4 @/ n, x              freq='WOM-1MON') # 每月第一个周一+ ?9 I5 Y6 d" g; m3 @  a- m
    0 u3 g) ?, P! R' j: h& J9 f
    Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    1 X6 ~; D- F5 t$ m. E* a
    * U8 F$ ^5 q7 R) P& N1
    . }2 j3 L5 {, y4 m) ~+ @4 H* [0 Q5 C2+ R$ w1 B% g+ h1 i/ x: G( |% `
    3( J; e& E2 ^- S7 c" c
    41 X5 n0 i+ i$ d8 N& {, w5 T2 C* s
    5
    : U* F. e# x9 h6 l6" h0 S2 q( r9 T
    7
    . g0 ~! v' x/ T6 _6 ~  U5 S86 m( E* i, |6 z9 [- }& m
    9. V, q8 `  @6 B" ]% y$ W! a( P
    10
    # I! e, V9 z4 J* u( y11
    8 F+ [" ?( o! a. k9 ]12
    5 c# }- m2 i6 \; k13. v* j* w( G, I# |
    14
    $ J4 F+ Y* A5 o2 T! K1 W' P) @' [+ M15
    . N* E0 I  t, F5 v0 ]16
    : E# k) n& A: y( u- W9 X3 h, B17+ J* j' M! M& x2 _) u6 d
    18( N! ?5 I2 q1 L6 D1 X6 U8 y
    19
    * N# z/ Y5 C3 o! ?. d' r9 [上面的这些字符串,等价于使用如下的 Offset 对象:& Q2 f+ {3 @5 j4 s4 T7 A
    , E1 b' V' G$ R0 P3 @# G0 x) p8 T% v
    pd.date_range('20200101','20200331',) k0 x8 i5 H5 v3 I" _$ _
                  freq=pd.offsets.MonthBegin())
    3 |" A  x$ k  Y& ?( {( p! V
    , u2 ?; n3 i% q- n; V9 o( mOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    ( Z  a/ j3 N/ d( L8 b$ u- `6 [  J4 y6 W2 [" J  A, R# G9 Y6 X/ _
    pd.date_range('20200101','20200331',* x% m9 _% V3 [" A5 z0 F
                  freq=pd.offsets.MonthEnd())
    ! T, O7 e: j& \6 s
    8 Z- F; V8 n2 b2 ~, ~8 p* o# yOut[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    1 j% _4 x, K6 U3 @- T" @4 M4 @" w4 ]" z% v
    pd.date_range('20200101','20200110', freq=pd.offsets.BDay())/ I# o, e$ ?4 x2 K, }
    Out[98]: # W8 [' k5 m+ M5 l7 m3 [& f0 S3 s
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    * W; y6 r) @$ {5 u' Q               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    / ?" N& ]+ X; N. l1 k/ e* H0 b  X              dtype='datetime64[ns]', freq='B')
    . e  h! X# B* `. C# ]% k8 v9 ^. U4 |+ \$ g. m/ n. c9 h
    pd.date_range('20200101','20200201',9 r+ T- y; Z- b! A0 [/ {
                  freq=pd.offsets.CDay(weekmask='Mon'))0 J5 Z! _( V' m

      j* a0 w* Z3 X; E8 Y) U# ?Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    9 L2 q8 s# o, w" X
    - D# z$ f. h, D! C) }" ]2 r3 {2 Ypd.date_range('20200101','20200201',
    ; o8 I0 B3 [$ I              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))8 m4 z2 l! g' e3 C1 v2 \, o

    6 N/ e6 B) B8 @' r: N# U% JOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    : |3 y( O4 T3 ?; v
    ' @7 t; R3 S. m1/ K+ A7 z0 c' e/ ~- A
    2
    & m- v* F) j2 ]: O2 K3, r% [1 T& m+ a; e+ ]+ f4 @+ M; c
    4& ?, f6 ?+ T6 w3 V1 N* x' j% I
    5! p9 q3 s/ q% D! ]& w
    6
    7 X5 U$ U( \" \8 B! K1 J7' ~7 h. B2 H+ B
    8
    ) c) U8 n/ S/ E% L# J91 j% [; p' ]9 f' U! l; ^& l
    10
    # m8 x' z- Q/ c1 V' c- m11
    ' T. Z6 }' W5 J& W: X12$ u% C  D% l8 H3 V
    13" G+ h! ~& J: W: |' n4 H
    14. S0 _! I* K/ n
    15
    , W! u& N4 X+ H0 o( C( J# R7 z6 U160 T1 N0 ~: }$ r9 w
    174 N5 E4 ?9 S5 B+ D/ d9 s+ [- M
    18% T8 ^2 z2 e: E! ~9 }( _
    19
    8 H% N7 F7 X( c! c+ f20
    ! @5 c% j: ?% @/ e0 ?! L21+ ]3 F# z7 a+ s% ~6 N- o4 u
    22, h. o' r1 _1 E7 S7 n* \
    231 V1 E1 ^* `; V( l: f# w4 Y' H: v7 \1 @
    24) o! c/ H) u, E8 J& ?" }0 i
    25
    0 A1 W) L8 H" R+ d7 C【CAUTION】关于时区问题的说明$ W+ W. P5 x& f4 B5 F; n6 g6 P
      各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。4 Y* C7 e% Y% ~. F5 a
    ) R# `4 m: H# O2 P+ T& L" I$ ^
    10.5、时序中的滑窗与分组
    9 S$ A+ l) s; o5 X$ {1 u4 N2 {# Y10.5.1 滑动窗口
    ' t' m6 S4 w9 x: P) n7 u" `  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
    ( B) s+ o: B$ j5 x) h1 d$ Z3 H3 @) }/ O" u
    import matplotlib.pyplot as plt5 E) [" y8 v7 c
    idx = pd.date_range('20200101', '20201231', freq='B')
    - L: {# ]3 I/ ~* Jnp.random.seed(2020)6 _# u3 }: d0 p3 ^; n
    ; j6 Y. ^0 C( A6 l. D5 i5 |1 |. |0 m
    data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加/ {! C/ {, m1 F
    s = pd.Series(data,index=idx)
    " y( Z1 U+ j) fs.head()
    6 D- v2 z! _3 V% I; bOut[106]: . S0 a7 ^, G. G/ H: Y1 J
    2020-01-01   -1
    4 a0 h! H, R" M8 F: f8 u) J: ~2020-01-02   -2
    9 s2 i2 P: i% X: s7 j0 ]2020-01-03   -13 N2 h7 a2 Y7 f% ^- w& q+ O
    2020-01-06   -14 t4 y" ?, `; B' U& }7 O4 a/ k6 Y
    2020-01-07   -2
    / n/ L. E) ?5 f6 `Freq: B, dtype: int32/ S+ j; j4 {* h( R: v
    r = s.rolling('30D')# rolling可以指定freq或者offset对象5 ~& L% t2 m* K1 n% B/ ]1 Z

    % _3 Q3 G( w# d) c5 g' ]plt.plot(s) # 蓝色线5 d. _6 O! A8 ]/ L& l
    Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
    9 K* L1 F3 c4 Bplt.title('BOLL LINES')
    ! o0 m( R1 p9 g# H2 P3 d2 ^; [Out[109]: Text(0.5, 1.0, 'BOLL LINES'); `6 ~2 e: F( R0 ~" C* n/ G

    & y" y3 A. d4 @+ y6 L& uplt.plot(r.mean()) #橙色线2 b! n  M8 G6 O5 F
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
    9 C; P8 O3 c) }
    ( X- c  e; L9 H( D0 e+ Uplt.plot(r.mean()+r.std()*2) # 绿色线/ p2 c$ T+ E" P0 m
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    , ~  h$ m9 w( V4 z! J3 D" z5 u4 @+ }4 O) }5 I& ~* H
    plt.plot(r.mean()-r.std()*2) # 红色线  }0 ?0 U. a, n5 l8 Y$ q/ S
    Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]6 m  ]& w* T% L$ B& r

    9 Y2 L; s+ h/ ~; \/ i5 `1
    0 x& N! O; t% }8 F2! j3 Y; h0 v) c$ o' d% f+ A
    3
    # ]2 F4 {$ x5 L8 [- O: Q' ^4
    ' I7 ]/ |$ ^. V* D5& c3 {- ?! L. Q9 Y
    60 s! D3 y, `' F$ \; _: Z# e
    71 J% ^2 H5 k3 s
    8
    . x5 V: g- E! a9
    % ~# P" w  X0 N  u10; q6 N1 _  Q/ y" q: X2 Y/ ?6 c* c; Z
    11* a! g% ?8 [! d5 M5 D) U4 g1 H
    12
    3 W. n( E5 Z& ~/ X! v13
    % K8 {; P$ ^, {9 a) e: g14
    ! [  P! P! R, z& |8 k- R& X- `153 ~0 f; v' t  o9 p& K  A+ g
    16
    ( R7 g1 u  A" m, C; H17  Z2 L/ Z; v0 Y
    18" G# I+ `) w6 j5 k  U' y* T" K
    19/ n- j9 e  v4 {8 Z
    200 \. d% m$ _! O5 I
    21+ R9 H3 s2 H3 D( w
    22
    8 ?- @6 h- o9 j3 F9 \23& P0 M; x, K8 i4 Q6 b5 j, z0 J
    24
    . E& d* C, e4 g25/ b5 z, x3 w' {0 e) J# _. u: B9 l
    26
    3 Z+ Q" J% K% {( ]2 [27
    & o' E# v3 p% x; m$ [286 f( I- H  G, r& `8 [) v9 m1 g% ^1 @
    29
    4 ^% c: d; h4 _5 J+ _. P' R4 m  o- y1 u
       这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
    $ J# ?+ g* j2 }# q6 F: s4 E1 r   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。$ H3 g$ x5 I8 i; e+ n) u, P
    ) P8 u  O) c4 y; ^! Z; |! h# U
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]0 V3 _2 e' j+ B5 S
    bday_sum=select_bday.rolling(7,min_periods=1).sum()
    . k+ j! U' n+ v% t# l; O: bresult=bday_sum.reindex().ffill()
    8 i4 Q1 n/ E1 o- E7 b# j1 U% ^result
    7 O6 A+ j. _! R2 ?$ E% P! j2 N9 o' D8 g2 E0 L/ N
    2020-01-01     -1.0
    " b& ~! p4 [4 [+ s2 T2020-01-02     -3.0% H! t5 M+ X2 Z' f! Q
    2020-01-03     -4.0# D& G; I* O3 |" q; n! s" Y4 o
    2020-01-06     -5.0, [7 G3 R$ `' f  A, {( |! }
    2020-01-07     -7.0
    . p6 l0 v; ~1 }4 f5 K1 s+ }6 \: w  V              ...  # l" B; V1 s' i: D1 A
    2020-12-25    136.05 s5 e1 U$ N+ `
    2020-12-28    133.0
    ! Y5 z* F; ^3 }% b% m2020-12-29    131.0+ W5 t: X3 M5 k; X8 m9 _
    2020-12-30    130.0! e" @/ P- V  ]6 t
    2020-12-31    128.00 }# |; B0 ]+ B( X3 G  T, w( C. G
    Freq: B, Length: 262, dtype: float64( I7 b4 h3 K7 H+ o: Q$ q; J

    * C- |* ]) `% w* O' |$ O+ {$ f9 n15 t8 ?* V) f+ R5 h5 ?" Z+ \, B0 ~
    2( D7 A. w$ N+ ]9 l8 c
    3& j1 y( X3 E0 N$ ~
    40 v- c# c2 @! z& R+ D: I; |
    5% U9 ]" ]. H6 u- v4 G
    6
    2 y; X' x4 v( d, q1 U7
    # ?, \' \3 }0 H- a1 G8
    + i0 O1 [. G  x! U) M9
    / T2 {$ d8 }. i. q  R1 e. v( O10" b3 n/ l5 R2 Z1 ]
    11
    2 M/ N1 b6 p; E# Z. \. E  V' a12# W! i4 `% P, d0 b
    134 q: G& l/ v& j, x9 ]6 ?
    14
    # g; V# L, V0 x' E3 G9 R: r15
    ) V& N3 x; y* Q  ^  N6 X2 I16) h' a$ j$ r; F; C
    17! E+ V8 W7 l! E( |# L
      shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。' e: M0 u( o. G) Z

    ' J; n8 ?9 f8 [9 `+ t- q& H% h  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    ( `) d9 Z' |8 Z0 {. m' Y+ D. i
    ! [$ A. w; Q+ l6 f2 ~s.shift(freq='50D').head()3 ~& W6 d. N- [5 _0 {" ~( M( K+ l# X
    Out[113]:
    * h/ ~. v, \& u+ t" S2020-02-20   -1# s+ x* B) N7 o& m
    2020-02-21   -24 h; a  f3 q, w  h' z
    2020-02-22   -1! \+ w$ m8 Z* A7 T( X" A  v2 `% O8 \  z
    2020-02-25   -1
    . ~! A, s( H* f, S9 a2020-02-26   -2# S) x7 Q6 t  Z, x9 Y& }9 b
    dtype: int32
    1 Z. Y" O. }) R. A; Q1
    - v# ]$ ^( R1 S  Q& V" @2
    * m6 M+ T3 V8 o1 X8 q- p3
    6 E' B' K$ }& ]0 T- U& V4
    " l, y# j" A( P' L& g# B57 N$ f- l$ A$ O1 Y% A
    6+ j/ }! ~# ~* W* V% L9 M+ ^$ s: L# h
    7
    % E# ^  V8 Y: ~7 i7 a" ?2 |8 {4 g8
    3 w% F1 r' _" E& n+ Q+ v  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    / l4 U* p! K6 R, \" X3 P: X1 \' A' p0 [
    my_series = pd.Series(s.index)
    3 P6 ?  L9 |4 B$ Q# ~* @+ kmy_series.head()
    8 ^, u9 {' s2 v' D* ZOut[115]: & W4 R& X( P( E2 E; T
    0   2020-01-01
    , x/ Y) W' k) T$ b1   2020-01-02* O; x' K! {6 f4 m& K
    2   2020-01-03$ _& @* n2 Y7 _0 \) q
    3   2020-01-060 x+ t" b5 W3 v- B8 {; [% d' X' i  ]6 r
    4   2020-01-07
    $ ]0 h% Z, o8 i6 D6 [dtype: datetime64[ns]2 V/ x' d0 @5 u: D- n' o
    ' v  H8 [8 w1 @0 |/ y3 u/ W: s
    my_series.diff(1).head()
    8 H' T" |, g, v  \# eOut[116]: # ~# S1 O9 F1 \  ?7 b9 _
    0      NaT0 y, I2 l. u( J. B2 w3 L- C+ p
    1   1 days
    3 ?- o8 Y0 E5 }$ @4 j% P  k/ F% ]2   1 days
    / p5 \/ T! i' Z. W' m" V$ k  Y' \; ]3   3 days
    + s0 l0 l2 Y% R4   1 days
    8 `* M4 R- Y6 n$ _* idtype: timedelta64[ns]! f' F" \4 P! h1 h& I

    . h& _# S7 e/ E" T" O7 ?) _0 i; f1+ r. o7 u  x9 h5 _9 p: p$ d3 P7 }
    2) c5 @+ V. Y, I7 U! o
    3! n& A6 m$ |( s/ G2 e
    4. D$ S+ F0 e, r  ^, J# o- `/ l
    5
    & J+ ^; h$ m3 d1 I- o( Q6; B+ O9 N/ c  ?
    7& F! V3 ^3 @; K( ]4 a
    8
    + A. a- p  D" F: |4 o9
    ' T2 U9 f& V4 p3 Q3 e; }% I100 L6 @' a% A- `( T+ [7 p4 B9 @& {
    11
    # w! W  _$ l! s+ r# ]9 y" S9 t120 Q8 s; j& m9 Y/ u  r
    134 i: `+ [7 O- X; d; U7 U! W
    14& o0 C7 R7 b: h5 ?4 b$ P
    15# g$ ]* @7 _( e+ o! Q- {9 c" U. D& R, H
    16  b7 F, s- v, G8 a
    174 W. B. k* m& @( p! }' i( m
    18- B7 r5 ]7 C$ Z: u/ S+ e
    10.5.2 重采样/ M/ z; y; E) e$ x, p8 I9 q
      DataFrame.resample(rule, axis=0, closed=None, label=None, convention=‘start’, kind=None, loffset=None, base=None, on=None, level=None, origin=‘start_day’, offset=None)2 h! ~) G4 r% w  y* s
    常用参数有:9 j2 x" V1 w) T7 j; G

    1 f9 s2 k& s" hrule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象0 \6 Q& M0 x2 F3 n  K% O
    axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样% S' H9 {' W! R3 J  d9 N1 ?1 d
    closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。0 ?% H; i  Z; }. e# G8 A
    label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    7 u) h- r$ R0 D5 x$ M. bconvention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。3 w" O  A+ I. c: X
    on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
    ) m9 E8 n* h4 E1 f( J: [/ Y( Flevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
    : Y: U1 M; |* Q8 V& j7 s5 porigin参数有5种取值:/ J) ^' U; J2 ~, R$ L, c3 s
    ‘epoch’:从 1970-01-01开始算起
    2 w4 Q4 y( e2 q, s‘start’:原点是时间序列的第一个值
    : Z! R/ o# {' F! \! C‘start_day’:默认值,表示原点是时间序列第一天的午夜。$ o# f  e6 M% T/ o
    'end':原点是时间序列的最后一个值(1.3.0版本才有)3 q" B& A, ~! q7 X& L% L! w6 N
    ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)% N+ p; P1 d. x/ E6 k7 v6 Z
    offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
      x7 W0 h9 E! v& q0 r  closed和计算有关,label和显示有关,closed才有开闭。; B- Q  \9 Z3 |3 ^  K9 M" d' @/ X
      label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
    - q0 y. D+ H1 c& S
    1 T% d% R0 S5 t1 I% [重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:) H6 a$ ~" n- k, A9 _1 e- B) P
    s.resample('10D').mean().head()7 l1 m% i: `3 ?7 A+ M6 x; E
    Out[117]:
    + f  x% z; W7 c) l* h5 N% f- {$ f2020-01-01   -2.000000
    ! g2 O4 @7 w  r  s& E2020-01-11   -3.166667, D& I5 I8 K  K1 h4 e
    2020-01-21   -3.625000
    ' }3 n5 ^2 V& K2020-01-31   -4.000000
    7 l. R, i2 s* }% c5 l8 g* ?2020-02-10   -0.375000
    ) g$ {0 Z- S3 [7 P* |/ `Freq: 10D, dtype: float64: T2 e! A/ j. H0 w
    1# j2 g' k& [' Y" J: e
    2) Z8 I9 {# t' a3 t% O0 d
    3# r3 ~* B2 e9 ?, X; Z
    4- Z' |% ~$ \4 U  j
    5+ T; ^% \' C- d( [$ r
    69 e3 W9 z, [3 V2 a1 c
    7
    4 v0 p. L9 F! i5 y/ K8 F7 b2 l8: C+ }1 A5 ~% K* q) H
    可以通过apply方法自定义处理函数:* j7 ~( t/ l) ]
    s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差( a6 _$ j  L2 X, q8 j
    3 U- I. l+ i9 _3 ~) R1 U) E
    Out[118]:
    1 W# H2 W+ _2 c, y9 ], f* f2020-01-01    3. w1 C& x  L- D
    2020-01-11    45 a0 }! v) J) x: ?1 L8 r
    2020-01-21    40 W2 T. l- E" ~4 @% Y4 d
    2020-01-31    2
    9 U: R$ t6 l3 y- ?2 ^* O2020-02-10    45 M% P# F0 K' R( I: V
    Freq: 10D, dtype: int32
    / Q1 c4 s6 E/ M) d1 Q1
    + c( b+ l/ D4 Y4 ~0 x21 }: S& K5 b/ x: W) B/ f: V
    3* `0 T4 m4 W9 V5 Q. @" J8 S
    4
    1 D# `5 E: J6 G, O( k+ H5
    + h, u9 o/ X1 v! k- P! m6/ N9 f& ~3 e' B) K
    7
    6 }4 a' N. E, y% [- i$ H8
    & S7 {6 O- b; N$ v7 G9
    ! W( K4 f( o( l. \3 q6 T  在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:8 ]. J- t2 ~$ Z1 t: q
    ( I$ x5 z- S0 t" e9 u$ K  m. Y' U
    idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')% D; J% l8 ]; G: M
    data = np.random.randint(-1,2,len(idx)).cumsum()) p' ^- W% h0 o! y7 P+ X+ t& W* L' @
    s = pd.Series(data,index=idx)" c# T9 H0 q4 }
    s.head()
    ( O4 ~- ?4 w7 P1 a1 y$ n& X  Z4 W  V; m0 s
    Out[122]: * [+ R" Z9 _% w. g# Y
    2020-01-01 08:26:35   -14 I% v+ v  J$ ?/ P3 W
    2020-01-01 08:27:52   -1
    / T2 X. g3 T8 I0 ~: [3 g" P2 J2020-01-01 08:29:09   -2
    $ L" ~  H, V' K9 S, C" G2020-01-01 08:30:26   -3: t& q- f( D1 o4 t+ r
    2020-01-01 08:31:43   -4
    * W1 g# l3 B+ J( c# M# }/ {6 \Freq: 77S, dtype: int32& T6 ]/ ~: W7 u# I; [4 ]. ]
    1) R( i; ?, k; Z/ x3 }0 `5 S- z" b
    2
    ) J: ?$ \( @% {6 c2 X3
    9 A& `+ e  O6 F/ h4/ R! s- X% e/ F0 H# ^4 [6 N) J
    5
    # |7 m5 B4 A5 }0 ?: z8 G9 c6
    $ u6 K7 V% v5 j7
    ' M* L  E+ Q" A. i8+ x' J& Y' P, ~  ]- x5 A
    9
    " p$ Q! H4 j+ }8 n/ |! W10$ M* h( K' B% Z4 k1 R% i& _
    11
    7 |# Q8 j6 T! y* [; t1 W* ^12/ D+ |( a! @" K( o. l1 F. K, S$ ?
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:6 m' y8 `) ^( Z+ g2 q  f
    + h! r  a$ e9 i
    s.resample('7min').mean().head()
    $ b6 B( G4 i+ Q% oOut[123]:
    & j8 w: V0 ~+ r, _2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值
    # z) H# y, N/ L9 Y( V2020-01-01 08:31:00   -2.600000* A4 \) A& X' U% Y& @9 z5 H
    2020-01-01 08:38:00   -2.166667
    . R: W% k  y' b7 @/ d8 B2020-01-01 08:45:00    0.200000# P  ?$ K6 U" e
    2020-01-01 08:52:00    2.833333
    . i9 {6 F+ z6 O$ @Freq: 7T, dtype: float645 \, V) X1 O  N. s1 K2 E. |
    1
    : n) Z4 n& E  q5 d& ^$ M2- o4 _, ?9 D+ j1 X
    3  ?; |# e; Q% E% ?
    4
    : d* s+ g) ?+ t7 |56 \8 H; o/ M  G, R6 z# ^* b
    6- M" Q8 V( t, p& k7 T: F
    7
    9 s' |' C  E, U7 d* Q# @( p% d0 C0 J8/ U8 i9 O  g" N" c
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
    ( D# J; x: `+ u( G. l0 [: i# `/ \3 D3 y/ E- Y7 o  N
    s.resample('7min', origin='start').mean().head()
    . _5 C& j7 D: r) C3 wOut[124]:
    * Q) b$ l" y' \0 l, F3 r. e2020-01-01 08:26:35   -2.333333+ p5 |) L0 z0 D. i8 C+ B" ~
    2020-01-01 08:33:35   -2.400000% [; t( l- y: I0 Z! \. H
    2020-01-01 08:40:35   -1.333333
    4 y  A7 d' F7 U7 r7 q2020-01-01 08:47:35    1.200000& O1 M* W. n' \% f
    2020-01-01 08:54:35    3.166667! Y* W" }* v: X( @: p
    Freq: 7T, dtype: float64
    ; `: m" P; N# r1, R2 L1 v! h5 x, b7 V, F
    2
      y  J% t' C/ S5 z1 ?5 h" u30 R1 Y1 j& ]9 r2 }7 }, {# k
    4
    % l. i; O( G! }* h  D- S5 W5
    * ^1 S; w3 T2 H- [* Z# _6
    ; P+ K/ a: W- M  L: W7. a, A3 Z( b, p9 Y
    8* E! n' ~7 Z- r( a# \% U
      在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
    7 r  z; Y, v: ?8 `0 O
    " h) B3 [" b3 u7 `6 ?) Z0 h; {s = pd.Series(np.random.randint(2,size=366),* s9 w2 ^& Z) r& ~
                  index=pd.date_range('2020-01-01',
    ' f1 i. D9 p3 K' J) L                                  '2020-12-31'))
    . I5 X  f) [' G8 Y+ U
    ! Z* Y9 n8 X5 s4 |  r
    ! a, n$ \' k) b; }7 bs.resample('M').mean().head()
      v& `8 F$ e. U/ `Out[126]: 6 U% s& t/ _" [8 z' F8 X
    2020-01-31    0.451613
    9 ^# N4 g" K9 S# {) p/ ?6 j; g2020-02-29    0.4482766 E, @3 m& _6 K+ |7 q5 [
    2020-03-31    0.516129
    % W2 [8 k" N# I2020-04-30    0.5666673 Q& j; d. V$ c. \5 c) m0 n. F
    2020-05-31    0.451613
    5 O. P  b& \2 sFreq: M, dtype: float64
    & M% [* W# z0 T( V5 q4 d  N6 F! d' H% j: M/ O2 M  y
    s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
    0 h- c: t& l: ~/ BOut[127]: + f& n0 Q) @6 V& a( {, v
    2020-01-01    0.451613
    3 T  c9 ]( P1 }0 H" I  U- v! o  q2020-02-01    0.448276+ Y4 _9 S& m; g! v
    2020-03-01    0.5161292 y3 P4 \9 j" A3 o! v: j- q3 p7 j( x
    2020-04-01    0.5666679 @; P1 V6 m' \" ^
    2020-05-01    0.451613: c4 a. l7 h1 p' U  r! Z
    Freq: MS, dtype: float64+ q3 U. J8 g2 `7 E5 R# J% J* {, t
    1 U9 K/ t; g/ o4 r7 p
    1
    ' }5 ~; w" w" O0 v  a/ j8 S% G- i2
    . y; a9 W- ?; }8 O( A9 o. D1 |3) p3 M6 J; H9 P/ h% V( S
    4. X9 D2 C. b7 L1 G' v# X8 J: x8 U
    5
    % ]5 _' l3 u; A0 ^2 e6* E$ n7 U7 x' K. V) W" \
    7
    7 z% e3 q+ K7 U# n9 o6 @1 a8
    : g, G* V( s$ M9
    ! N* z/ _2 O& S( @! w10
    4 Y; m, a- W* g/ j5 I8 y11
      l* Z2 H. y) Z7 Q12- V9 }8 h8 p& L4 ?3 g9 ^* V! f
    130 N8 J- a3 Y6 R. A4 u9 i" |& A
    143 O+ V$ Z9 U+ ]! y1 `" W
    15
    ( Q4 ]* Y" [* y' I16
    # D$ G5 [$ i1 N- f% p! Y7 J) @17' C4 a( N  t1 w5 Y, T, w' H9 a# s
    18' ]1 M' D4 k/ q# G* s
    194 \' U6 {7 M5 T" x: H7 o$ k
    20# I$ ?0 d5 b% R3 y) G5 O' Y" S
    21
      a5 O$ W* U" R221 t& w. ], O- E1 M
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:3 u( d& T; K& J6 Y
    d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],8 o) r" |: u/ a! O% [4 e' [) q" ]  Z7 r
         'volume': [50, 60, 40, 100, 50, 100, 40, 50]}! s8 ~8 X/ A, T8 C* ~! F
    df = pd.DataFrame(d)/ ^: m3 |- a5 m: B, T9 j
    df['week_starting'] = pd.date_range('01/01/2018',
    - i* O9 b+ V2 p# G. s0 i7 i0 V                                    periods=8,' g5 I/ Z; H  [* T+ W' u
                                        freq='W')# \4 k/ U2 a( P2 N
    df& s: ~0 t6 ]9 k  R
       price  volume week_starting1 W1 E( o' ]( D0 L% _: r
    0     10      50    2018-01-07
    7 y) m8 X- E8 M' F6 n5 l1     11      60    2018-01-14
    % D: X8 Y+ H: `# _2      9      40    2018-01-21
    0 g) `) A4 p. W8 A+ P9 a3     13     100    2018-01-28
    7 v: R0 p1 o5 g* c1 y5 n( {4     14      50    2018-02-041 i" Q9 D. \& `+ `
    5     18     100    2018-02-11
    ' C$ ]* e0 W3 s- }+ @1 X8 \; v6     17      40    2018-02-18( [, V' E5 b& l$ D
    7     19      50    2018-02-25' m3 W5 H9 K# M) K$ N
    df.resample('M', on='week_starting').mean()
    4 F( F7 \7 t( S               price  volume7 N( x; C" r. ~* U! d# B2 }+ M! D
    week_starting- D& [, ^# [$ [, e) _( P9 _- h; ~
    2018-01-31     10.75    62.5# {: m- R) F! i4 L! d+ m
    2018-02-28     17.00    60.0% i, V* @0 v2 Z# S

    0 n' ?, J7 Z2 H9 L11 t* a' d- L1 I4 v* |) s! B
    2! c# U% z" u" j( N* U' t2 E% B
    3
      l2 u3 y3 R5 }4  S% E8 q+ ?2 @& u
    53 n+ {* K( T8 W& u9 f
    6+ N. T9 {0 C# X2 `/ N+ q
    7( T' _; w' J' X7 S* r
    8" Y5 `" M7 B' A* V" K! x
    9# T- x7 m: l2 l1 J/ b' @
    10
    0 Y& G) e3 ]8 }11
    + C( |) D1 t5 l/ r4 A129 E8 v& L5 c) f/ S, F
    13  ?: c+ u/ v: P% l* C' r0 [
    146 R4 ~/ N$ Y( m1 U0 w5 e
    15' {8 p6 `( ?8 J, h$ b
    16
    6 T$ v$ ]( S8 T) a5 {+ a1 p$ N1 k17
    / n* f: A& A1 N! ]7 M18
    ( G7 d$ @+ j. M$ h9 L8 Y( R0 _19
    # d( z' j+ c$ g6 [9 {7 O. i20
    , }) l: I- C' L: G' {0 t& U21* `/ ~; `4 ~. h
    对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
    9 F# z2 D7 d# W' }days = pd.date_range('1/1/2000', periods=4, freq='D'). `2 `, Q. B6 Q" J# A- F& I
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    % q+ D& n( h/ K: [! T      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}! B9 k% E( |. O: p! N9 ?* @( h- O
    df2 = pd.DataFrame(7 @4 N# {1 f* d' f
        d2,
    % j3 c! ^3 O$ S* B; K6 T! t, q    index=pd.MultiIndex.from_product(
    / F4 \" o8 E/ \* P  s- o  h        [days, ['morning', 'afternoon']]% T! ~) U8 |  J0 |+ d. p) X' ^
        )
    ) k) ?2 m2 O* q' U- H)" p& A6 y; H5 t. A7 c# N. {
    df24 @8 U% g" g" p
                          price  volume
    , T' Z; f: d7 L- E  h+ Y; F2000-01-01 morning       10      500 T! f! b- B" J9 ?8 F* x
               afternoon     11      60
    8 e, Q) J5 y- X% e# f+ s/ ^# Z* N+ q2000-01-02 morning        9      40
      P; A. b! c) p8 U# B: r! q6 _: N           afternoon     13     1003 O. U' ], p( p# O$ `1 B
    2000-01-03 morning       14      50# o7 P# ~  X6 x
               afternoon     18     100. g* e( \, E7 C: H2 k. y
    2000-01-04 morning       17      40
    ' W9 P% h  ^2 u4 y- x           afternoon     19      50
    + \( a. W% c/ w: H$ Y( edf2.resample('D', level=0).sum()
    " @' }$ w. K- {$ T7 S  A            price  volume5 D; s* V! z5 V7 Z: c. `
    2000-01-01     21     110
    % g& \4 [, b: S/ w2000-01-02     22     1405 N/ [7 F/ o$ O$ U( t
    2000-01-03     32     150
      w4 _# L4 V) y3 o: J; K2000-01-04     36      90# @( m) Y# z  Z" q- L

    & P+ J7 I* E( _0 ?6 N2 ~# a1
    4 L2 ^! h5 }6 ~# b6 y# Y: F6 ~& ?2  M: z' S7 [0 `8 R5 h
    31 z: L2 @5 x! P
    4
    : J: i% S* o5 q; V+ y- Z59 H8 N, a$ R9 Q$ [3 `  f7 _
    63 o, ?$ x7 }7 n+ G: ^5 s
    78 t. r( _5 `; M. p' c
    89 U- J( @9 f& H5 Q1 B* K. I
    9! y& g% ~( r2 ?! m
    10* I( i9 _: S! X2 [: @
    11/ g; S% d" Z# g# k+ q* C" t
    12" c: W8 ]* A% Q! L7 y# Z
    13
    ; Z) D1 C/ Q- {- h4 @  R( m! I147 }+ i4 I/ P' w8 ^7 H4 R
    15
    1 T- {) T3 x0 B) Q$ q7 A16$ U, ?* |- Q; V. o
    17
    6 t" X( h- C3 j0 m' X9 P18
    % i$ l* z# G5 ?; v/ B6 o# A193 Z$ k) Z0 C9 K3 f3 Z
    20
    9 `/ m+ c/ o" E21
    , E1 W1 w& R  b; S227 c% G( f- P  c* l: o: Y* J+ ~. g, J
    23: ~& F# @+ L1 U3 C: U+ ^
    24
    . t; M6 |0 G$ `  @1 a9 H- B; O25
    & E, W( V( b* [: d* \% b+ h根据固定时间戳调整 bin 的开始:
    + T0 h7 q8 l5 w! ^2 E' astart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
    6 p$ d# U" j6 Irng = pd.date_range(start, end, freq='7min')0 N- N: l: }# w9 F* |
    ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
    8 G; h, I; @% U: p/ T6 _: rts2 n. u5 B. z4 C6 V  R7 b
    2000-10-01 23:30:00     0
    / f: p) ?; m8 F- z/ I2000-10-01 23:37:00     35 X4 y' q2 {5 f* v2 @9 ~+ a
    2000-10-01 23:44:00     60 f/ K" O: \+ V6 G; h! i
    2000-10-01 23:51:00     94 m  T6 A" {) U3 D
    2000-10-01 23:58:00    12
    & ]0 {, \5 f* U2000-10-02 00:05:00    15
      V+ T8 C/ p! ?: R, d5 t; [, v2000-10-02 00:12:00    18* |8 ]* S( [5 E" t# e
    2000-10-02 00:19:00    21
    , d/ [. L: W) F6 \5 Q& u. N2000-10-02 00:26:00    24) B' y" P! ]( B* M( c: w
    Freq: 7T, dtype: int64
    / l: Y' _6 f+ |, x# }5 `' E% S5 q9 H% H) o
    ts.resample('17min').sum()
    5 I# u( Y* s+ K% \8 ^  O8 `2000-10-01 23:14:00     02 G1 P3 T8 q4 J# r8 h
    2000-10-01 23:31:00     9' b) a+ G: P/ L8 P
    2000-10-01 23:48:00    21, x0 ~$ K: ]2 E9 B
    2000-10-02 00:05:00    544 f' W* b; [' T3 T
    2000-10-02 00:22:00    24% W, J4 }- ]* ~6 x" S5 L
    Freq: 17T, dtype: int64
    / ~% h4 Y7 G. U7 ~" v; ~$ C3 [# B) T( M, j: ~$ D3 l& [' _
    ts.resample('17min', origin='epoch').sum()
    ! d9 w' @% A& s. N( \6 v- r' a  C  y2000-10-01 23:18:00     04 Q' A' d$ @1 E+ g# @/ u; Y
    2000-10-01 23:35:00    18
    1 {  W$ y0 ?0 w0 t8 g# H, j  I- H6 q2000-10-01 23:52:00    27
    + S8 Z4 I  a( J5 j0 W! p5 I2000-10-02 00:09:00    39
    8 R9 [, _: |* N+ S1 e6 T2000-10-02 00:26:00    240 m5 [2 Y0 G' W* L: I( u
    Freq: 17T, dtype: int64! T' ?" b' r  [3 e0 _* Y. a: F
    % B7 Y# ?+ Z* {8 n4 |$ E
    ts.resample('17min', origin='2000-01-01').sum()
    + Q/ q, ^- H7 `3 {2000-10-01 23:24:00     3
    * j$ E/ u  ^% ^) w2 D/ z2000-10-01 23:41:00    15
    + h) d( f2 H: m( n2000-10-01 23:58:00    45
    7 m* P& [& C8 a0 N  b# q4 H1 M2000-10-02 00:15:00    450 h4 _& ^9 c( R; r) z
    Freq: 17T, dtype: int64
    ! L2 F0 F: E: H, R5 n" g! N6 W( u  v4 T/ }
    1
    , ^) e+ X& e! A' d' W2/ c7 D7 F' w, ~) P8 d* ?
    3" ~1 z% g1 x" @, ^& d
    4
    3 |& I+ L; V2 ^  y% g+ D  ?4 |59 g8 m0 r3 ]+ G& G* B
    6
    8 |! n) Z3 |& N( v7 [& t7, n$ q9 O- E/ I) \
    8
    " P7 C! X9 h. A6 E9
    7 f( t7 _: i! b9 R2 F105 b; r  \, i& j
    114 O) J9 H7 ?) ]) ]3 u! i; q
    12
    & W$ b! W. ~8 p13! Q2 B$ t# F2 n3 y7 ~
    14
    7 ?0 @; n3 _/ j1 V15
    % C. Y1 o: V% A7 H1 R16
    5 _. j% ^* b! [9 l177 F" c% n' v; r1 E
    18* o7 o4 R/ k2 J$ q
    19
    " P+ C: e7 p( e* F2 [20
    & X6 O" ?% s! C3 K( o' X21, ]5 a8 z0 b/ B# t/ W' |6 s4 m
    22
    ' @$ S! `% c! W6 r# O5 _23
    , s( U; H9 A4 R$ G! ~24
    2 t' w0 j. t# b& O* r25$ Q3 S6 |1 ^, ~" ~3 I7 n3 K
    26
    4 {; i, ]( \" ?, J; x" `# O0 k27
    5 F, k* J+ o, i" B28
    7 f1 B4 I3 e8 X- A7 q2 [29
    # x- [  b9 y+ [6 v& x9 m30
    & }1 e/ ^+ V( t31$ L+ e8 m+ S- s7 k9 x
    32+ i4 j; m. ^8 m
    33! k6 e* k4 i1 E" S* n, @
    34
    - h9 W+ P- I, ~35
    9 ~  H) h. g9 J& j" Y& A36
    7 M) \- x1 j9 I37, g0 R# X5 ?8 B; D  ?# B
    如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:8 @$ G' f2 l' R. h" H. ^, a/ k0 l
    ts.resample('17min', origin='start').sum()
    , J) V, t! N5 B' `1 Q- e. W5 Ots.resample('17min', offset='23h30min').sum()
    " v6 U$ N) |3 M) f; l2000-10-01 23:30:00     9
    0 u+ K# N+ r' b% c9 c1 ]2000-10-01 23:47:00    21
    ) h2 X' D( v( H8 w4 f2 U% S2000-10-02 00:04:00    54
    - S7 p0 i7 `! v7 r" G. c5 ]  z! I5 [2000-10-02 00:21:00    248 P3 _2 _) h0 l5 L! g- O
    Freq: 17T, dtype: int64. i7 s; M9 }& v2 q! P& q8 Q# x
    1
    1 ], B0 r3 ~9 ?2
    4 l  |' j( {' S' t36 y1 v' x% w! H* u- h3 h2 Z
    4. C0 [! U$ `* {+ k/ A
    5
    $ T0 P/ [! ~! B7 d  ]$ L' k) P% x6
    7 A( J: E8 u, f1 ]7/ w9 X0 x& W# j
    10.6 练习9 T- {  ?' a0 x- b: ?) w3 e
    Ex1:太阳辐射数据集: k: T- A' U5 A1 K, |  h. i# A
    现有一份关于太阳辐射的数据集:
      x) ?6 @* a; c3 o; Y! v/ S8 e- c
    df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])- y7 F2 p) `2 L/ N% s; y
    df.head(3)
    6 S, J/ H2 j0 p) f* p& V" {- B% p( r- P# j' `4 r
    Out[129]:
    , f) X# w& p- D* R' _9 x" c                    Data      Time  Radiation  Temperature! }" c0 I$ R: B7 _2 z+ ?
    0  9/29/2016 12:00:00 AM  23:55:26       1.21           485 O; b& P9 a5 `/ a4 B1 D& Y
    1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    5 E; q+ _6 J2 ~+ J6 ?2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
    - r3 x, f) R; l4 y! C16 e& k/ i' \# f+ Z
    27 P3 L9 v, |: v) }7 P- ?# t- G2 E
    3
    ) W$ x8 T- [, A* A8 Y4
    4 A% ^# p( P! z5 I7 y( D, ]5( O* m! a( y7 u3 c. F7 P) J
    6+ v9 ~7 x. u& G' I/ w+ L, q) f
    7
    # I( T* p' D3 R( Y8
    3 r! J$ [  R9 b2 u2 a* B. `/ f将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    % k5 U$ l; U$ V. k8 v每条记录时间的间隔显然并不一致,请解决如下问题:
    ) W! g" c, H9 t$ |9 S9 w0 g找出间隔时间的前三个最大值所对应的三组时间戳。6 v+ B* o* T0 Q  X  X
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    ( J( E! r4 ^/ u. j求如下指标对应的Series:( l% d/ o7 M" a  P# L4 J
    温度与辐射量的6小时滑动相关系数8 q& {% [5 E" w" n  g& D
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列% O* t  y" ?, v: S( z# h
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量). F, s( B! X  m! K. S  P
    import numpy as np7 [- l( D' ]$ v2 D
    import pandas as pd+ u% E- ^1 x6 S( D/ O7 e
    15 V/ o% S8 x9 d& x
    29 w* ~+ e& [4 y$ w
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。, k# G1 k6 O4 W
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
    1 r8 p# }' T% l% v5 Gtimes=pd.to_timedelta(df.Time)
    ) `- e6 E6 l: d$ Y* ?% S+ `df.Data=data+times0 t: U1 e- e8 n2 p
    del df['Time']
    3 I+ n( @0 o/ ]1 Q# B' bdf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
    ; R% h2 L0 p) S. M# t% U$ \) idf
    7 i+ M5 A2 ]! Z% e/ R$ i                                        Radiation        Temperature
    % E( F6 [, x5 aData               
    4 l) {' Y/ o0 j6 k- w: o# W2016-09-01 00:00:08                2.58                51
    2 j) X/ T$ s7 B9 R8 o' r2016-09-01 00:05:10                2.83                514 b: x$ t+ U! E2 v6 ^5 e
    2016-09-01 00:20:06                2.16                51
    ( c9 U* I2 k+ r9 H& \: s2016-09-01 00:25:05                2.21                51
    9 B; H( c; i( `* u- |1 l0 `2016-09-01 00:30:09                2.25                51
    0 H/ y; c) l% A6 ^0 b" p5 L...        ...        ...
    , y  l8 P: d  q  i% U2016-12-31 23:35:02                1.22                41. a& g% i! R" G+ B
    2016-12-31 23:40:01                1.21                41
    7 J8 _" Y: X: b& X2016-12-31 23:45:04                1.21                427 V% o: n; D: }! a6 R2 x) C& G; @3 J
    2016-12-31 23:50:03                1.19                41% B, @8 `0 s# J  n  v
    2016-12-31 23:55:01                1.21                415 X( c! L" `9 y# Y
    3 h! ^1 ~' i; g& I- @* s$ m
    19 g% c% @2 u3 O4 D9 s: t7 N5 ~
    2
      u" t0 N9 |  g) [6 @% }. |3; C7 C4 U. I; l+ r# }2 n# n
    4
    1 |7 O+ f, s) `0 v) R; i5
    $ w; `7 |$ h) X) `7 s3 [$ w6
    * o7 b# `* g+ x8 [/ A7
    . t9 j* r% L( S2 R8 a% {8! i5 X1 }8 [6 T$ Y% H, V
    9
    / x1 w; |. ]' @  _9 ]10
    : C- |( P2 ]  i/ ?4 s) q" T( g* T11+ e) }9 {& b( r! h; T+ y3 M
    12
    8 f. ~4 {5 h* B5 N1 a. p% R13& B0 k3 o6 p: i( q& ~- F) x, G0 [
    14
    ( _& Y/ J# b, K, Z' Y15
    * E" q. f* L5 D& t$ a; O160 o) G+ a2 k9 y0 C; u8 @
    17
    1 e' v7 S: w9 Q3 J  h18, c0 G6 c( E# w  m+ ^& U  P' I( u
    19! M" J2 v( A) H; O1 ?  b
    每条记录时间的间隔显然并不一致,请解决如下问题:4 I1 A3 b4 W0 b
    找出间隔时间的前三个最大值所对应的三组时间戳。# R6 z, P# L* \' a. _9 ]
    # 第一次做错了,不是找三组时间戳
    ) W- B# J, j/ l! ]idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]# }0 l# P6 a) @# S# i, v9 C
    df.reset_index().Data[idxmax3,idxmax3-1]
    ( b! I# m1 C0 P. A- N  z
    2 ]- B& {# @0 W& n9 ^8 L25923   2016-12-08 11:10:42
    + K' U1 g1 D, \+ b8 f) M/ Q4 I( W24522   2016-12-01 00:00:02
    $ {$ h. Q9 V+ x( U7417    2016-10-01 00:00:19
    ' @6 F  v! h$ y! sName: Data, dtype: datetime64[ns]! q* C$ k, o" F0 C
    1
      I( O) J3 Y! k0 i20 h8 w" L& d) y# q/ C
    3
    5 h3 K9 n: f) X' s( I: l4( O% z' `) P( B9 o* [2 l0 [
    5
    , |  m) G) }6 C$ A. a6
    6 S5 d% r" G! H  o7
    # |; n9 q& f8 l: Y# o8
    6 D! C, T% h# [1 p3 \+ J& A4 h: {) ^idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    $ L' j" g8 s, w  \' hlist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))5 f+ e3 _" L+ C, W+ o$ G9 @4 l, r
    7 F3 E' v' V9 x7 f
    [(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),! P7 W) g* t$ [9 L" k
    (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),2 z) H* v( a" G: U. \4 f
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]0 |6 Y! w7 F9 P
    1
    ) k! ]# }; p" g5 z2- D0 [, K2 L" b, U; m
    3
    # X; Q  X- [% p& f3 ]" ]4! q" R3 G# d! J
    5: x7 f6 _+ t) K& s" q7 x
    6
    & `! g7 d9 P7 u, o6 f参考答案:
    ; D2 ~) T0 o7 m1 \2 H! |6 F
    . M' N% q% W4 _) Ys = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
    ! Y1 q" \( P. ~max_3 = s.nlargest(3).index/ U, U- V; A% r5 s( Y
    df.index[max_3.union(max_3-1)]
    3 M7 V1 t$ v9 ]5 c- M. g' p2 M$ z- U& O1 D; a
    Out[215]:
    - P# z2 q1 a" XDatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
    / A  X( X5 G8 O! l               '2016-11-29 19:05:02', '2016-12-01 00:00:02',
    / Z; n( w6 `  p' n               '2016-12-05 20:45:53', '2016-12-08 11:10:42'],/ ^& s% ^8 J+ I$ m
                  dtype='datetime64[ns]', name='Datetime', freq=None)
    # \8 ?$ E4 J. L0 q1
    4 y' i: N' K& M8 Z/ \3 S4 {2
    , e( W$ j2 a& n* Q3+ f. ~+ A, {; i# O3 ^$ k3 G
    4
    + U8 Y" E8 N4 L" a* V  u: d4 ?4 \5
    ' W0 h. ~8 {9 E* S: o4 e, g62 U4 G  g- V3 h- J% {3 a% `8 P
    7* M1 w+ p" K2 t/ q) g. w5 E9 W  r
    8+ h; R1 H& ]9 {& S( A0 o
    9
    ; e2 t" B: |1 h/ f2 N; B& k$ o是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。$ \  _0 ]1 ^6 {3 r; p
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间' C/ p' l0 j: K1 w( L) w9 x1 f
    s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    9 h7 w. r8 G. L% vs.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)" T" m- a, m9 h. j( w; W# T

    / j7 U# x2 u, n9 U" Y7 W(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)0 b  B' r# o9 O4 w; Z4 T# X
    1
    - |# [: P6 O. q- Z1 ~2/ o+ Z! \. j4 F( W4 v+ H. I
    3
    * o: W3 E- I/ v# k6 O6 L40 \3 w% T1 ^3 |, V- C8 U+ G3 v
    5
    1 K) [1 ?& L( ?8 {, u7 X+ @%pylab inline3 p1 B9 O; x9 E3 x
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    - U' f9 ?2 z) q& r  J  Q: lplt.xlabel(' Timedelta')3 w$ ~( i1 e% k. O2 o
    plt.title(" Timedelta of solar")
    % d1 N" h1 P8 F7 G- B1& T  w/ f/ h5 D1 w  {2 B
    26 M( W- K% I$ a5 N& T
    3
    . r& A$ S( P% h1 b: N! U42 d, q) u  {5 q6 x5 [/ E

    ) _6 S" X, H" H7 B$ |. R! L
    . C( ?. F5 s3 f, g: Y* h- ^0 T求如下指标对应的Series:4 K5 z! k. p( r8 h
    温度与辐射量的6小时滑动相关系数
    % W- i! Q3 T5 [3 `; D. {& e6 t以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    % u- |' E' Y5 a6 X5 H9 F" t& Y* I每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    * I& O5 A& n# G& G; I) C5 Fdf.Radiation.rolling('6H').corr(df.Temperature).tail()2 T4 V1 X! E# \. m2 E" _
    - V) w8 K1 v& `) M) R, W
    Data
    / Q: e. s. f  O2016-12-31 23:35:02    0.416187; v0 d  R/ I# j/ Y. @; b! U) L" R
    2016-12-31 23:40:01    0.416565* J4 O/ U) v7 T& O4 C3 D# t
    2016-12-31 23:45:04    0.328574
    : ?7 r, r3 z3 m( L1 e2016-12-31 23:50:03    0.261883
    $ O# m) V# t2 t0 r2 {  o2016-12-31 23:55:01    0.2624066 C" |1 j9 I8 a
    dtype: float64
    9 I/ Z% o2 }1 m: K8 j+ z9 j1: ?; n4 i2 ?3 G3 k' f# E$ Y9 W
    2; c* x+ N% W5 W
    3
    / f( B" r" k; V9 I1 s40 Z# \! e! p0 x8 w) ]/ T" D+ j
    52 C7 u4 s- C/ ?4 y. o( X
    6  P. }) Z* x# m) U2 @
    7  f7 B6 n# H: l) y9 G' I9 j) z
    8" N( U) f3 f2 D& N8 Q/ c
    9
    ! M6 U7 \7 l% O" y$ G# gdf['Temperature'].resample('6H',offset='3H').mean().head()
    * B/ W' S, Q2 n" d2 k4 g8 Q" g- g8 f9 E7 U. \5 R7 @" z
    Data1 {. X; _  g5 @: Q/ G, P
    2016-08-31 21:00:00    51.2187505 B& d. s  r6 [5 w
    2016-09-01 03:00:00    50.033333' `8 z) e2 \, D
    2016-09-01 09:00:00    59.379310
    9 d- s, t0 \3 A. _7 E2016-09-01 15:00:00    57.9843752 d4 Y4 B& [2 c, C/ s4 K3 L
    2016-09-01 21:00:00    51.393939
    1 i- \2 q, \1 f5 o% }Freq: 6H, Name: Temperature, dtype: float64
    , c  p# V. w  S# _9 _1
    : v; P4 Q7 G6 a: p4 I0 \* j2: U" {& N$ ?; {
    3
    $ K6 x+ Y& p3 t1 B/ B. t: e4+ N, j- M/ y- N* v
    58 U" u& \" v* y3 k2 b3 x
    6% g! L3 z4 Q6 }# ]4 ~
    7
    6 G. C2 x& L" v8
    % k7 ]& J  O9 T' M( n, J98 o9 H) C1 P" T( a( M, c
    最后一题参考答案:" P: M, |  I) i) S- m
    ! B" \+ ^& ^' J9 a! d% @6 b  X
    # 非常慢, |) x2 ~5 L+ ~  W, B  E  }
    my_dt = df.index.shift(freq='-6H')7 J; ^; H1 I) U
    int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]: O! |/ m; c) m+ R* L
    int_loc = np.array(int_loc).reshape(-1)
    ( s6 k2 A4 l, w6 y4 ]& x  xres = df.Radiation.iloc[int_loc]
    . a, o% u0 [% G  _* p* w1 M# Q9 s) Sres.index = df.index
    ' R, f5 p) ^; F3 t5 @- Z6 K9 P7 Wres.tail(3)
    8 S$ }& N7 U& M) N' i6 \1
    % A! s& _" V! }* m9 Z2& O+ v8 k; \2 }
    3
    7 \9 ^# |  ^; [4 a6 K; m4
    2 p' l/ |3 n+ u; ?, [5
    , ~1 ^7 }  ^- I- k* }6
    : g2 E' v; g: r8 E7 j, ]- l5 x1 L7
    * L1 W! r' `0 I9 J# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    8 Z" v6 K' R' `target = pd.DataFrame(. b) w+ Y% G' J# U+ y9 v" h$ J
        {7 x  c  h6 z5 W; _
            "Time": df.index.shift(freq='-6H'),
    4 C/ M. _" A+ ?/ e        "Datetime": df.index,! a0 L" D! H: b) b" I( ?9 |
        }: ]' p7 i6 K/ X* J! P0 \) w1 K
    )
    6 V$ K: o- G6 {0 d: @5 T( E6 e& q* u
    res = pd.merge_asof(" H. |, N& D, U$ N/ [
        target,/ [2 K  |( g9 ]5 [
        df.reset_index().rename(columns={"Datetime": "Time"}),
    3 x* {" X4 \! U; P% x; K    left_on="Time",
    $ |) X$ f: Z0 d) _) Q% p    right_on="Time",0 I; e  J/ H( A+ F% b3 ?$ o
        direction="nearest"& ^# X7 v9 x7 y7 G
    ).set_index("Datetime").Radiation. n) d$ g+ B$ m! i  T& j

    + c- r5 H! ^4 V' _  s/ ?res.tail(3)
    ; j' f5 [# x) J+ t# N& ]2 [% n( pOut[224]:
    # o# T2 L3 h, I& o3 \8 }Datetime5 h  X- k+ a  g/ X: \& M
    2016-12-31 23:45:04    9.331 f" H( y& G: z6 @+ o
    2016-12-31 23:50:03    8.49
    . D* A( ^  ?2 z6 M2016-12-31 23:55:01    5.84
    ; C% ^& c8 i7 {# OName: Radiation, dtype: float64; R( r0 w/ u1 t3 b
    , g" y) V* P* ?8 ^
    1- o. {. @1 T7 L+ i8 C, l6 o- p6 }
    2
    4 }, d9 x- e9 A3 g" b3# V$ j' m- o6 }$ ^9 R2 g
    4
    3 {0 z* x8 |7 a% O$ k0 W9 j5
    % k0 j+ T$ b! o! W6# f* A# o4 R& b( I0 v
    74 y0 p( Q% G5 O. L- T, K$ E
    8
      A# E5 |" M- e; h/ L2 U9- Z( q5 E+ P7 k; U# X
    10
    1 e5 _6 Q) B: [. y# y11
    " r+ J9 c3 N$ T6 \3 v6 ?12
    2 `9 ~8 l/ K4 ?, i13* K0 |& D, T: R; X) `8 I
    14# U6 N) v% G. j1 @' V; g
    15" D( o7 C% v. t4 E. O2 Q
    169 J1 N3 _7 D4 c4 [6 p2 `
    17
    2 E9 B  `3 \& j5 U5 d( p# J: d* S189 d  G0 m' l# S
    19* I9 m% q! R! U- T3 x" R; h1 O/ t/ T2 z3 M
    20( E; k1 e9 `1 i3 [9 D, x
    21+ v9 d4 e5 j: W
    22
    ! ]* f* e5 Q' \* E6 y/ b23
    $ m  A% i# c3 ?9 QEx2:水果销量数据集1 \0 |4 u# _3 l# F; h) L. r
    现有一份2019年每日水果销量记录表:: K9 R5 J; N9 L3 ^' b" o- X! N
      m7 O0 J. A& I- U- Y' O% }
    df = pd.read_csv('../data/fruit.csv')  {, Q* V$ y6 e' _2 f/ S7 E8 M! z
    df.head(3)
    ) j5 w# Y4 N9 v8 \+ M8 }$ d* i3 l  F5 W
    Out[131]: & @7 b0 T& A+ X7 s" {
             Date  Fruit  Sale
    2 \8 A4 Z3 G" z3 i0  2019-04-18  Peach    15
    7 r! r3 Y* C" M* R) J5 d; z1  2019-12-29  Peach    15
    : p3 n, R, |3 |: M2  2019-06-05  Peach    19
    / G/ V( ]% b9 g+ O# Q16 j) N; l5 s$ s
    2
    5 @- \1 i# i3 F' A& r) l0 o) |/ N3; D. a1 V- C# U/ }7 @
    4
    5 G' X9 |" n8 P1 Q56 @$ ^" S8 F& m) {% _
    6
    2 Y: T7 d3 {2 ]# x7$ v& h9 R' h" p
    8
    6 ?$ }+ \! `8 b3 T+ t! j; w统计如下指标:
    / Z; B! Z. ]2 a9 A2 G每月上半月(15号及之前)与下半月葡萄销量的比值* r+ b7 a  f9 d) P8 v
    每月最后一天的生梨销量总和
    ' X# l) f: x# a- L6 r2 x# |每月最后一天工作日的生梨销量总和, I/ ?" Y% @+ q$ D) Y3 ?
    每月最后五天的苹果销量均值' _7 y* o) N/ W2 P# ]( T8 O
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    5 N4 d# z( X+ _0 x5 M. u9 M# L按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    1 Z+ T/ Y! S6 x; W8 _import numpy as np
    5 f0 `, F7 t# }import pandas as pd
    4 P5 ~+ ]8 g! S" k1
    4 _: [( v: k( S2
    8 ~: L5 x! l4 B! ]3 z2 E统计如下指标:' e* ^# d2 ?  R! K+ ]5 {! R) O
    每月上半月(15号及之前)与下半月葡萄销量的比值, \3 \& ?. }% i# Z) S0 W$ a1 ]& d
    每月最后一天的生梨销量总和
    2 y0 }, r8 z0 g' m每月最后一天工作日的生梨销量总和
    9 l& V1 B8 @# P! }3 j每月最后五天的苹果销量均值3 ]( A3 k* b% P
    # 每月上半月(15号及之前)与下半月葡萄销量的比值9 s. T7 u/ Z' h+ Q- v0 f
    df.Date=pd.to_datetime(df.Date)
    4 ^" q0 O& `/ b, i9 |9 bsale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    ' T4 I& `6 L+ y6 msale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    ; a( d/ ~* V6 nsale=pd.DataFrame(sale)
    . b3 e, U" i) ksale=sale.unstack(1).rename_axis(index={'Date':'Month'},
    7 O/ |' ?) M8 C0 r: p                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名) a2 A, w: h4 I' }
    sale.head() # 每个月上下半月的销量
    + U# C, U; p( e/ F! a4 |
    ( a" d* j% l* \  [" b* A( g. e4 M  Month        15Days        Sale/ n& h2 H  m, p
    0        1        False        105039 `) r1 c( |: L
    1        1        True        123411 S8 o7 s# R7 _* g6 _
    2        2        False        10001" |: u0 r/ p# a* w' H
    3        2        True        10106
    " \# r$ x: w4 Y* Z8 M2 ~4        3        False        12814
      }% _: c3 |( @% y- ]* q
    1 N) y8 p# g5 s+ l( ~) s# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
    2 ?5 `: l5 H3 z- s% ]7 _8 [" k- G) G" jsale.groupby(sale['Month'])['Sale'].agg(
    ' j& C, @( i# g  s                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    & S! D( ^9 v; X- t8 \! r7 o
    ! Y; n5 S+ E4 E- hMonth6 V4 J# X% x: Y' g) K' v3 G* b
    1     1.174998" W, u$ l" q- ^8 ~& l  ^3 L+ c
    2     1.0104997 s, a# y1 c. O; ]7 A
    3     0.7763383 V6 D# O  Y8 c& v
    4     1.026345
    / _; P) X% E6 `) [5     0.900534# }" }$ S% I& {' o% O6 @1 `
    6     0.9801368 \% ?# ~) C5 ]" ^( v
    7     1.3509609 v6 X# R1 A& r
    8     1.091584# h6 h+ L# f6 I2 {$ Y
    9     1.116508  d) Y. r  f( o6 l0 l/ `# |
    10    1.020784
    ) d4 ^* m( k+ l% c) R' ?9 k11    1.2759119 K4 v8 G* i2 _* ~- p
    12    0.989662
    ; |8 u9 W2 g, P* U! L* s2 n$ Y% f; mName: Sale, dtype: float640 a1 U3 m0 G) [6 H) y, F

    . O1 q; }$ v4 M3 a, s1
    9 t, _8 {* M  H$ W9 _4 m# J- V9 i+ C2+ c# n/ C  {! H4 S2 O
    3
      A. X: @$ p! v1 |; P4
    , _1 H; c# O1 Z$ R5" R- g9 }& B+ z. B
    6
    ! L+ o- _/ x+ B7
    2 Y( ~0 [. h8 X, O& V83 @4 n" \1 N0 A. g
    9
    . R0 I, ]( S- I6 a10
    9 c9 {$ \# v* j* q" `% y11
    % c! I, _, K4 i" f12
    7 a1 {* q6 y  v% q/ F; J9 Z1 H13
    : F2 J& J0 S" G8 u4 }: Z8 h14
    + a" Y6 z, p$ S1 e& p$ p  W15
    & I) ^: B( m5 I7 E16
    " H7 s; b) ~8 R* S* y6 V; _9 t+ g! _17
    6 q* ], T: P  r18& T4 l3 q* e1 w1 o+ R
    19( x1 }# ^$ Q- @  r6 s1 p4 k
    20' c- s/ |4 c: |$ H, ]! _  t# ^
    21- V) X6 a3 t, h' ^
    22( X6 X: j. c! ?
    23
    8 P5 V& a# ^! v9 n: Q% o5 [. g& [24
    7 W! q# R" k  r/ B6 w2 P5 |, r254 q9 H' H% q" c, m7 v- I3 U
    26) S) c8 C$ s0 K; C! y% ]
    27. q% n/ C$ C' ~5 M6 J& _0 H4 B
    28
    3 G, f8 b7 K9 K29
    0 Q3 _# G; n3 ]  B$ O, V; |/ x3 D30
    - g) K7 r! }  w$ t) l316 {( ^$ f$ y! T2 g
    32& a+ s3 r8 I" Q
    336 x) a, v9 A" L' z4 i* }
    34
    " m* C* E+ D& n* ^# v1 K6 k# Z# 每月最后一天的生梨销量总和
    ( k# V$ Z0 ?1 K" qdf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum(). i. l$ D4 W& J" Y% d) l

    , n2 e; y7 g# s6 g$ u. d/ y# n1 b* IDate
    / P' D  m) T$ {7 u& g) f2019-01-31    847  W" H$ q# N+ P: t
    2019-02-28    774
    0 y8 u2 q$ h  _. z3 {( M; |; d; H2019-03-31    761
    # ]2 V) x8 ~; N7 b+ @4 ^. f2019-04-30    6485 ]8 o( p; s% d9 j/ g0 L
    2019-05-31    616
    0 g$ c3 D' j. W/ H  u" Y' c- F1& G) Y- @: k9 S5 Z
    2
    ; ]) p$ l+ W! O! t7 f3
    ; f, R+ E8 o. W9 f4
    3 E; ^- ^  ~2 G5
    ) h; w# s- m/ x. ~" Y# `; U: \% a6
    7 K+ R2 w3 A7 Z( W- o$ a% k7; O5 v! L8 H) L* h4 {
    8/ O2 h+ r& z6 [
    9
    9 y3 @; ?: |4 o; ^8 ]# 每月最后一天工作日的生梨销量总和
    * c' O( k& ~9 v3 U  Yls=df.Date+pd.offsets.BMonthEnd()" b$ H  ~  ?8 W; B7 }  r1 ?$ U+ Y2 u
    my_filter=pd.to_datetime(ls.unique())
    4 ?/ U4 D& `+ _& Z6 Ndf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    6 {, @0 p( h% B% U$ u
    " |% ^# G+ W. lDate
    6 ^$ x( o" ?% ?5 F2019-01-31     847
    3 `) o4 m% M0 B" @2019-02-28     774
    8 q% R/ A$ [# N7 J1 P" [# Z2019-03-29     510# @- b# n. F7 g: n1 a
    2019-04-30     648! Y/ Z: r% X/ x* p- O3 W. G
    2019-05-31     616
    " z' f5 ]4 c; |1
    3 E, y' B2 ^- @7 M. V2$ @. D5 n0 [; E% F- H1 P9 y
    3
    5 |8 o1 r# ]# W7 R; \4& T0 d; Y* G$ J+ D
    5/ B9 c, u% n9 ]0 M
    6
    / ^$ _) N" F# n3 c8 Q7
    4 v  U% N# j( r0 K. {+ y8 F8
    ( ~: e/ x/ A3 I9
    8 e8 i* `# K* A! n3 G: o10; y% }: ], d1 I$ p) W
    11
    : F8 p9 Z+ b/ N7 g9 s9 u$ h# 每月最后五天的苹果销量均值
    + Y4 c* i/ j$ u, rstart, end = '2019-01-01', '2019-12-31'  |4 G% F% q5 g' f) h8 p8 q
    end = pd.date_range(start, end, freq='M')
    # o; w$ J2 m  `& K6 ~* vend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差) x! T1 V, k+ i) x2 c6 I
      ]7 I8 z+ k; ~
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    7 N# Z% H4 N" w. d" rtd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天6 c6 h  p, W" I& N$ X; w
    end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    / \& r( i  M' x( n' T" u6 Q, J
    8 L$ y8 M  ?2 F3 F  ^" w0 Q6 tapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    ( T0 z% A- m$ M- i: ?apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()2 m' K1 S( w( a, x! d1 h
      k7 U" E# }" p8 i3 T4 D
    Date
      A. F0 C5 K, T3 M5 A7 @4 M1     65.313725
    2 w  o0 ]0 b6 L2     54.061538
    , W/ p% r  L$ y! t/ K3     59.3255810 \. G3 }7 W4 b  Y1 C# Q
    4     65.795455% b' V9 J- X5 M" S$ U; Q( M
    5     57.465116/ x& I/ k: l+ t0 m% e

    + j( k/ P4 R2 b- r6 I9 z1# Y6 a7 }6 [) c
    2
    6 Z. y& }3 f: ?6 ?+ O% j3
    ( D# z) k0 Q0 u, x- Y9 W" D4
    ; L  u1 a( k8 u8 {" |" Z4 m5  v9 C, ?/ S2 N) K
    68 F$ `: J: k% I5 S5 _0 I; j" U
    7
    0 t, Y  l* D- }6 x) V4 p80 k4 j7 D) ^3 P2 K# H" v0 b) I
    9' r& F8 V- l! Q3 X7 b5 p, f
    10( n0 F8 y! S+ r' ~
    11' E& l+ k( ~* k0 q; ?  ?
    12
    ! W2 L: X7 M* V, i" X135 J* @. ^; O$ _% l0 c
    14
    4 m8 Y7 B# d% O/ l/ `15
    , e$ [# g) B, O% U16" L; n3 F# r$ S9 r8 m" J
    17$ O- p+ g% F- @' W. g& o" _$ L# e# Z' @! g
    18
    " a9 t' H% _! x% _4 ^# 参考答案:/ l; n1 K; U  \* ]
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(# R& `* H( @, F7 h; r+ w8 B
                ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
    3 k  ?! H7 i% a  A7 l  ~3 q3 r# _2 D$ r& Q7 z7 Q8 J! W
    res = df.set_index('Date').loc[target_dt].reset_index(! E6 D+ B3 F' b* u' Y* q
                ).query("Fruit == 'Apple'")2 O/ P! ^  s. g/ X' _( _

    + W& j+ p# ]- h7 m& C% \+ Rres = res.groupby(res.Date.dt.month)['Sale'].mean(
    9 O! ]% R0 z0 \7 y            ).rename_axis('Month')  d( c3 U" k* M! m
    0 {) y1 ^, k  s* h3 W: [5 M

    + U9 _+ O8 b( d- l# Z9 kres.head()
    + G4 f5 B9 x1 Y" g% ^Out[236]:
    ' z* {, n, T7 l3 w6 ^% ^. p; j- |7 ~Month
    0 ^! o" B. A$ g1 n' F1    65.313725
    9 [: r  m& U; X1 U6 ]2    54.061538
    # Z$ E! f. C- o6 t* [3    59.325581/ d4 s$ n8 L+ f$ I4 X1 N3 w2 `/ t( t
    4    65.795455
    . [, ]+ ]. D9 d5    57.465116
    - `7 S2 X8 y* a! z0 C- Z% S" ZName: Sale, dtype: float64- H9 i5 m) y; v+ S4 z

    - p. Q/ L& ~/ O/ ]3 t7 {1* {- r, `- ~% J% G. L- L0 R
    2
    4 Y6 |4 i' A( p& a; }3- |" U, U" c6 E, F
    4
    5 Z# ~5 H1 H+ k6 s50 k8 z4 d' R( G; r
    6; k9 B8 C% @* e& z$ P/ T
    7
    / U7 q! D1 x; F! Y- u8
    * s4 I. y. }( a6 a: C# }9  V' ^; Q4 j- ~  P2 M& o
    10& u/ B( i$ i9 a& D0 w. @$ a
    11
    5 E/ b$ F6 H& M123 g6 t! k# Q1 B5 z
    13
    " u& o+ x6 |( y7 W14" x4 P/ }! \& ]% j9 s
    15
    1 l( G7 ?, l( ~' F. W/ n16% \  d6 r0 Z( ?, u! ]6 L6 g8 Q7 z
    17; C" K9 C% ]( U, i' g7 B
    18% q7 k$ k/ |) K/ V
    19
    ( ^' U( M1 q7 M  y: e2 P# @20
    # s. {' `. z0 K7 s! r! {按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    - O# H/ z( X! U8 Dresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date./ O/ D/ I9 w: k
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 , o5 N! J! `7 F* n" n
                                            $ p) E1 j& _4 J" e% h1 o! ^
    result=result.unstack(1).rename_axis(index={'Date':'Month'},
    6 q- ^) [4 p; o8 z' ?8 G. h1 u                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.
    % X1 D3 }' ]' S' eresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)" f# I/ Y) F6 b! L$ A& e7 D9 ]  E
    result.head() # 索引名有空再改吧
    9 {1 R8 W. }" X" s& I3 Q6 D: Y, r* }. S# A$ i/ {1 T# J
              Week        0        1        2        3        4        5        6! H6 g+ `: K. R: U0 R, _
    Fruit Month                                                       
    * N3 ?5 i2 Q7 F& n5 uApple        1        46        50        50        45        32        42        23
    . m  G- h6 @0 u( {9 M! t/ FBanana        1        27        29        24        42        36        24        35
    8 H6 e8 a- M& V' _Grape        1        42        75        53        63        36        57        46: ^7 \% `+ o8 J) L8 l. O
    Peach        1        67        78        73        88        59        49        723 `7 I* I% y, N
    Pear        1        39        69        51        54        48        36        40
      \+ V" @+ S3 j1 r3 s( j5 i) ?18 {9 N( k: N8 y+ ^  I8 M! s
    2) j/ {2 j# v+ O4 [
    3& F* M8 p* ~) }. F; _4 ]- C
    4
    ! @3 t3 U* v+ D5" K  Z' I! L0 t1 n! o  P* T1 j
    63 q8 l* X" i+ r1 b
    7
    : |" F3 B0 j2 M0 q0 }0 p86 }. o2 j: k* \: B
    9
    . C7 }* D! ?4 j107 B' y7 X; A$ ?9 q
    11
    8 N& r/ q5 s2 n* @12& v7 N/ b, j; b" E3 N- F
    13; v; {* {! p! X" b* {3 _* |
    14
    ; g: ~- @3 L! E& g: ^5 J$ I158 {1 m! q1 P5 {8 a8 @2 a# _0 Z
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。  n- v" t; b8 _) t+ f$ g" R: q
    # 工作日苹果销量按日期排序
      M: I1 V1 k% x# x/ a. {select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
    % F1 L0 X! p; \select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
    # A# O& `; G6 C3 @select_bday.head()
    * S2 D& P6 u5 C3 {) G! h, Z$ E5 ^( M' J2 W( O7 F
    Date0 a0 K& h/ v& a6 K7 X! N5 `1 [
    2019-01-01    189
    ' x% {7 P* v- K( [: j: `; b2019-01-02    482
    ' Z! l! N/ y) {2 X$ I: ^% F2019-01-03    890: i. r, }7 g. D: M8 M% M
    2019-01-04    5508 \  R% R' g: @! a
    2019-01-07    494+ y1 c$ R8 o3 {! \, b  l# g

      a5 N' F# h% g+ v9 r# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。( z' [7 d/ j* h4 }9 x, E, T& c4 C, b  F
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
    . [4 l. ~3 M! C: \0 j3 b: d2 O" z& b- s# l. }3 ]
    Date
    - m3 p. x0 ^) d$ |1 X' f2019-01-01    189.000000- b) h7 y! G8 }3 T" V
    2019-01-02    335.500000
    6 M& W" p, W; |; C+ m0 E2019-01-03    520.333333) n4 R  R" B' O& e5 ^# G
    2019-01-04    527.750000* J. m% g2 x) ?9 h
    2019-01-05    527.750000
    5 R8 _1 @, G( a9 d8 J7 O4 x9 W4 E3 N2 ~! F! L$ [6 i6 @' g/ o( x
    ————————————————
    / G- u2 i0 a1 I5 @7 b, Q9 g版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。1 }9 u) H2 p1 S* F
    原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    " Y2 ]& Q  }* d2 u9 c# y9 n) W5 D6 K; o# J- i. t! o& P. B
    ; z$ l5 F/ t6 y2 P7 Y& Q, t4 }
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-4-16 07:43 , Processed in 0.704167 second(s), 50 queries .

    回顶部