QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2816|回复: 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
    : A; }# a; V" \- W
    5 ~7 T' [, q  _0 v4 v* J, V% |

    " a$ m- ?! S3 \1 P& r文章目录1 f$ d5 Y$ j& m4 A7 x
    第八章 文本数据
    8 w$ b# \# C1 }. M- V, V. R& s2 ~% c) E8.1 str对象" W' I1 G; V5 m; j
    8.1.1 str对象的设计意图2 j% c; m' |+ u$ J" y' X8 L9 e
    8.1.3 string类型
    $ c' r" s- q2 t8.2 正则表达式基础9 n) A) g- C" g$ P$ ^$ y
    8.2.1 . 一般字符的匹配' }6 b. R4 U/ X" z- Z, e0 j
    8.2.2 元字符基础8 u8 @2 t5 Z) V" g* x% j/ c
    8.2.3 简写字符集3 U# O4 n2 M- u: d( ^- j
    8.3 文本处理的五类操作/ H- N) K% N$ |& y. L
    8.3.1 `str.split `拆分2 @/ [8 \. X/ y! ~$ C% z- K* Z
    8.3.2 `str.join` 或 `str.cat `合并
    & A8 m0 W5 [9 z7 p8.3.3 匹配" [4 c  T2 g  s# e! {& G
    8.3.5 提取
    3 ^) ]5 R- h7 y' q8.4、常用字符串函数
    3 B2 U8 W2 y: o% L- s8.4.1 字母型函数6 Z" x, }6 L$ Q" |4 e  T
    8.4.2 数值型函数+ j$ i: B2 r# ~& P0 L+ Y
    8.4.3 统计型函数# w/ T4 q" S% Q4 M$ S
    8.4.4 格式型函数
    0 z5 J. c, D3 U0 x4 l, X1 z8.5 练习
    % o2 {4 e. u$ AEx1:房屋信息数据集
    ' _8 T, q: y2 I% @* j! dEx2:《权力的游戏》剧本数据集! m4 b! Q: M9 @7 z
    第九章 分类数据
    4 ]$ K. K1 X0 \7 J! X% P9.1 cat对象
    ) l6 s% A% O7 h: ^3 a9.1.1 cat对象的属性5 ^7 P+ e2 N% Q+ s$ M$ W1 J. x" Q
    9.1.2 类别的增加、删除和修改
    + b$ \0 {3 G. N1 Q/ D  e9.2 有序分类
    " Z7 k/ y" i2 t9.2.1 序的建立
    7 ~/ i( C& a/ w4 `& i( c# a9.2.2 排序和比较- x- \$ C7 h+ ^
    9.3 区间类别4 s8 X. G' X' ~) U" m6 T' ~- f
    9.3.1 利用cut和qcut进行区间构造# _( f% \1 F- H: h3 p" s
    9.3.2 一般区间的构造! n  K. e) f$ ]; i2 J( t
    9.3.3 区间的属性与方法
    ! C4 J# r' G/ R; ]1 \7 y: @9.4 练习7 o% {% a0 O* O8 F1 f1 z2 r0 [
    Ex1: 统计未出现的类别- M/ L. {: b- g/ R/ O# a: v& _, o
    Ex2: 钻石数据集
    6 s# d. j3 N4 Y3 X; {5 v3 ^第十章 时序数据
    2 }8 g* w7 k% M1 ^) h' I10.1 时序中的基本对象
    8 X% e4 U/ f" M9 W) t10.2 时间戳
    + N7 D' j- u5 k5 u5 ^: Q, {10.2.1 Timestamp的构造与属性
    3 J& r; X+ X7 h10.2.2 Datetime序列的生成
    ( L9 q, V* [) M8 x0 n10.2.3 dt对象
    * ~: R9 V+ B- X7 T& t10.2.4 时间戳的切片与索引
    # s* {$ x2 _* y% m10.3 时间差6 v7 J7 O' S! G% F6 q- m9 W
    10.3.1 Timedelta的生成0 n0 [: c7 k5 E' |6 x/ N2 O! h
    10.2.2 Timedelta的运算, W5 K3 M, u. p" B$ P6 `  ?4 N
    10.4 日期偏置
    % ^1 Z8 D" o: {  b4 [10.4.1 Offset对象. L; M9 K) U. s5 C/ q- L9 |2 k
    10.4.2 偏置字符串
    6 N: M  \, v& |4 ?, s) m) z" o10.5、时序中的滑窗与分组
    7 B& m% ?! W. v10.5.1 滑动窗口
    2 J" |/ X' G0 I$ V10.5.2 重采样
    . `8 h$ \( G' Q( _10.6 练习
    5 n7 a; r. ~7 N) f9 z, L* W0 h2 h+ m% OEx1:太阳辐射数据集$ L( Y' a4 q( h
    Ex2:水果销量数据集0 {& l/ r, x! W' O: B7 o$ d* O
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
    9 F, ^, J2 z" ^7 S1 a* a3 M传送门:' K* d" k1 F* }/ o* H6 {- d

    3 F* D& o' [. X* r" K# M( J' Qdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
    : J: y/ ]" O8 c. C2 gdatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
    4 ?( R# T, t( D: b第八章 文本数据
    ) R7 e/ G/ B& H2 K3 ~7 |) n# T8.1 str对象3 s/ C3 n( E" T9 H+ i
    8.1.1 str对象的设计意图
    ) p  I' X3 H0 {$ }7 K  str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:1 M7 Z+ l: Z+ A( D1 L
    5 }. B  y4 X" H5 {
    var = 'abcd'  b, X0 ^: ?, W+ L( ^. K
    str.upper(var) # Python内置str模块  e: g/ Z5 L7 r/ a% J, o
    Out[4]: 'ABCD'
    1 u- e9 T' e3 V9 [
    $ k, a4 u% e( w( `s = pd.Series(['abcd', 'efg', 'hi'])
    6 y8 j' E$ f# L9 w: g! L2 N3 x8 `6 @7 F  ?6 ?$ t3 }& f
    s.str
    $ Z* K# L- D9 Y; ?8 g2 cOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    % N. e2 H. Q! J/ u7 o
    - B3 k5 d: u! q9 F2 Cs.str.upper() # pandas中str对象上的upper方法% y* j" I1 V2 o
    Out[7]:
    1 g, l* n0 {% v0 e- ^/ ^0    ABCD
    / G. @5 T5 A1 S. I2 z1     EFG
    $ b7 ?) T  {0 l: Z1 V2      HI
    % E( z5 K( f3 E3 d9 gdtype: object; d9 a- z( E6 m4 b: b) L7 r
    1* {8 ^3 s. w' I6 ]: C! w
    2' D/ h, ]& i' F9 L
    3
    8 f. U- O& R$ h: V4
    ; i. ~! j. k& g* T5( h& P- D* F7 s& s( s
    6
    - q9 f) ?( B+ {% f9 M4 k0 E! y7
    4 N. t8 X1 T8 {' [& v8 S2 m8; V) k0 ~: Y& T
    9
    4 Y2 ^9 y8 C1 z5 i10
    ( W2 o8 G8 ]% t- p5 t# |11
    3 s7 r5 n1 m3 [! b3 n3 I12
    3 R( W& q2 Y4 H2 E1 I' D13* P9 ?& w- ~, b. L8 i: Y
    14
    0 A6 \* S; u3 l& X1 e) x+ L8 Q15
      M& I0 F+ V2 s3 [0 N' l8.1.2 []索引器  J. J9 `3 a: o7 Z4 @
      对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
    3 D' u( [5 c/ I. [! R# b6 i  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    5 e! O4 Y- U1 M$ [1 ~8 `1 a  w
    6 h# ]. P- X/ Y  s& \/ z! ?) Ys.str[0]) E5 e" z" l. M- [" P! q4 M
    Out[10]: . C* I- I- s" B! D
    0    a
    6 Q5 T5 K" [) |: L( E' S9 G) l7 Z1    e
    0 k# t% \2 u1 G! k* h, n2    h* e: F8 _. w) Y& h/ {8 h
    dtype: object
      n/ ]# C4 c8 `5 H
    ; D5 Z: ^. |( D( U  z! Vs.str[-1: 0: -2]
      ?, |+ _' S7 m. V* t8 K/ gOut[11]: : c, U4 p5 |( U/ ?# Y% N' E8 M
    0    db$ K: K- h- v; S8 O
    1     g3 a4 I; e( u0 P: A# C1 B
    2     i
    $ X& c, Z5 `& Z+ w4 Y, f' d' W  }% Odtype: object
    7 T* P, d  F# }. c0 |1 c: d2 x! y4 [- w2 N& c) z5 q, ]
    s.str[2]+ n( m/ E- `- E" h9 l' y
    Out[12]:
    % v  q4 B& M6 H; h, }# }- ^0      c
    2 r0 Y2 A: s. G/ a* l1      g
    9 A: h: R. C7 X: M: j2    NaN. O! h  ]+ X( k' D7 n1 S0 N
    dtype: object
    3 x5 k5 g; _/ b  g$ O  d
    + |- s% E: s# M: f3 Z0 ^1
    5 J. H' A* Y- o" e28 N* }7 q" }+ J- I' |6 H! Q
    3) g- n  g4 j" E$ Z
    4
    ) a4 t4 i# z4 q7 e3 k5
    7 h0 m8 L5 L* ?  v6
    5 M0 y# I' X' n# Z9 C. Q7  j3 R4 w+ T: z; G
    8- F! V# N) w1 i* f* v
    9. k, A- E, t( D- }6 o* O( D6 b
    108 a  q' n5 i4 X8 R% R. C! ^" _
    115 ^% A% V1 y1 @
    123 _2 O1 Z6 }  v& ?0 S
    132 ]+ u5 z4 B+ x7 ?+ Z
    14  t1 _" Y7 M5 w0 Q0 V
    15( s* Z* e, |9 K' S; w
    16, a! g5 K1 e/ Y# s. Q8 q5 y
    17
    0 f8 x4 w8 s  P* s18, R- a$ }9 e$ g1 r' @0 A" p
    19+ @$ j& y, ~! J" `7 A5 |' D. q
    201 ~4 ?& {  K  f, j/ _) ~
    import numpy as np+ l7 |( b, P7 p% F
    import pandas as pd
    8 p& T7 y) C2 z- _* N" T3 B% Q7 M: _# Z+ t
    s = pd.Series(['abcd', 'efg', 'hi'])/ t) u% |- j* k4 Y9 Y
    s.str[0]
    1 Y$ z6 |% H4 [7 [) @1
    ( k! M! ]9 k. f, A# Z' D; m2
    6 K/ v* j7 Q% W6 G2 |8 V30 t, T; b, k4 k' t0 Q
    40 e9 d. l3 ]  A$ q$ r  y
    5
    & O/ T' a1 H% w" t9 u- L  i  X$ m0    a% Y" t- P# {$ \3 g5 F
    1    e
    6 v/ M  \/ y" ?; g) J( |5 L' q" p2    h
    6 ~, w6 C3 f! adtype: object9 X- _  k6 ^, L/ e
    1! B; n3 z& s% D: x2 H
    26 Z! N# P  w& ^( n7 q6 r. Q
    3( g5 m) x0 {. U3 L( p
    4
    * D- l5 J4 `& ]) M8.1.3 string类型
    * Q8 N3 G0 ?, z* k/ L6 k1 y  在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
    & z2 G: n. E9 D: p" L" S  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
    # s- i% Z$ W; ?% ]: |7 ^8 J7 a
    ! J" D- s5 i1 r, L* [. L3 V/ W  r" Z二者对于某些对象的 str 序列化方法不同。
    + {& ?8 N8 d; x( y, z$ }+ R可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:. h7 O/ O& K1 o" \7 n
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])! p9 L! r1 J& l1 y+ s& E
    s
    1 U; m& o$ A; \+ C6 d& q  G4 }1. ?9 U% \9 U* T- X( }" ^
    2
    4 A' L  d& ~) C0    {1: 'temp_1', 2: 'temp_2'}
    6 W% Q# V/ k4 h. C: m0 m+ a1                        [a, b]- J" o& J: @4 P
    2                           0.5
    4 ~3 Y6 @+ ?9 h3                     my_string
    8 f6 {/ p9 Q" {3 Adtype: object6 V! S, _9 x) b/ X8 z
    1+ Z( o7 W8 m1 R2 h4 K  P# `+ T6 ~
    2
    + @/ E) E: A5 ?1 p- b2 o" P  @3
    : w) P$ t- j7 F! g4
    * S  B8 b( ]6 O, g5. c0 E! E- w: E- ~% k$ V
    s.str[1] # 对每个元素取[1]的操作* U+ r7 z, \% {' A* S
    1: H5 ~4 n6 {; |; I) Z0 m( M- q
    0    temp_1/ F  ?% R! A% z& v. `
    1         b* u: E! h: _% T- {
    2       NaN( g2 K; ]7 }% W) m8 X
    3         y
    4 v* m3 N' z8 ~9 Y0 ^" E0 Edtype: object! ^2 w( ^" @+ H. G: I
    1
    ! Q) [' m! X; B# D0 J7 a$ X2" [. d% E+ k# Z4 x
    3
    * E' h# w1 c4 Q  P, a- f) H7 E45 _( F% _7 K  J" L$ K0 A3 S
    5
    4 |" k/ y# G2 N3 M/ ws.astype('string').str[1]5 q! N7 E7 R9 ]; _
    1% l$ H5 L/ g: A1 @5 p8 R; G4 X! S& q9 ]
    0    1
    ' O. G' E) i4 ?) m1    '/ O- g8 P& _% v- t0 e( g& ]$ w
    2    .
    6 D- m9 K9 }9 R) [4 W" @* v. N3    y" ?6 J! ~- W; y( Q
    dtype: string
    ; C5 R+ z9 l2 s2 |: U) Q$ u1
    1 L) L. y7 v1 Y9 W$ M2
    - |$ W( B5 k* x% p6 Y) x37 E2 _& |0 ?! x" v* W6 e  D
    42 @5 l& D6 }7 c% ~) M- V2 F5 h! j$ |
    59 x7 c$ [  r2 Q- }1 R
    除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    2 [6 {% r9 W' j. C
    ) x4 l0 I1 I7 @: C9 _当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    , y3 f+ a8 T, M0 @) p# ^string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。2 d0 K9 s* Q: U. N! z" ?
    string 类型是 Nullable 类型,但 object 不是
    4 k! E2 @3 y! k- ]. Q6 W  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。! U; Q1 ]" Z$ q
      同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
    5 H' O& o3 O' H' Is = pd.Series(['a'])
    0 W8 K7 D" L( o' j$ f  a
    + D0 I% }- F2 o) N+ z& i: js.str.len()
    0 Y! l( h2 W0 z* _Out[17]:
    4 B1 ?, n1 p2 _  x7 j* k% [0    1( ]: ]% B9 }: g, x' q
    dtype: int64
    2 t7 ]! u4 P. ^: l8 d" b% ~8 y0 c
    , j, T' U7 }) \3 J6 ~s.astype('string').str.len()5 i1 ~" x) J7 X' u6 \- }
    Out[18]: 6 O: ]5 X3 q7 `2 u
    0    1, _# p  ^; n" V$ a/ L& Z
    dtype: Int64
    " W, C9 K& J: s" l2 I
    ) p2 Z, E9 _3 S+ D# q% [" K6 Ps == 'a'
    + X3 k; q0 u" q6 e' b2 p2 FOut[19]:
    3 J# T- Y* f1 C0    True
    ! l; E# N: z4 O, {0 F3 Q* Sdtype: bool/ m( D7 F$ x6 r* ?* _
    # V% U( \. [$ a$ y# r
    s.astype('string') == 'a'
    + T. |* A  s! ]. H3 bOut[20]: & a! D" d4 T" F/ i  e) p
    0    True
    ! Y% T  C$ g+ x9 b  q5 i! Sdtype: boolean
    * O/ I" m$ _7 `/ j) i/ h
    + z3 d' W8 l7 T, ~, ss = pd.Series(['a', np.nan]) # 带有缺失值. S* r* u1 ]" `: j3 g4 R
    ) I! y& Z3 e9 X$ E4 I/ }: }- M
    s.str.len()4 d/ D* u$ ]. A! b2 T1 w
    Out[22]:
    $ F  G7 e7 H* t3 F5 i* Z6 l0    1.0
    + D& B" {; o+ H1 k2 B& o1    NaN" G: s+ U2 r/ ^' N- c
    dtype: float64
    . C$ C8 F. b9 S; }* x' m8 n, s7 e9 a6 u* J, _3 e* S; B
    s.astype('string').str.len()3 t: t8 Y2 o! u$ @
    Out[23]:
    2 g* S9 v, V0 M9 i1 G0       16 ], ]: T) z. r* q5 t5 I
    1    <NA>
    3 l( L$ z# u, u( G. m" ndtype: Int64# \) i) v# ?: e+ f

    0 i: s7 n  `' C( U3 Os == 'a'& a( [6 N1 ?) T
    Out[24]: ( s: j" c$ A0 ?9 W2 a
    0     True8 m6 T& z3 b4 ?/ A
    1    False3 M7 b: r4 F; m* M' j& L2 E% \6 l
    dtype: bool5 u1 i5 J' X. |% k
    # x5 {5 p0 L, A5 \6 L3 W1 N& n
    s.astype('string') == 'a'
    . B1 q+ {& i( n5 g) Y' Y0 e( _Out[25]:
    + @% k- x+ p& A0 p6 v0    True' i9 G# f7 G5 Y8 T5 y
    1    <NA>
    ) ~7 H# @$ D" x; C% \dtype: boolean5 P2 d7 O* X' c" D

    4 W" _' \2 p! ^, j* \. H6 C/ u1
    1 d. J3 H8 L5 E6 \4 d2
    7 Y  X: }) o( m3
    ' ]: L# a6 |; o( k% F/ g* D4) X! _) ^6 L; j
    5. N; U1 t' J8 G2 g" U  q9 p8 c
    6
      x  ]- [) N% |% W: ^' n7  S0 ^# W7 F; D& e+ z
    8
    ( c8 k; u, Q, ]3 {9
    , j6 q# A' s2 r) c+ u10
    9 R* R- i+ h) N* ~$ m- e# v11) u/ h6 M+ Z+ r' Y
    12
    6 H  r0 ?* `* u1 ?* }  j# u( l3 ~132 m, `+ E, f! q/ P4 F2 R
    14
    : v# Z  g0 v/ A0 x, V15, F0 d* D1 f0 j! c" G9 S0 F
    16) g- M/ q! j5 M6 s/ o
    17
    * N; R+ j; \; y0 B9 T% K4 }18
    5 O4 W1 P/ }" P" a& p7 k192 J5 L+ c8 w- j5 N! n: @/ @
    20
    9 L8 y. }* W/ e4 t6 p* s21
    8 X' P% e  i1 }, [8 Y6 n" ~, X8 D22+ C+ V+ |$ R: B! M
    238 @8 t9 l2 @5 z$ d  l
    24* D3 H" |7 j/ L
    25
    0 q: m* i# J1 {7 P! d26
    7 F3 ?/ N6 C& |! b1 x5 b  Y1 c+ i277 U0 N3 l; y* L$ P% V
    28
    2 b/ d' E/ L5 g8 G, U295 ^# {; f. L, t3 K( B# [2 U( ^
    30
    & S' }1 X. X" b5 }. G31
    - q& b( Y  X, J/ E32
    7 s; R0 `. C6 H+ ~$ K) k33! t. q1 z+ u  Q, n( R" J4 C1 O  e" g
    343 O7 r: h' q9 ]. F1 f: e- F
    355 }& [8 K: i$ Y* S7 X4 c0 _
    36- I: N, ]! v1 u$ d% A
    37
    ' m- l0 e1 M6 v1 g38
    # n7 U  X) K3 k& q( ]39- y  v0 ]* C2 S: n7 e0 m
    408 z6 `8 n; B: X! F* W
    41
    . ?$ |, b0 U( N42
    6 E$ E# m! A5 P2 d6 ]430 m1 M6 T: W/ C- R. u. W
    44
    2 z" s( i! K' l) c45
    - b# n. C+ B8 Z46% }4 I9 ^, T7 Y3 r
    47+ K8 u$ o4 Z+ `0 S/ @
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :! U- C% u* y: _4 i

    % i0 Z; R5 c5 y/ Fs = pd.Series([12, 345, 6789])' J; c4 p. U1 B2 q# ^* k7 R

    , P8 |6 E& L" S: J# ~, g! X* l' N1 @s.astype('string').str[1]5 _+ p, \, Q' T2 o7 K; L
    Out[27]:
    . u7 Q4 q1 F% z! b) \5 @0    2# Q6 ^; ~5 ~/ u: y- ^* ^, _5 _. _
    1    4
    ( @: c1 p: y! ]0 r/ M$ X2 m" W; }2    7
    5 N* B% \& z' m3 Xdtype: string
    : V* _0 T9 I( H- S- d3 d6 q2 _, A17 o8 \& R5 c1 k- z: J( x. F
    2" r7 {' I/ G6 H7 I* t! \
    3
    - ]; B; }/ z5 C' p, f. s+ w9 i4
    9 P# X% d1 j5 G0 U; Q5& N$ V- h7 A) M: e) b& ^2 t
    6
    ' s% n) c( O' X! V2 X; j; p- G7: Q0 }. H% v1 j' w* k
    8
    " g/ V) U# }. L! f' C8.2 正则表达式基础5 b7 y' n) W& z; P' R7 G- M+ A
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
    ( i: ]/ G& N1 H4 v$ M% B- A; m( o$ d" C
    8.2.1 . 一般字符的匹配
    5 z6 ?! @* y) O  I正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    : o! V& F5 N: U# J: P) c6 m* Q# K/ N3 A
    import re- k4 M- s  i) e# j* o

    " B% B& ^' J; q  M; y6 V* Xre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配# X, o& Q3 M! w! i( ^* T
    Out[29]: ['Apple', 'Apple']) Z1 T! [4 q* U* a
    11 {9 C( B& E, ?
    2
    9 ]: O9 k1 H2 J7 P  k3
    ) U% x# p' E* e4, f: j( V: o# z" {5 z% J. [& t
    8.2.2 元字符基础" h2 h* S" @% K+ \: _4 Y0 v
    元字符        描述
      H. s8 X$ [( C( Q* Y.        匹配除换行符以外的任意字符2 T! r7 M- p: J. {' Z
    [ ]        字符类,匹配方括号中包含的任意字符
    6 u! k6 R' `) \" r! b, M[^ ]        否定字符类,匹配方括号中不包含的任意字符7 q  E) c  j6 `/ T, m6 f5 S
    *        匹配前面的子表达式零次或多次
    ) y# |  D% s/ d+ P  e7 x! H+        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
    . F9 }9 r- u, V- x, V* p; F% r* l, f?        匹配前面的子表达式零次或一次,非贪婪方式
    . r# u! f1 i% D2 u. V  f2 C8 V{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
    ' W/ W6 E7 ]5 }. o# a! M7 l" _(xyz)        字符组,按照确切的顺序匹配字符xyz
    % O6 W/ k0 x/ s1 @4 a|        分支结构,匹配符号之前的字符或后面的字符
    $ k2 N0 ?! S4 K  {$ D\        转义符,它可以还原元字符原来的含义
    8 l: e( q) X" y" m9 W; v^        匹配行的开始0 o- j3 v; x. u& e0 }* V
    $        匹配行的结束
    : {" y" X  \6 ]0 |$ K5 ?5 limport re
    / Q4 e. R  P$ P3 ^re.findall(r'.', 'abc')
    - I  a- z( D+ ~4 s( uOut[30]: ['a', 'b', 'c']2 [+ F% B/ N" ?: l4 \

    ' R4 `. r0 U, D* D+ }/ p3 q- kre.findall(r'[ac]', 'abc') # []中有的子串都匹配
    % Z3 M  G! k8 t$ UOut[31]: ['a', 'c']" f) l; H; q5 A3 w& y9 }
    . h9 D4 t! \" y& J* Q- ^
    re.findall(r'[^ac]', 'abc')
    6 @  F$ ~7 a* |# R0 y  \$ R7 ?Out[32]: ['b']
    ; h' E5 A& S4 M% X
    ( G( Z0 x% e+ I6 Y1 U) I0 ]" o0 rre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    1 {7 Z; ?( L4 h, s  n9 zOut[33]: ['aa', 'aa', 'bb', 'bb']
    ) e* n8 k/ R: w$ S# l) [
    3 E9 C" d: R* G/ D0 ~$ a2 Kre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
    9 [* d4 i3 p  e. m. X( ]Out[34]: ['ca', 'bbc', 'bbc']1 E( P3 c. P( a4 Q
    % X$ J4 ?9 o2 ~2 T
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
    $ S) v# m9 G, D8 i) _+ q& x) C% y"""
    ' l6 d- m. `" @( k0 b6 ?' v1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。& M8 z3 ^7 Y7 S6 s
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边  `3 o4 @+ V& k- J6 ?. b
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,- V$ O' J/ q* Y4 v( m8 J
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a2 z/ `5 j! q+ e
    """
    " Q4 I$ [! s' P" z% `1 X8 P4 C  \: b+ D  |/ _( o, Y! U
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。& P; T" I# Y2 U5 @4 K
    Out[35]: ['a', 'a', 'a', 'a']
    4 x( E  Q' y. L- z- o  x- f( X# q6 S% _& `) q
    # 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。5 p/ D- G  L9 A& ~- ?, q1 ~2 q
    # 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。4 B" Q7 _! c* r" \4 G3 _
    re.findall(r'\\', 'aa\a*a') & n& e( P. `" |& H: R! j
    []7 b+ r" u7 {/ n# s9 h$ @" {
    ! m, o( U/ M8 H
    re.findall(r'a?.', 'abaacadaae')
    & @% R! `, P, ~7 U6 J8 \% t. _; v2 oOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    : _+ ]. e% [; f& i
    * X: c' ~7 M5 \% E2 r8 mre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
    ) n  {1 K! D+ H2 Y/ @[('width', '20'), ('height', '10')]! i) {: n6 p! `, S

    * T+ \  x4 [$ a9 w1
    / {* I2 X' _# f2
    ! z% ^) ]: j; A+ p4 Z6 G* r- L/ ^3' M0 l9 p; n2 ^9 v- c/ D* P) |, v
    4# ^; L8 z$ n+ Z; T" }( c
    5
      F/ v7 }' [* N2 d- I/ x+ v6% D4 D/ H5 E4 }7 v  c
    75 N. x% a7 n& g/ Q7 T6 G# H6 D% s
    85 H) n! |8 N9 a3 L
    99 w- d) c3 Y& N
    10
    . V; o2 \" p+ t3 @- l, m11& j; I3 {, Q8 Z# E# W+ L
    12
    2 N9 r1 @4 A: O1 g$ v3 Y( {13. N- J" K4 D7 o+ n
    14
    6 ?& u. X, K$ R3 g15( D1 V) A/ p5 p. w1 Y0 p; r5 F
    16: ^) |+ j$ y' w% a
    17( I0 a7 p8 v- {  c. c
    18
    , G, v6 ]2 d! d$ K2 h* j19
    $ W1 X. l2 _# q" r0 E200 ?/ Z( P% e2 X$ W
    218 J' @7 @& C/ @' |
    22
    8 V4 @& z- F4 y8 B5 E4 ?239 D2 W( r3 L, t  I' {* j8 G. x! k
    24
    ( J2 C% D( D- R6 Z  h+ d3 s8 _4 b# K  C25
    - {$ v( m; y1 f5 `' \8 ?; V26
    / A# N- d4 d$ h* u27# I3 Y7 @$ w3 U* ]! P
    28" {5 m6 G2 G5 a$ `& `, ?1 Q$ h
    298 y* }! r2 s# c( v5 E' B& ~
    308 G# l2 ^2 ^' k# }
    31
    ! L! j- b3 `  B; v5 |$ y32
    1 l/ D2 }, W1 b" ]2 p& x33
    $ \9 H: Y3 p8 g: B8 ]34' V6 c$ t5 A5 ?: O5 u; M
    35
    * }* b% o- R# d+ y0 M% y36
    & F# L) o+ O# _* y37
      m2 h  }4 r' w# P3 ^5 K8.2.3 简写字符集- A. O! @2 ?* o' A4 H
    则表达式中还有一类简写字符集,其等价于一组字符的集合:
    * t3 V% {7 n, U* z* \
    ( b$ J* u, b8 y% K# K1 u7 r简写        描述
    $ b9 T8 O4 w- L7 a2 q) X  i2 T\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
    * f7 l9 S4 V, K+ ]+ e8 c$ `\W        匹配非字母和数字的字符: [^\w]( @/ A8 A! ~( ]9 f% q+ l; @+ D. e
    \d        匹配数字: [0-9]0 F* h+ y# `  ~7 Z. C
    \D        匹配非数字: [^\d]
    # Y9 N% U; R; t4 O\s        匹配空格符: [\t\n\f\r\p{Z}]
    3 W, m# H3 s& o9 O# w\S        匹配非空格符: [^\s]' u% D9 K2 v0 l5 @# C% F7 u$ `7 K
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    ; l& `% p1 w3 A; x: l: V* g  bre.findall(r'.s', 'Apple! This Is an Apple!')4 X( I; L" f, m9 T/ @6 y$ z8 I
    Out[37]: ['is', 'Is'], L: B9 _& V5 [- E4 v, k7 j
    * a% @8 I7 B5 p+ m6 v
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次$ Z9 ]  V0 D3 W9 s" H0 t
    Out[38]: ['09', '7w', 'c_', '9q']! G  S0 R/ G+ w) z

    ; c; F3 E5 d2 f, qre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
    ! M+ [8 V$ Z6 A/ K: M  Z( cOut[39]: ['8?', 'p@']
    ) n6 o8 i) i; U: @* F* O0 z& ]# E4 U  X! Q# h) `( ^
    re.findall(r'.\s.', 'Constant dropping wears the stone.')
    , t; i4 P/ W2 \( |+ b+ hOut[40]: ['t d', 'g w', 's t', 'e s']/ V4 b7 P1 [) H+ v

    ! z  P/ h3 g! S& Y0 x- are.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',1 K' H0 n; `* X$ b' F
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
    $ k+ ]+ R$ D( V. u) D; f+ ?: [1 K6 ^1 O/ D# b7 Z1 K
    Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
    . e, P9 D* D1 \4 S' I' n4 U7 i- o  S5 q5 G- X: R6 E, q+ ]/ e
    1
    4 H( p4 U4 \. G( u) O2
    , h& F2 A) G5 V. I" f7 A; P3% l, F/ C% K2 L+ B+ p! D
    45 G2 @4 @7 P6 {+ T9 }" G
    5( G8 E2 ~: I- p7 V! m
    6
    ! J8 ]8 U4 [# l* U- j0 u" g7" N, ^! l% D8 Z9 _4 x1 a
    87 ^3 }% {& v( A* t
    9  k! ^' s% u7 B  w) }
    10
    - d: D% A/ {+ `# W" }114 R3 F1 {( z2 r; W; _
    12
    6 u( T  u! R- h+ x: \' Z- a1 b7 [, f13
    4 a$ n+ B/ d2 H5 k$ n+ d" V- i148 |5 B( Q6 ^2 A; l' {& J2 u9 X
    15
    # R, I& H+ g! T# ?+ d9 m16& F  Y6 I3 k+ \9 a/ I6 V
    8.3 文本处理的五类操作
    - Y$ A6 M' L4 H* v3 g8.3.1 str.split 拆分
      p$ _7 w8 K' P  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。1 J6 u2 d! F! e4 y& ]

    3 h4 O% T0 {8 Gs = pd.Series(['上海市黄浦区方浜中路249号',
    8 ?1 q1 V% y1 n0 a7 k7 B            '上海市宝山区密山路5号'])5 V& t5 X( |4 o1 r& o8 Z3 y; z

    9 n- \+ V0 s6 I. h6 `' v; S; j9 y
    s.str.split('[市区路]') # 每条结果为一行,相当于Series
    2 j! J7 Q( F/ nOut[43]: 3 P+ ?- J( C8 s5 t
    0    [上海, 黄浦, 方浜中, 249号]
    3 G1 Y9 O8 G- |1 X; M+ N; f# m1       [上海, 宝山, 密山, 5号]
    3 i! t. ?, G  P" o2 |, [dtype: object& i+ X  B9 s9 z7 F& f& O

    : \* N% E" q, c, X4 e% `s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame$ ~, X) B7 m! C( }
    Out[44]:
    6 ~8 W! H5 ~: l& }+ L    0   1         2
    # e; U8 N5 D, \4 a0  上海  黄浦  方浜中路249号/ }4 K/ c3 J! I( D9 U  f
    1  上海  宝山     密山路5号, H4 W8 d+ }6 G+ P$ {1 M
    1
      E4 Q* q  E, [& X21 T. M. x& d+ x& s
    3& b) r. V' a2 g) S0 n# T
    4& b, p' `5 d5 n- ]9 ^% S; H
    5
    . x* }+ ]1 R2 O; S9 ]64 R$ m* p/ Z& H1 _
    7
    . J2 X  {) y8 V( h4 d# U0 I3 ^  t. u8" u1 v2 a- W$ C. k
    9
    4 P  i" I/ v/ F" g9 W' C& V# z10
    6 g3 I( z3 W! ?/ t3 [11
    ) ~! E% t# A* `# a+ l. J12  G. Z' J* D4 w6 g8 t
    13
    9 |# ]6 \0 O) V14
    5 ]9 ]! l4 O9 Y% V15
    + O9 U5 Y! ]4 o% z) p  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:6 C: z; z. [$ }" L8 `

    ' W' T9 B8 I  l. x$ X  o5 {6 I' {/ As.str.rsplit('[市区路]', n=2, expand=True)4 a3 o( w/ B2 ?
    Out[45]:
    : ~3 Z# i- c' Z  S# j3 t3 X$ B                0
    ; A6 ?' n: v0 Z  P% h/ Y6 y0  上海市黄浦区方浜中路249号
    ( b. E3 |1 p& w& ^1     上海市宝山区密山路5号& g  r, ]% H$ G
    1
    , W  Y  y# l7 Z6 m. \# d2) H4 Z# w# h& W+ }5 Y
    3# ^! T4 w; w. d- E4 x' u) G
    4
    : [! H) D; k/ g. ^4 O9 x: ]5
    ' I# g2 O# e0 g0 d/ a8.3.2 str.join 或 str.cat 合并
    ( ^% `; l( @- Z: A: O( a( L  P% ^' Rstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。- X5 [, P' t# z+ x3 S
    str.cat 用于合并两个序列,主要参数为:
    7 l* R& v& n) J0 ^# [3 p! |sep:连接符、# I9 O7 h, \/ k) }
    join:连接形式默认为以索引为键的左连接% @# e/ d4 y( V; u6 T
    na_rep:缺失值替代符号$ h8 p$ a/ @: l  V" ~' E+ [
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    . C& l9 p+ g/ c! j  r$ T+ O4 A: ss.str.join('-')9 g' |) I( I6 B8 d4 |
    Out[47]: * y$ r' I- |8 \0 I$ A
    0    a-b4 K' ~2 j2 o' X7 e) M$ d% N8 b
    1    NaN8 m6 F$ N/ E7 F
    2    NaN. `+ J9 S. Q8 v- N4 D; D  t- I: c" U$ C
    dtype: object" y. y6 [) M* e( ]* G
    1
    ! H- d% K8 D) v0 n2
    ' P& [  O: t* e0 _5 ^$ ^32 x2 u2 q% a" o  j
    42 K- D2 z9 D; q# I
    5" Z" {8 R- N) o/ y- f/ |
    6
    : {; F8 L  S+ t9 {7
    ( Y$ K! n" k/ x3 K* p9 ]* c! i( W# Bs1 = pd.Series(['a','b'])+ L; a0 o8 a0 P  t
    s2 = pd.Series(['cat','dog'])
    1 j- a3 ^5 c9 P, u# Ps1.str.cat(s2,sep='-')/ n3 G) o7 ^" `! `
    Out[50]:
    , m4 _3 L% e( G+ p) f4 Q0    a-cat
    : R  j" x- z8 k- U0 I# e, h1    b-dog
    3 a- V; f4 W& T$ k* U0 p, U% [dtype: object- x3 Z. a: a; ]; V& W/ @
    ( ~% |: o- z) K4 [9 }; N0 o
    s2.index = [1, 2]- E6 p$ V3 l7 ~9 `9 r
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')" n8 ~: n: V  W! g; U
    Out[52]: ; r: _8 e. I8 t8 l6 x6 C; @
    0      a-?4 m+ U  _/ q& @& Q
    1    b-cat
    & H! {' O  v& z1 h4 [$ ]2    ?-dog
    : b; H' i, F- `4 F) h/ t+ ddtype: object! O( R' {/ I% P/ S4 c
    1  d2 s0 ^. B3 t4 w' t4 |, M
    22 x* H$ ^2 o4 a3 N" w0 N9 n4 f
    36 s5 w3 X. @1 J& a
    4
    & S0 R3 t, h% E4 s4 h1 J1 i2 l55 ^( k) o) |+ p$ V
    6
    * O6 I% U/ ~3 v7 L% x  U" L/ l7# s4 p1 F- T( O& d$ m5 ]3 c
    8, V+ t; y3 J) I1 U5 u
    9
    ( A! \5 j& @9 O7 c* N10( P: l& d* \! O. U' Y. _) ^" h
    11
    - I- {$ `1 q) s8 c2 h$ r12/ d4 ^  z# C* C) h1 [  n
    13
    ) \9 p7 v. {/ A* F; S14- g  s- A# g2 N5 B
    15
    1 p! M5 k1 M3 E$ \; D7 j- C) |8.3.3 匹配# G" w  A/ H$ ?$ D% `
    str.contains返回了每个字符串是否包含正则模式的布尔序列:" `4 T7 `3 k' g/ T  p
    s = pd.Series(['my cat', 'he is fat', 'railway station'])* @" F/ w9 K  Y' _& ~2 ~
    s.str.contains('\s\wat')* ^2 [, n" _% G9 C  p2 @' z
    - ~8 g5 O1 ^- m& \0 m" j  b9 A2 C
    0     True
    ( T' S4 q7 B% F1     True. A8 X3 {5 e0 A+ S7 Y$ W% v; U
    2    False
    % q* C. s7 h) w! t! Jdtype: bool
    9 Y& H) X$ T. |( m1
    ( U7 X/ s2 ]& I  x2
    ; X% y; b5 n1 \- D+ ~- o6 d8 ^- @4 h2 k38 N  e  d- n& I9 K
    41 Z% n4 V: `( q! L
    51 Y0 h+ f' z- ^6 n
    6
    ( U7 D1 A9 e" }, E7 x( _& |7
    1 x) ]$ ~: [% X' f' x1 F5 X% Rstr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    ! |, W* m0 U5 I5 N3 Z0 Ps.str.startswith('my')
    6 _; S. N: V  Y3 H/ V5 m
    / T% Y: k7 p( Q0     True) b  G9 H6 G. ~# \( V+ ^
    1    False* S3 _" \3 n) t/ |
    2    False
    8 w, m  \1 Z2 e8 {* z' W; xdtype: bool% P; D9 l. H; d, p
    18 u$ u% h2 d: n2 b/ N4 ?
    2- w% K0 _) [7 q# H  F2 g. `! [
    39 v2 D: G% ]9 y7 _  Z$ @
    4
      o5 H4 F' S% E/ f; V- T: U6 S5* C, V+ [0 P1 j1 Z
    61 |7 W0 B1 {* _
    s.str.endswith('t')( k  \! F5 r4 n4 {. X

    ' c) q5 ~( c  k5 Y; c. P; S3 d5 U0     True
    , b! l+ b4 [4 n2 d. V* N1     True3 ~+ M* k  b- W0 e
    2    False0 U+ z1 b! S  s5 n- R2 v! A# z
    dtype: bool/ r8 M7 k3 v! C; k: P& }
    1# r5 U+ N/ `- n3 h/ [+ M: N
    2
    ; h' R) Y$ n' W3
    6 J. [. G8 B1 z3 |) k0 E/ M41 ^! B% f( |" X4 s' n1 o3 J
    5
    2 j2 G2 y3 K6 \7 R+ W& Z6' p1 K8 j( X9 k5 }; ]
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)/ p: \8 t" v4 M& }1 S6 O& [( e! M
    s.str.match('m|h')
    7 M& s; h) L$ u; U$ `s.str.contains('^[m|h]') # 二者等价
    . n+ @, I; n0 G# R% V
    $ }9 {$ B/ {5 ^4 u. E0     True
    ) I6 C9 u& Z* P8 u+ A5 p1     True
    , ~) P5 m- P: }2 ]6 E: Y) U0 U: |2    False! K7 T3 J) @( q0 h9 A, A4 r
    dtype: bool
    - B" x$ K! d% u$ s6 @' l+ H, [1
    + _5 ?, H% I$ @* x6 R% t26 R( D9 C" V" Z6 I
    3
    ! ?2 n; p% _- @6 Z4
    2 F+ Y' L  U7 c5
    5 g1 U! C6 H, W( S. {" Y5 I69 E6 P( A% \# S, J( ?7 `  m
    7, s) f( ~3 Y" @. |& h3 |" z. G
    s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配5 \9 E5 |$ ~0 Q$ L+ S) o
    s.str.contains('[f|g]at|n$')       # 二者等价& G% F# T  A: U

    ( J  V6 C) {5 e0 o. K  m6 `0    False
    8 j9 I7 M( H  g6 |, ~1     True
    ' R- ~9 |% b, B! m3 H2     True
    ! m  l" r* N. v1 T+ Z' Bdtype: bool
    * C1 c! ~! I  j8 y5 _1
    " N- |, D( e/ [$ X' i8 ]  r2
    + u" y0 R" E0 L9 I7 H( w3
    ; X$ V- {+ }) ?' l% ]3 D5 o4- F! a$ Y6 h1 t& j6 w
    5$ d- h9 z+ S( w; d/ V6 n
    61 W) d1 h; K) P% F: @
    7
    2 V# a5 X6 C# R! f% `7 hstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
    * W; C: n; X) t2 W' Y9 R* R; \  b& ws = pd.Series(['This is an apple. That is not an apple.'])5 O: J' U) T, x
    * Z) v) Z6 F2 F2 \& B3 X
    s.str.find('apple')/ _6 k* u7 V% Y) {3 m" P& F
    Out[62]:
    5 k; J7 b3 u5 b# [( w+ f/ P0    111 }& @8 }: Q1 f3 Y# f
    dtype: int64
    # i3 P: L" C$ B3 O2 o4 ~" c$ E
    ( c7 `* s$ x6 ~' {8 ^s.str.rfind('apple'), T1 A" K9 K/ e# T
    Out[63]:
    - T5 l! I" K" X$ S2 R9 V0    336 w9 w/ x: g7 Z+ a" @, D/ g
    dtype: int64- k* r' G6 a7 K
    1
    7 J6 d7 X' F0 a" G: ?2
    1 l1 Q! x$ V% v! N3 q5 V8 h% b/ C3
    9 J' Y9 O4 p3 N. W4 d4
    , O( a' D. B( T/ S6 W. i52 q4 M: |4 Y1 M/ q' y9 ~4 l, N1 |
    6
    & h$ o; _5 B3 P/ e8 s4 H7
    % r" N# `& p, M' s, G5 _80 ?( L: m) K- U& E5 u3 k
    9
    % X! e  v) r4 n% ?& e) d) L( Y10: M$ F( I2 k8 L% k% F2 ?! q* `/ L
    11/ @, a! r. ]2 x6 F* H
    替换  e* b7 w* p. C; [" E/ c7 d( @4 m" w
    str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。/ ^# I, r, o% T! R) K2 m% n$ g
    s = pd.Series(['a_1_b','c_?'])
    2 y( |- _/ C8 P+ W7 \/ M# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
    ! J2 {9 z4 h  N; w+ |! H3 s, Fs.str.replace('\d|\?', 'new', regex=True) ! V9 J  w9 @- `- [/ |
    ) W/ w' t: D9 l
    0    a_new_b" w! V+ H' T3 m6 E' b: G5 v
    1      c_new- V* G$ ?! n$ M/ h! f! H& {. X+ Y4 b& E
    dtype: object
    2 b$ S! v, V" [% s4 p% M# K17 X( z* K  s9 k/ @. Y) o
    23 K' `& E0 ^3 a, ~& s- E$ ^5 C
    3
    2 F6 G. M  U& S" s; Y( {1 X$ X4
    ! C$ C/ H) J* b; x/ t, `/ M$ b7 G5
    " ~5 v- G/ z: ?: g6 Y" }, L# X, H6
      F1 }1 K! [! A; x& x7
    7 y" Y7 w! h& U* v  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):' n4 J( X0 K2 {  c) u" p
    # m( v/ r9 f- Y
    s = pd.Series(['上海市黄浦区方浜中路249号',  E- W4 b, D$ u, Q+ p9 T# u# f
                    '上海市宝山区密山路5号',9 R5 ~4 B- \1 m, B& M$ E
                    '北京市昌平区北农路2号'])5 ?+ [4 ?3 n0 R* m+ O
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'  v9 g/ h/ q  h8 d: k
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
    & e& C& e+ _8 _, h. U6 W% hdistrict = {'昌平区': 'CP District',
    # @0 k' y# g- v, R( v; A            '黄浦区': 'HP District',2 w. d5 G5 K5 K1 g: O# Y9 f* A
                '宝山区': 'BS District'}
    & [4 R7 p& l, Q: U$ W) x, groad = {'方浜中路': 'Mid Fangbin Road',
    0 l8 }& l  M& ^$ V! b        '密山路': 'Mishan Road',- O- m- B9 t% O3 A! h! c- |
            '北农路': 'Beinong Road'}% ]+ o( x2 ^. q( r
    def my_func(m):
    . p# I7 Y6 e# k0 R5 f. S    str_city = city[m.group(1)]
    ! f( o* {) r) v    str_district = district[m.group(2)]2 Q8 D4 I8 B5 o. a
        str_road = road[m.group(3)]: p7 N) E1 V, M: I
        str_no = 'No. ' + m.group(4)[:-1]% e/ L! `. w2 [% g2 F# Z  R
        return ' '.join([str_city,) P/ G/ F, c- x% @% i+ Y) p8 n' f
                         str_district,6 J6 l$ e; Q' _0 E0 b
                         str_road,
    0 m- A) N# _( E                     str_no])
    & R9 V1 s* b% V4 V& Ys.str.replace(pat, my_func, regex=True)
    2 A  d! R1 J$ C+ n9 G
    . H! R7 S& D$ @1 W9 M1! g( U1 D8 n# n  v
    2
    0 E0 E' L3 X, s# N  d$ A30 k5 W0 i) T, `+ p/ a" v, g) i9 k
    4
    : P- t  h. a$ P1 S0 f) b4 B0 @5+ w" G2 X% k* m6 ^& e1 s
    6
      e+ ~- b6 q, q0 `7
    3 C3 ^) k% r! F! u8
    : k8 i$ L8 }4 O) u0 v- [9' [2 C* P% r  Y' x7 U
    10
    1 M1 M6 n) [$ l3 q. F; P11
    . p6 y7 X  j% l121 G$ p$ a1 b# f6 K
    13& ]- |* I4 ]/ ^- f* U
    14" G5 n' D+ e. Z
    15
    . \% N4 h4 Q- t16
    & L7 G+ {9 A0 o. V+ e5 d17# m" P% y+ Q( G9 N9 u
    18
    6 ~' Z4 n6 F* L. \0 W19& O& P0 I# I( c$ Z+ S
    20
    - R+ S. Z1 d5 i0 D# D) N  n2 O- q5 Z21
    4 u  p5 L( B5 c1 J5 {& A" X0    Shanghai HP District Mid Fangbin Road No. 249
    6 I' r* ~; z% W1           Shanghai BS District Mishan Road No. 50 q0 U" s+ \; c& k
    2           Beijing CP District Beinong Road No. 2
    * o: o3 x9 m/ \' g, [dtype: object+ ~# Q( D: Q4 Z5 P3 w
    1% N6 k4 B. x, g/ }) ^" C
    2
    % y* z7 p' s( p+ s3
    % z4 ?8 t% a$ c7 J1 F- s4! j( y% }5 Z* W& d7 l
    这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:, v2 i0 s+ D# r+ o& q$ X  @5 s
    5 ]) O; q# N9 L, D  z
    # 将各个子组进行命名: ?- m& o  |% W; M8 J- Q. E
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'( O2 h$ @& c; I0 b9 l' h. y
    def my_func(m):. Q$ ^4 J7 B* e0 v
        str_city = city[m.group('市名')]9 |8 T( D1 C* x
        str_district = district[m.group('区名')]
    $ F0 {+ ]+ X! k5 \# o3 y5 l    str_road = road[m.group('路名')]
    + ?9 b- I2 B% ^5 _; _    str_no = 'No. ' + m.group('编号')[:-1]
    ; |6 S+ Y9 C: }6 n    return ' '.join([str_city,
    ; z0 Y* X) a8 N0 ]: z                     str_district,
    1 u+ a. T3 _- v) J; o; a                     str_road,
    0 Z8 j9 p# O8 [& }% `4 ~) Q% I1 W                     str_no])
    , {( v* F9 ?3 G- _: O1 Ls.str.replace(pat, my_func, regex=True): _) D+ {" V& ?6 c
    14 F# _7 n1 \9 X* s% b. A+ J% C
    28 P- P2 Y, a# D. `
    3& E6 z& t, P9 ]8 n* d
    4/ Q( t) Y1 b: [) _6 U; _
    5+ o% m2 y6 Z9 Y1 l4 A) D
    61 A4 Q. T4 Z/ V0 h
    77 g) u- A) d; i: v& d2 O
    8
    " L8 T" w$ `& K2 g4 B3 F9+ k7 a1 Y/ Y, W7 l( w# a
    10' F" y" y! C! z( g
    11
    ' t8 Q" p, \% o5 |! h4 q$ T121 K# A) e( k/ B; a6 w
    0    Shanghai HP District Mid Fangbin Road No. 2495 ^8 Q" d# G: j3 Z; [' H$ q0 D
    1           Shanghai BS District Mishan Road No. 5
    % T  R9 R0 S' p/ n! I2           Beijing CP District Beinong Road No. 2
    3 H: C1 L: l0 e8 ?+ ^1 _dtype: object& ?, t, m+ _7 M" y' _" m9 G: k: @
    15 Y5 C: h1 C. @: W+ [; K5 Y
    2! G! Y5 |! A( d$ g, Q
    3# I9 s9 }) l- A4 s0 F- K2 R( Z! ~
    4) M4 u, ~5 {! e/ C6 i; P+ k
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。# [6 }! k1 @6 h9 B
    4 z7 U% j8 e- Q% R) U
    8.3.5 提取6 W. V% G; ~$ `" E, \. M
    str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:, w1 w% \2 D" B
    s.str.split('[市区路]')
    ' O) b+ ]# Q; u- V% M4 U1 ~% I. dOut[43]:
    9 @' h% S. w; @# W, l6 q0    [上海, 黄浦, 方浜中, 249号]9 ~: V" v/ g+ S0 [4 E
    1       [上海, 宝山, 密山, 5号]" r& ^( h8 k: ~4 e. j7 E
    dtype: object6 a# Z" k: ^$ ?3 V# T: x7 c

    ! ~% o7 C9 ]( b( mpat = '(\w+市)(\w+区)(\w+路)(\d+号)'0 _6 `- s- y2 _- y
    s.str.extract(pat)
    4 \! y1 A' p% F  w" uOut[78]:
    ) R! @! X. Q; s+ C# r    0    1     2     3
    7 H( t2 O' o+ J; p$ }) w0  上海市  黄浦区  方浜中路  249号4 h1 S( o' W" V' O- ^2 z' N
    1  上海市  宝山区   密山路    5号
    & B- T0 d& d7 B" r& C% c2  北京市  昌平区   北农路    2号
    3 t1 c( K; W) C1+ d7 f8 M8 `$ D; i
    2
    6 @! l) |+ x. Y7 s- N( q3; q& M  G5 g; l( @
    4
    % I" G3 u" ^7 v5
      V+ ?1 m/ P. h; l! O6
    : [/ {$ e! q" e' n, R9 @% Y3 Q6 u7
    7 z2 b( h. U& R1 s& r8
    & n$ o  `# H* d& W# ^* ]# U' P9
    " o# A7 j4 D1 V: _* v" f105 b) d! ]6 w3 z1 U+ u
    11$ B  r9 i9 k4 P& e( o2 h% z
    12
    % q5 b" L# e9 W: S) {) s9 H13! \1 v7 o$ F+ N# ^! R8 e9 Q0 U
    通过子组的命名,可以直接对新生成DataFrame的列命名:# ]2 L- o8 [' d/ ^
    4 H  P  T; t0 M  i  M" F
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    1 Y4 q$ d7 r5 ~0 l9 n$ As.str.extract(pat)6 j) u2 I, c/ a8 W+ }% X0 }
    Out[79]: 2 V; ~" f+ p, V/ e( ?. [; g* Z7 ]
        市名   区名    路名    编号
    - }1 k5 q4 F. ?" v0 o- `0  上海市  黄浦区  方浜中路  249号( J0 L; B4 w" C& j: P! K) X* i" J
    1  上海市  宝山区   密山路    5号
    ( P/ W$ s+ s: X* g4 x8 u2  北京市  昌平区   北农路    2号
    9 _: k; ]6 u$ I1
    7 w: c5 ]- a+ m. p- @2
    ) I9 a: S1 g5 [1 a) B7 |2 g3
    # P1 P# F: |; b' j  f* e4  y2 o' I6 p  e: j" E
    5
    # M/ \( O6 D- N& d$ R6
      a6 m( B7 \' W5 z8 e2 g/ \7
    : I! Z  K# H; q4 G( `: a. x6 {8 Qstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:$ F- I1 X$ Y& r& j( {0 f. ?- v4 `
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])/ n  O8 A8 n  n# k
    pat = '[A|B](\d+)[T|S](\d+)'
    2 q8 u0 v3 v9 hs.str.extractall(pat)& l, m- R; x  F: M4 x
    Out[83]:7 @' E& c4 w  V: l5 b# ?
           0   1' P  s0 j* [& E, ?# e, C
         match         ; M9 H4 @* x+ \! l, v6 f/ H
    my_A 0      135  15
    / Z% G* i, ^9 J3 |9 M0 \     1       26   5
    ; G" O; U; E, }( z" Qmy_B 0      674   2
    / `2 _. p0 P- e& K$ [- u( x# Q     1       25   6# W' p* C( l/ i! O9 C, o
    1' \4 v) ], `" @- J: O# y1 J
    2
    + S7 S3 C) _# T& U# O# v3
    ) y. D+ n1 r8 N1 _4
    , X( w: u: H) R4 A6 B; y& n5
    ! p+ Q9 M1 t# M6
    ) k$ ]" M# _1 z9 r. |. G0 G0 x7
    3 @$ H: Z( u- C( j* E6 v85 I( j; y; H4 w+ T$ \; _& e
    9; q' m3 K6 G+ _7 a
    10
    ; j; U4 U4 J/ m* L' }; Npat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    ( t* \, Z4 b& D9 Ys.str.extractall(pat_with_name)* r. G: S  Z+ X' V0 Y
    Out[84]: ) f+ N, C9 }8 O' y  J( ?( C) t
               name1 name2
    / k' k4 E" i& ^6 W% `0 G- s2 O     match            
    ! p' g3 H8 E) H# \7 Qmy_A 0       135    15
    1 G% G5 @2 K2 N  C     1        26     5
    ; T7 E) ^6 Q5 s3 ~/ Z, N- d' H  Amy_B 0       674     2
    % X. c7 U8 k+ V7 \2 A     1        25     6
    - i5 u; t* ~  r13 m6 S; T( h6 O7 W5 p0 e
    2
    ! `! w4 _6 j! w. f3
    ( E$ D. [/ {8 j0 G+ k  W4
    $ v; M4 [& {  I3 e$ `. K% |57 W# e- n1 R1 O% ~% Z
    6
    8 A  x* A. h9 R7% C6 Y8 ~* P5 i6 ?
    8. u$ L, F. T: P' u, m1 P
    9- b# O, j  @& y( d
    str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。( f  o0 X9 |% A& G) L3 r3 h) J: e
    s.str.findall(pat)/ @7 @5 G8 \$ ^6 K. e% a1 v2 s
    1
    7 H6 w& }9 _" D) ]& [5 j. f8 emy_A    [(135, 15), (26, 5)]
    ! z# G& c. M7 ^0 J' [. Y1 U3 mmy_B     [(674, 2), (25, 6)]4 B! O; z8 z8 Q5 B
    dtype: object
    1 x" M6 x) S# X1
      R$ L7 u  d5 D( L1 Q2
    + S5 m# N5 i5 n( A3
    ! w8 p* x7 @5 M8 i) C0 Q8.4、常用字符串函数3 b' }7 B2 ^2 v$ p  L! H6 D
      除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    ) d5 k$ a# L5 i: n- w
    9 x3 [  h* y/ N) r4 ^% r1 o8 m8.4.1 字母型函数
    2 W6 A7 A: Q# R  O5 J2 ^  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
    9 T- p9 S- V1 x8 ~, p; w+ J& S8 j6 X5 h1 a8 M0 q6 m5 A1 ^
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])3 h  v! H& i! X6 ^6 s( Y* S
    $ n2 G5 k5 H1 v1 w* @. P
    s.str.upper()+ r/ F) r6 B6 M+ Q
    Out[87]:
    ; D. i" X& T# m' Z- O6 P7 \' a0                 LOWER. v7 y5 u4 w+ p, _: q1 o- q
    1              CAPITALS4 W! q& {$ Y4 h* X, N4 e, {; b/ Q
    2    THIS IS A SENTENCE/ J2 O! t! \$ n. `1 g/ N
    3              SWAPCASE: d2 D) ^5 {' R( @6 m: m. @; X
    dtype: object2 F8 o1 h% v+ g0 s
    ) m% k7 k6 a) v9 {
    s.str.lower()5 ~. }; Z4 d2 k
    Out[88]: + j/ j6 Z' d; @& u" ^
    0                 lower
    5 j2 V3 @6 [' L8 G( U  I1              capitals0 q) b( J# Z- c$ @/ L) j
    2    this is a sentence/ u  ?/ E0 x4 m5 I8 I) d
    3              swapcase! j, l& P* }: A
    dtype: object
    + I! @- j" A+ [6 Q8 h. L3 B' Z) L  V" n" W1 P; t$ @
    s.str.title()  # 首字母大写
    ' ^( Q, d1 A7 {Out[89]: 1 @. l4 y% R) d# U, m
    0                 Lower
    6 E$ |1 Y  P3 ~) O/ U1              Capitals/ {, d4 G. D$ I5 P7 _& r% c
    2    This Is A Sentence; M  Q1 A) a( {! S, p/ \% P' T& M
    3              Swapcase# r* s/ q7 O. B# O* n8 ^
    dtype: object4 M! \- s0 ]9 N' m

    5 T7 ~$ A$ f$ {* @s.str.capitalize()  # 句首大写
    ' F$ n: x: Q% W! _3 tOut[90]: * F% Q4 p: ]- B/ Z
    0                 Lower
    % I' I. ~) K/ U$ [1 ?1              Capitals8 Y# m  h9 k# A) q
    2    This is a sentence
    ' {7 z, Z2 D3 }; z- J2 F, I% L3              Swapcase' U$ f8 ]4 K0 \* x- w$ f
    dtype: object2 Q: I# |9 v8 `9 m0 ?* f- |, A
    4 H0 c" ~# `4 R9 y& {/ t2 j
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。9 V$ D4 z. J4 D4 `& B' ?! f9 t& z
    Out[91]:
    # E. P0 |3 H0 Z* ?( R5 B* m6 B+ ]* P0                 LOWER6 H2 v, p; s2 P5 z/ s) j
    1              capitals
    $ t. [4 q/ @1 J/ x& W0 f1 W9 V2    THIS IS A SENTENCE1 O3 o7 X* p) O, C/ q8 f
    3              sWaPcAsE6 t( i/ X& U2 {" X( R
    dtype: object
    ; `3 B4 f2 A; e# u6 ?2 U# R% D0 L* i- k7 @! X
    s.str.casefold()  # 去除字符串中所有大小写区别+ k+ ^: `+ J/ \$ ~- B
    # L' Z  [9 i1 |& h$ H0 U
    0                 lower; D4 O! [1 N" o: W2 g
    1              capitals
    ) t0 e2 q& M9 A1 ~8 B0 N2    this is a sentence
    & S) b) r: _- P: t3              swapcase
    5 ~, J1 M+ a# d) }: s' R+ Q! q; h) Q) a5 s) z
    1! M) ?- K  ^6 J& a
    2- @, n/ U+ G3 V( P$ k- Q' y2 ]
    3
    5 |% J! \$ e/ ~' E) s8 Q4 I4
    6 ~6 |! e, P! Y  T& y7 f) H5
    ( q, Z8 r" P* X; N" H, L! b' n6( T& _# z. g* W5 R: o4 Q- U
    77 P3 `% s& K; m8 n* U6 [' d
    8
    4 ]% }5 O! c! Z0 `3 J  u* c9+ U) S' g1 `7 _! {( z1 J
    10* W# z' a+ l6 ]; K8 v
    11
    1 ?+ ?- p* e  O) s" f1 W9 W12$ m- Q0 u! c" G3 e3 a
    13
    3 t2 ?0 s4 f) a; w  R1 {7 m- _14
    " [; h) l! c. k9 {5 A7 u3 @% E15
    5 j& B% J. |& r: d- W: `- V7 Q& Y16
    / m( K4 l7 ~+ _170 Z( |6 U3 t' B: G
    188 T) s6 s. q) @0 g; d+ r
    19$ \6 D! y: a2 _9 F
    20! L5 f9 n. o* m8 C$ I
    21: ?- G. R8 G% [3 H# p8 x& t
    22  N4 B" [  H5 B5 E! R" w% s2 Z
    23
    & l4 L0 @3 `! R$ q24& _0 Q6 v! X) s. J3 ?4 ^$ y, Q' S
    25" R* `7 |/ P: F( T8 }. P5 `8 Y
    26
    - ~0 X+ B4 ^/ ?3 h" r7 W! ]279 Y0 d2 q! m2 D2 m
    28
    $ Y+ x& A5 A/ c( ^" j295 i2 o$ G/ }$ p
    30% \# \  u! z4 k2 n8 U
    31
    0 A( I* Q) O* x  D( h) W, K7 H# r7 v322 A, s1 ?! {. X( a5 z$ [7 m
    33
    ' c2 g7 P6 ]8 [8 z34
    3 c2 c1 I# }- z! O5 k" B5 }) }7 s35+ G% K- j! R2 M3 K* q
    36- x( ^4 \+ @$ J( d5 k5 k) I0 |
    37! q6 ^9 _/ V) {0 R! M# n# i2 H# d6 r
    386 V( v- p* d0 o: `  \+ [
    39+ V( E7 d" ?4 r7 L" ]& G
    40& u4 e; D+ X8 y' I; T
    41* i- f9 Y* V& `* [+ H
    42
    / ]  n; r6 h0 {4 M43" u/ W7 b2 w: w
    44
    5 O0 L: `3 Z' j2 k5 R45
    - o6 V! f* Q+ U' E46: B9 {3 {7 E: I- f/ P) {6 G5 k$ t; y5 E
    47
    - {# j0 u2 f/ l% }4 P) u( T& C9 \48% Y" _9 F# }' A$ k8 u* T5 ?' P
    8.4.2 数值型函数$ H; x( N& y5 g/ t: ?3 D- Y  H
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:. c5 k" Q! f/ `! G0 A
      @3 T* Z7 p. ?, M" f
    errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:3 S6 [- X) g4 h. i, t
    raise:直接报错,默认选项
    * O+ K8 C: }1 |/ Z; ncoerce:设为缺失值- ?- t0 K, R* i2 Q$ w7 f
    ignore:保持原来的字符串。% p" W4 z* ^4 P6 N: x5 Q3 L
    downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。. N2 s. g$ N# n- e+ N
    s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    % }! o4 G6 u& X) m4 H7 Q- `$ S8 D8 Q& T  B( g
    pd.to_numeric(s, errors='ignore')* v! A9 A4 m5 M1 d. a
    Out[93]: ( ]( {/ ~, P+ p# M/ Y0 n' l
    0       1. }  z5 c! c* |. S5 ]
    1     2.2
    0 Y/ \4 f- M$ _% y2 Q2      2e$ O0 |/ t0 n/ N' x5 L, H
    3      ??# L$ p% ]" a  b9 ]$ ^
    4    -2.16 ~) w6 z) X, `8 ]% R9 i
    5       0
    , t( A& q# F: P7 ]  R+ xdtype: object$ B& H6 P3 n( [7 j7 `

    ) L; l# X- u- a, L0 Q+ Qpd.to_numeric(s, errors='coerce')
    1 y9 @. `* E+ R& f& VOut[94]: # ]5 Y3 U) F  Z
    0    1.00 ?1 |; q" U% u! o# ~! k
    1    2.2
    $ e' ]7 R6 `0 X& y: r2    NaN
    & E+ z2 b! ^3 u! C$ ^" |* u3    NaN
    + C2 O5 b* @* b) b4   -2.1) x' o7 l0 t5 e0 W
    5    0.0
    : Q; J* x, N8 cdtype: float64# n3 @! T  X* w  M& B! C6 \
    ! c$ w$ _1 @+ k% n4 r# y5 a
    13 G- ^- Q. ~) s6 z; O
    2
    ! q* S7 \* E" _5 ^3  T- V% X  U1 H. j4 a# L* L
    4
      M( y4 N* P* W! p+ i7 ?1 m) c5( K' }3 C1 D# r- P. i8 Y
    6
    - \6 L1 `$ _8 D6 Z7 ]' b# _: \2 o7
    0 k% I+ ?+ {2 P8
    " a; d' k* `/ U9 D) X- ]$ E6 H94 e( b  q  h  X9 r+ K. e$ K
    10
    : I& R) {0 v3 f3 Y$ w11
    ! d% ?( i7 f" Z- K% d125 I3 O; E0 `; Y1 v! z: p7 _
    131 L0 Y1 s0 w5 f1 {/ e
    14
    . p- E& @' R: t# P' A7 A15% }0 V4 @3 H+ h; v5 k
    16, e9 X9 d8 X4 ^+ M1 g: M8 }) T
    17; A8 Y; e$ t. Y4 U/ Y& [
    18
    ! W; }+ E2 Y% l  Y6 _# e19! t$ j7 _8 B& ?& i( y
    20( O" H/ x; }  u9 o4 [. z7 V! d9 e. ^
    21
    / l' g3 Z5 _' P/ l  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
    $ @7 ^2 {( p6 o6 ]( F% o; X. O: U+ H8 w8 T/ _
    s[pd.to_numeric(s, errors='coerce').isna()]
    " C. J9 T. ]5 NOut[95]: 9 F& ?! G# C# f) U; q$ X( J/ l
    2    2e
    4 f, f; q" b$ S. E8 ^3    ??( e6 ~3 Q( ~  b) ?% c) `3 I
    dtype: object0 |) k: x. L. o5 @6 }! N) v
    1
    5 y; F' A5 y9 o  h5 E2
      z1 k7 u" F( k# g# ?, V' r3 [2 `" a3" `; z+ [# c  A7 {8 I" k
    4
    : W/ z+ ^: Y2 O5
    " M  f1 Q$ ^( F8.4.3 统计型函数+ `4 M5 k8 M2 \
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:$ ]. L- a: Y& \/ U! u: S5 x3 ~! }- s
    / c" ^1 l4 V0 `. J/ L# ^1 w
    s = pd.Series(['cat rat fat at', 'get feed sheet heat']): F# Y/ Q: _+ T: e; ]1 L

    % q+ c* D5 O; t* V5 ]2 p) P2 os.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次$ f2 _( F- n3 f7 D7 J, H
    Out[97]:
    6 K# s1 z! S9 L& I0 g0    24 m4 P2 {8 c& ^+ `# ~2 N; m+ K! x! E8 F+ ?
    1    25 W) x5 m6 c5 T  S& k- ^! y# c
    dtype: int645 W& X- g5 B" X8 X* N' `
    + k$ s9 w! K  n, \
    s.str.len()) a/ h' Y: K" k7 p* g: o
    Out[98]: $ i" L( q. L  j! O) \
    0    14
    : |2 e2 [* T+ j: c1 Y1    19
    . F! |- g1 e; y5 B1 P8 ldtype: int647 ]1 c' k2 ]4 V
    1
    % T+ r$ X5 J* F* {% d5 K29 z, X$ L% b! t. ]$ I, |- z7 E
    3
    " G" \) x- W1 R3 J% R1 o8 Q# m3 N) k46 ]  d: g8 }, g% k6 k+ L: R
    5
    $ p/ K+ v, B: y5 v/ v% z0 L6: D& M) W# C4 N% l5 F
    72 ~  ]; Q2 C3 e- d
    80 |% @5 R7 z1 n( i0 S) s7 ^, t
    92 v& K5 ]) |3 [0 H
    10
    ) s5 f2 V( Z# R$ V11
    - I* q7 `1 N) Z  a. a- E6 C) h3 ?12
    " d& |7 [" W$ K* }! F( K! a7 Z0 _" J13
    - i8 S6 q  C. F& \( ~8.4.4 格式型函数
    + w5 Y% _1 e+ a) b  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
    4 ?: [4 e7 V3 H9 e. g4 R' z3 J$ F/ T
    0 @  a# \+ E6 [* S' y4 V7 }# mmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
    $ E4 o* A' [. ]: B1 L9 e9 d3 C( S) }7 G- ?
    my_index.str.strip().str.len()
    ! A+ U7 s! n" L2 h7 ?Out[100]: Int64Index([4, 4, 4], dtype='int64'): @: f2 L% d7 b0 U1 P3 ?
    7 r- j1 L* b9 p6 J8 B- I
    my_index.str.rstrip().str.len()
    4 h# |2 K2 J6 u  ^2 M2 `$ COut[101]: Int64Index([5, 4, 5], dtype='int64')
    2 ^* O. l9 u( O6 |, [  [' N
    # V, P' F3 E+ L! e5 A- Rmy_index.str.lstrip().str.len()3 Y. g9 Z. X$ y, D
    Out[102]: Int64Index([4, 5, 5], dtype='int64')
    1 O: @* R4 u$ m8 `& q7 c1
    + ?1 F. e$ [# M9 G1 g2
    , t3 r+ _; ?0 m" a; f8 p4 F  o/ I3# u' n! v, g) f7 R. i
    4
    - f  P1 k/ C; l% f1 o7 n50 I1 y7 j" E; S, }5 w
    6+ J5 \. H2 h1 z/ i
    7
    / ~  R5 ?8 }+ k) W- w8 }, v82 y' Y; g  m4 r& ^5 g
    9
    ; K1 f6 m+ Y* Y3 D! h/ \- q$ b% C10) O0 M/ j" Z  ?$ D0 X6 M1 s  u* J6 S
      对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:. E4 F( e( K7 @8 ?

    3 i' k1 Q) v7 G! as = pd.Series(['a','b','c']): R5 I& [$ C% k: d5 B* P1 v9 X! B

    & V- b0 c# m6 O* Z7 T9 G6 ds.str.pad(5,'left','*')  _  f* S! n% x
    Out[104]: 1 z+ \' R3 R: V! r4 H3 x/ |7 X+ Y
    0    ****a
    & Z, x# F8 a$ j1 b! A& U1    ****b
    9 Q1 i1 L9 u- Y9 E2    ****c
    # y5 i( J" w, O" }2 Bdtype: object
    , [/ |! U% Q! S$ t
    / }& Q. I: a$ Y  x2 e0 Q/ Is.str.pad(5,'right','*')
    4 R# r2 B* H2 @& X) _0 S# ]' BOut[105]: . {' {3 Y% ?! O4 C% f& C2 S
    0    a****' J0 O5 M( u# m8 o( a
    1    b****
    ' J8 R$ R( ~- D+ k2    c****
    : z; m, Z1 I& E' ]) V1 y2 M) Edtype: object4 a1 I0 y, G! v3 i. g* G+ h

      j+ T0 Y5 R) ]+ e3 O; X1 p  ~1 Ns.str.pad(5,'both','*')' s, }6 J2 ^; I" V* ^
    Out[106]:
    , w  z( k$ ?/ u( c% I( O, ?0    **a**
    0 Z0 T8 ?5 j4 a' ~3 n1    **b**
    3 ?* j& s. ?! V% Y9 j2    **c**
    6 i5 \, D( R' ~5 q8 j! x" k" Q1 Gdtype: object
    # p% R7 a" P: y
    * _' k, b& y! C: A+ d* s5 o1& o1 _- b% n4 F: m0 B
    28 y) C9 |) D0 @4 b5 ^
    3- f, \6 j/ I2 G2 }0 u; ~
    4
    ' R8 v6 r9 f3 Y+ `5
    % o( `  O/ e* y$ b* G7 p% d4 J6
    + O0 ]4 S- r! d* [. v) t79 d: |* w' d  q
    8/ D) D+ _" n; d0 A
    9
    , W- `$ q/ y6 W* }9 b1 j' b( V10% ]8 H% \& t1 D; W7 a9 a
    11
    : q2 d9 ~9 r& I; i" A; ?5 ?* p12; m! z- J8 f7 G" M
    13
    + g0 a6 g6 ]7 i- u) s14
    5 ^8 Y4 M$ D( F( T6 r  }# L15
    : [0 {* ~8 v6 v6 O16
    + o; T0 I# B% [$ d% b8 g$ Q17/ ~: j& t, ^- T( M
    18- W7 k) k" @5 h, U
    19
    $ }8 A9 ~/ f0 i' C8 z9 ~2 `20
    % Y1 G, W' P8 h21: U6 D3 L) @- f) }* S
    222 q( T. ?$ P1 L( r& ]
      上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
    ' d6 H* V  t* e( U
    3 h' M% O* I1 Q, _, r7 J; ss.str.rjust(5, '*')2 [, y; b+ N" u# o
    Out[107]:
    1 c1 d# c6 M, g  n% W  N/ T0    ****a
    " \8 U8 l3 ]6 }# A2 t1    ****b
    : \4 y- M  k6 C; w2    ****c6 _! a* w. S: D; _; E! _: K+ J2 e
    dtype: object
    2 W! F+ T- {2 X  U1 ?0 s  w& l1 g9 \% t# l" s) d/ k# U
    s.str.ljust(5, '*')
    , g: Y" y% k/ M& y4 ]Out[108]:
    8 s. |  ~# G. T6 z9 b, ?* {8 F0 w0    a****
    9 {/ }" p, X; e/ }4 [1    b****
    7 j7 j  w9 k0 r# a2    c****
    ) t+ T+ I" W7 f2 tdtype: object
    8 ^4 s$ a" ~& @2 n6 L% m, o. |! h( m6 V! Q3 s3 Z
    s.str.center(5, '*')
    / i# X' G2 v1 n% \/ X6 Y: xOut[109]: / h# H: w5 R, t  m
    0    **a**
    % F9 `# _% `1 z4 @# n! b3 J1    **b**# O( ^  e1 @" h+ O- M
    2    **c**
    4 I+ o& W/ F1 t6 V: gdtype: object4 s; A* ^$ P% j  N8 I  R
    $ C6 w& A, j, [9 O" x
    1
    2 ], c2 M% G# i% S' U2/ x% g7 e' d5 P( p% P8 p/ h
    37 z6 e, w# Z3 |  }1 Q
    4
    , [. p$ I1 z5 D- z5
    6 }# C; l, W+ ^" ~7 W6% P  }" T) C# P1 d
    7( F1 y" A. J# P- q1 H
    8% T7 R2 w4 z  w4 R2 L' M; h. J: M2 J4 f
    9
    ! d1 q; a4 u' c  Y10
    # r" i* g7 z$ b: E# w11
    : ~8 c% i7 y. H12
    3 I& ~* j) O4 F5 ~. ~13( h% W, Y' R0 V7 ]4 W
    148 G: ?3 I; R; W
    15
    $ f  c+ p- U! x2 Q8 q' ~9 i7 `161 S4 E# L: @/ ]' i
    175 `7 }3 M! V- |4 Q  V
    18( P  ^; M8 C/ g, n% q2 h
    19. _6 \2 ~  D. c3 i4 z
    20
    4 @# D3 v( D1 f5 |  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。) f* v( x( i4 u: s) j5 Z5 G

    7 P) U1 k* o1 [4 vs = pd.Series([7, 155, 303000]).astype('string')
    # C7 F# m7 R1 D0 ^: E9 `9 q. R
    / N$ p0 m8 W! ~s.str.pad(6,'left','0')$ v, @0 h8 s, y+ n* M
    Out[111]: 4 Y- }$ p+ f, L4 R
    0    000007
    $ u- Q: s2 _0 f4 p& K7 V7 `1    000155
    9 {- y& G( F! c! Z  R2    303000
    : |8 _- Y' h9 R5 S( Gdtype: string
    3 {# X: G- [( {! I. x
    , {2 N$ K3 W+ y* Y& H3 b% ?5 N8 ?+ ws.str.rjust(6,'0'); [1 \+ J4 n) ~: \' v
    Out[112]:
    : `4 l; d0 E) Y* t3 X$ P, H) \0    000007
    $ `) ?3 H* Q, W1    000155
    8 k/ R3 W" F: ?- U# Y' D3 f2    303000
    " v& u& [* K. Z/ L6 R/ w. n! \dtype: string
    : X) ]' ~% r6 ~7 y, W  T4 a; E! T
    * j  N' {# i1 Ss.str.zfill(6)
    # ^! }% l2 N9 t! g( LOut[113]: 4 \* o2 O- M" r/ p
    0    000007+ L# R( G6 ?) V% c& w$ U% X
    1    000155+ V1 d6 S1 Y  J/ `- ]( {
    2    3030000 q# k8 t/ R$ Z) S& R; E2 r
    dtype: string# c1 M3 Z1 Z' r3 k/ y" m

    ! ~" M# Q& l  U+ f0 m; C9 C/ i19 f2 o2 O6 k( _4 k
    2
    0 O6 K! y3 N, N0 j6 ]! o3
    6 n$ V$ T/ b+ f4
    5 u5 u  j* o, q4 ~0 X57 w8 l+ L. d3 P. a
    6# y+ H, E2 ?& c0 N
    7
    & k/ k6 Q4 \- x0 t$ l! l8! `& }7 A8 r: B0 l) x1 {- U0 z
    98 }: }8 C2 \( A/ R
    10& E/ g4 [  e. m# H' |
    11
    6 f8 r# ^2 z  @2 r9 X; L: v" t2 g121 ]: o; a. t. \) Y
    13
    : n9 }  S5 P6 u2 f14
    7 [. {; {+ ~% Z% m' ^) ^# I' h15. c) ?( u5 T0 P6 v; W- S; \8 y* |
    16
      o  N. y; Z+ M) R: e17
    : g6 \7 a+ P4 t3 A18
    ! ]! E/ E* L( q8 R19
    1 Z4 s! w* x: [. q, D5 v208 N* j# h: r" S$ {
    21
    7 j3 n% k- O3 [5 e& I( i; u22
    3 B  A) u% U9 Y$ g8.5 练习
    ! _. i. L) M& y& y3 p) [$ X# O% C% FEx1:房屋信息数据集) o: l" u& [+ F' Y
    现有一份房屋信息数据集如下:5 j& R8 B. J4 o: B2 Z8 l  p/ v
    2 c  S5 L2 d, F' T
    df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])* s$ T* x; L- w' J
    df.head(3)
    9 f! v- n; \3 q/ vOut[115]: " ]1 ?# z2 P! [% |% v3 Q" S5 Q
          floor    year    area price
    * \& q! G& U/ J( E% z8 i+ b0   高层(共6层)  1986年建  58.23㎡  155万
      l5 o- e9 L: H: s1 C# ^7 m1  中层(共20层)  2020年建     88㎡  155万2 `8 A; f: Q: }' T4 j
    2  低层(共28层)  2010年建  89.33㎡  365万
    + I% o+ V  Z' o" S/ b% y1
    " n/ j8 L( {& E. t2
    - u% Y7 T% |, |" U) h$ S3
    4 x2 D4 E1 d4 ]4
    2 b$ C. \. \  U0 Q2 M3 \' u8 L55 ?# C2 b. L/ O& L) B0 k1 v
    6
    + P' c* o  c2 C# J2 A! M72 U, E! P; z- a2 E
    将year列改为整数年份存储。: Z7 h  F" g) j* k
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。! e) @) [. }/ Z# O) q
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
    . ]3 C0 T6 S2 y0 c将year列改为整数年份存储。
    3 w3 w* f" Q. P/ @- s* K"""# K. q0 b) `' o0 s
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
    ! z6 P, ~* H% A- k9 s注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
    + g/ c/ K8 Q/ a- P- u7 d- U) `% W转成int后,序列还有缺失值所以,还是变成了object。
    6 ?6 n* B( O- e; j  e' p# x. c6 F$ e而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
    " d# r. C. B6 y9 E* {# Z1 v4 X2 C"""  ~* b, E) r( g: v" M
    df = df.convert_dtypes()7 P- l1 W$ F) ~# Q
    df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')% T4 E, n, u3 {& R% N
    df.loc[df.year.notna()]['year'].head()
    0 H; I' I3 g) h6 z# L
    6 p, {( Q: p( {# i# H. ^, @4 [5 ^/ J/ T0        1986  k% }# Y# _) X) n
    1        2020
    $ x. s% q/ k5 w& {: ]! {2        2010. K% V- l4 B' K. l
    3        2014. ?; l* H- Z  W( U: `
    4        20159 U" f! S8 F  D
    Name: year, Length: 12850, dtype: Int64' \* t' j  z$ A& t7 F$ C
    ! n" R+ q* _2 A+ L$ I
    1
    7 G) w; w; o* Z1 v7 Q% d2
    - z! a- _1 r9 S33 l7 w: @8 K2 R* h
    43 c9 Z2 t7 l: W' c/ a& s0 U
    5
    8 N. @. k) I7 n# U; ^9 ^6
    * R# L: C% j4 P4 u5 {7
    0 J) b5 w% b4 ]: H( G# G* @8
    + p/ b+ L$ Y% v) {# `# Z" ]9
    ' `" @( b* l& T4 n* Z109 t* X+ c  o: ~/ g* L
    11' V. K3 ^) i9 q6 C6 _; B0 _( h; l2 q
    12- w8 [. [; X! _* ^
    13- |4 m& P, K7 m. ^; i, S& e
    14
    : [8 c1 @& S: r' X/ R8 X( N1 l15
    1 @: L) M, g* Z0 {- i165 P1 e* p2 O1 c0 M9 G
    参考答案:+ k% {! j' ~5 e: k" \/ b
    - z' N. M) X4 G. p6 L
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
    0 @0 J+ m8 j. i( u' S
      ^! J3 w  R' f# B% s9 Udf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    / s3 x& s: L& A* g, [& Wdf.loc[df.year.notna()]['year']: ?* b$ D5 h- B) n8 q2 x
    1. u- S% k1 W7 X8 `
    29 g1 Y# L/ ~+ b) u# x0 R" V
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    5 @& {9 V# N2 k5 y8 J" G8 fpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
    1 p* f' z# a3 m- q* }df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次/ s% q0 a1 k. Y( y0 ]9 P0 c/ W
    df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型
    + U9 J0 q. V; e1 X, K9 Udf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
    . v% h7 x: X5 B. O- f$ sdf=df[['Level','Highest','year','area','price']], J& O* I/ e% Z: ~# n0 O; F
    df.head()1 t" a3 x$ d" M5 W- z3 ]' v: c
    , V8 V4 P* O5 z) ]
       Level  Highest        year        area        price
    + A' v/ d$ `/ c0        高层                6                1986        58.23㎡        155万0 u8 u( P4 F8 g9 g2 |+ i
    1        中层                20                2020        88㎡        155万" e. Y7 p  C* m
    2        低层                28                2010        89.33㎡        365万6 D4 ]6 J8 W2 ^' \
    3        低层                20                2014        82㎡        308万, h; k6 H3 V4 W7 m4 E+ T7 Z- j
    4        高层                1                2015        98㎡        117万
    3 [7 }" q2 [) Y1
    7 c/ @0 f2 W7 |- G) }# K$ V2
    : }, x# ^& [# S5 P8 W3
    & Z- |0 A; \: A8 H4
    + y: Q. f: i6 }; h: e% T5
    * c$ ^6 A* g0 _5 f: d6
    9 t6 Q0 D: w7 f& B2 X- O& s0 o7
    3 z* b7 |  E6 u8 c8
    5 c; F) F: S1 l" I1 |: z' l9
    + a2 |$ R! B" o9 _% I# e10
    6 m$ o& z) z. @6 U+ s5 M11% i6 _& y5 }" c4 t% n' {/ D
    12
    ! Y) c! ]4 ?" q/ j. z13: D) b: h2 a8 ?9 y5 |' \
    # 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了5 p( d! e/ @  G0 B
    pat = '(\w层)(共(\d+)层)'4 [& E) h) S9 g# l9 [$ I- t
    new_cols = df.floor.str.extract(pat).rename(" x% l) b( S7 Z0 y
                        columns={0:'Level', 1:'Highest'})! |* }+ u( e0 i8 s* P
    ' V' G" z- @$ D$ A# i
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
    5 o3 R; R( q) }8 E6 G/ k! Z; mdf.head(3)
    * I  m% G& V/ ], v( P) T
    0 h1 K$ Q) u3 u( [3 b4 MOut[163]: 3 W9 t- X5 F0 S5 H7 `
       year    area price    Level Highest
    ( n7 F0 X! X; P7 s( `9 e+ k0  1986  58.23㎡  155万    高层       6
    , R: Y4 }' b% c0 ^, J1  2020     88㎡  155万    中层      20  c! r: ~1 f" N/ Q  F* h
    2  2010  89.33㎡  365万    低层      28+ F3 A! H0 C* N4 L# |6 R
    17 x! h: S; V/ K* D4 L2 E2 I
    2
    8 c+ x3 m) a) b& t* i3
    # f' {) B0 N: Q( A# S4" W. w" @/ }( N6 n6 ~
    5- F' r, P! q4 a& I
    6
    6 ~2 v7 s# B; F" e74 {4 ^; g$ [- F* S/ z0 a1 c- y
    8+ m" r# A% Y, w- ]% W) R6 a3 u6 G
    9
    + d# t3 L4 z9 Q4 |9 _% q10) p( S. c8 }. E, v( B
    11/ M8 h" Z0 F/ d5 t8 g; J
    123 G% E( c. e$ R1 f
    13
    4 R0 i' y- j- T) B+ N计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。, g, B0 w2 h4 z' k' t
    """3 t% q# X2 E2 k1 S" L1 {
    str.findall返回的结果都是列表,只能用apply取值去掉列表形式
    3 \& W4 w+ J% Q0 [8 W参考答案用pd.to_numeric(df.area.str[:-1])更简洁: v5 C5 c; A" `7 D! f
    由于area和price都没有缺失值,所以可以直接转类型- ]' @+ N' ~7 i9 b/ O
    """
    % R" e2 s; R+ p0 Y3 L1 h( m- Gdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
    . n4 ~+ ]3 V0 }% o: X- l$ tdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')& {! I* }  `* C
    df.eval('avg_price=10000*new_price/new_area',inplace=True)9 A3 x0 Y4 b; s* A! u& Z/ Z. G# k% }
    # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))& }# B- s" c8 j/ S
    # 最后数字+元/平米写法更简单' x; U) a4 d$ w- {. O0 M* \" J
    df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'! T, O0 _, A" @0 `0 N# Y
    del df['new_area'],df['new_price']
    4 @' f& a% Y7 H0 Y. [9 wdf.head()& o- ~* e! p0 r! q9 m/ |8 N0 h
    1 H4 J& G) F; ?" i
       Level        Highest        year        area        price        avg_price/ X+ }3 S' F6 b5 T2 P* [
    0        高层                        6        1986        58.23㎡        155万        26618元/平米- Z. r% h+ h& A5 \, g
    1        中层                        20        2020        88㎡        155万        17613元/平米
    7 Q0 X$ l' O/ |, Y2        低层                        28        2010        89.33㎡        365万        40859元/平米9 r  i5 |+ t; X  j( O
    3        低层                        20        2014        82㎡        308万        37560元/平米! j  A, V/ o) B
    4        高层                        1        2015        98㎡        117万        11938元/平米0 X: B1 v: N3 w2 \# \- x
    9 p8 m' [) }) F; w- |9 q
    13 P) \/ K* N0 U# X
    2
    0 m  G# s2 r9 H9 T9 e35 n- F1 [0 c) i
    4
    / G5 D" Q! u: x# ~5! O& V! Z: s1 l% s' e
    65 R, S3 G4 m0 D" ^1 ~8 t& d/ r  a$ b- g
    7
    2 c; _6 S, q5 X( ?6 o+ h0 U82 Y  }; }5 P8 T1 M
    9" i: y1 q$ k% v' \. [
    10  y+ Y& f8 L, r# |4 ~/ E
    114 w0 Q, X1 h4 h+ b9 x4 y: W
    12! B; G  X- R* e2 C/ E, o4 H9 i6 s
    13
    : K" P/ v" a3 @! w6 f14
    + j% t  X0 \0 ^) S! K9 `0 }% M; X* M15
    / c' w. s7 o# K) Q161 t2 D6 L" w- L. H7 F! R/ ~
    175 F, W) _5 `' d# @4 @7 ?4 R
    18% z7 L3 X, Y5 N$ p0 y2 ?
    19
    5 k& ?9 z' C  }% a' ?/ m; Q20% ?4 |; F, D1 {$ ?
    # 参考答案
    * I6 x# |7 M  Y/ K5 W) J" v2 Cs_area = pd.to_numeric(df.area.str[:-1])
    9 D6 D8 q6 R2 e* q3 Is_price = pd.to_numeric(df.price.str[:-1])
      k2 @$ a" W. F' E; }8 xdf['avg_price'] = ((s_price/s_area)*10000).astype(
    8 w& }: X) g! }5 V1 @0 z" E2 f                    'int').astype('string') + '元/平米'6 X, ^9 n8 \! H. w" X
    ( `' Y+ \  u: Z! j' y
    df.head(3)3 i( C8 G7 u0 P8 u
    Out[167]: % n0 b! S' y7 E2 N! C' A2 C
       year    area   price   Level Highest  avg_price9 J: y+ ]- X2 |8 B7 i; Y: x
    0  1986  58.23㎡  155万    高层     6          26618元/平米
    9 S0 Y6 t& J' Q* u: ?* _4 |1  2020     88㎡  155万    中层     20          17613元/平米
    4 E; s: z# c4 l0 Y  W: f2  2010  89.33㎡  365万    低层     28          40859元/平米7 M5 t1 y" N$ b3 ~. ]
    1
    $ V+ e8 j# Z' B0 N& O, @2, M) L/ `5 o; D7 d0 M
    36 W& M5 B' G5 T- z% t
    42 {; B$ k- U8 `1 f. P
    5& y  S+ s, K; ?& l  }
    61 S# d3 x/ t4 K5 N& s
    73 x# e" e* @2 M1 S! X1 r" M# A
    8
    - y! Z$ ^5 @4 N- y* i+ Q9' R" O( ^* a+ K0 I# `3 ?
    10, F0 R* ]8 k% _
    11
      V% z: v6 F: S/ c5 q! F! B12( I9 T0 X$ _% N. {  q+ T
    Ex2:《权力的游戏》剧本数据集& @- k1 ?7 T4 D% E& l
    现有一份权力的游戏剧本数据集如下:
    ) G) D  m7 E( Y5 D, ?! O. Y9 H0 ]
    df = pd.read_csv('../data/script.csv')1 l8 f) P$ F9 `* i/ j. ~2 ]
    df.head(3)
    % r$ e. N# L) m# f& F" ?1 C  X# C$ ~
    ' {2 w" C' t4 J8 \9 K% k5 g. uOut[115]: - h& \  J+ a) ?- f2 J# j6 V
    Out[117]: , X' P& b6 {; w; t# w& K
      Release Date    Season   Episode      Episode Title          Name                                           Sentence
    ' O0 B, K+ p7 n0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    7 \) R7 z% z+ c# O1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...# F* n& C! K  ?: F" H
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce # a1 B1 i4 \, P. p" J& J7 x
    18 w. O/ G+ ]; M, @2 R3 A
    2
    - Q" P. b$ V9 Q2 I  w3' p4 y) F/ n1 c) h2 D' L5 \! H
    4+ Z4 X% @5 G/ s5 l
    5. ^$ _* U. \* P0 r; Q6 G
    6
    8 K7 [( D2 w1 [$ J$ s" ~70 J% R& r5 l( J/ L1 B, s, |
    8
    1 d  z# e+ O+ T+ t& f* p: a9' N) {! U# Z6 i  x. ?5 q
    计算每一个Episode的台词条数。& n' x1 }1 Y% Z' }5 r7 f
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。  I, k( G) \$ k5 c$ f  S9 ~
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。& \5 x$ K) i( q! v( b$ m! m
    计算每一个Episode的台词条数。
    3 m  [2 N' v/ c: T/ s. gdf.columns =df.columns.str.strip() #  列名中有空格
    7 R5 W# n4 {" S" t: idf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()7 l, s$ y5 g+ x; s2 e

    7 E2 n: [  h( _: b$ X9 Yseason    Episode  3 O: k; l% m4 }  L2 f8 k, M3 d
    Season 7  Episode 5    505
    8 `+ _) t0 [* m% b9 L8 _Season 3  Episode 2    480
    9 K0 a$ E2 L3 l- P; |' ASeason 4  Episode 1    4752 G6 m0 h% O/ X9 P( m% s2 |
    Season 3  Episode 5    440( ~7 \* M  S! Q' W
    Season 2  Episode 2    432
    9 T: Y+ S9 A0 f) t0 K) f19 G% q* r8 E, L# y, v; M( y
    2
    ( R. {2 {4 C# s. B% ~* j' y3$ r+ v% q4 l, ~: P  T! S: ?
    4# R. d3 q' S9 [) Y
    53 [; b4 Z6 Q' I" _. W
    6
    " U  z  l$ ]2 L: u7- a. S0 _' T. u+ c$ ?1 I7 j7 z2 o6 I
    8
    # \  t0 e" V2 ]  f" A# r9% d" p+ D; Q  U
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    + o0 @: y; T0 M% u. ^# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数% \! M  t" N% N8 `; T8 w. z# r
    df['len_words']=df['Sentence'].str.count(r' ')+1
    8 P, R' d0 \; s$ S/ A% y8 q- vdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()* r4 L2 j  F% q4 O6 W- c9 z

    4 e7 l6 P- j0 s8 k3 jName
    . d9 w9 e- D$ l0 Emale singer          109.000000+ Q6 `4 O0 w' G8 ^' C8 L
    slave owner           77.000000
    ! S! A: [! v: w, W7 z, a) H; amanderly              62.000000' L5 g2 n. }7 G6 b
    lollys stokeworth     62.000000% P9 U, W- S* V7 ?% |5 Y' j. \: l
    dothraki matron       56.666667
    & k  v* m8 `/ J! PName: len_words, dtype: float643 j( \& z( }/ B4 x
    18 d( d" p* ?4 _; u$ M7 w
    23 F. [* a+ @6 m1 }! x7 ]
    3: ], R3 y/ h1 u7 u2 A" b0 w
    4
      C7 N3 U8 T6 k5 ^5) }4 z5 Q' @1 R
    6
    ) r, \1 U8 }' W6 I75 `) x% x* q0 N4 @( k* U5 |/ P8 n
    8
    : S8 b0 }' l( l9
    2 _* k7 b" E- u7 ?/ }10
    3 a% P" p- r- [6 V; P: O; Z" x$ L116 j, i, ], ~2 y% e' T
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。1 e3 d8 O& R$ I6 m, r
    df['Sentence'].str.count(r'\?') #  计算每人提问数& E  |" P+ ^5 L) T7 `- _' ?  z
    ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0; Z' z& E% J0 j; M- p5 E
    del ls[23911] # 末行删去
    6 i/ T, Y6 |1 \. ~* @" Udf['len_questions']=ls* Q% x% s- B, X* G$ A- U; k4 U
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()  g% n3 M% e3 G5 T( l( r5 A! f0 f7 N

      M3 ~; Y6 M7 F: h2 r7 FName1 O' B7 p% |/ s5 o9 E( S# P
    tyrion lannister    527: z1 `& I+ K" M0 f; L6 `( R
    jon snow            3746 n9 J/ Y  `5 ~: ?9 W1 @) ~
    jaime lannister     283: k/ O6 D8 G& X* z+ @% \' |0 }
    arya stark          265
    ; `* w2 p% J! i* Gcersei lannister    246
    , \4 [) f5 @  i! tName: len_questions, dtype: int64; n  Y# U! x8 @4 H

    : \- K- C% K/ z+ U2 J/ L# 参考答案
    : U- a7 B# o7 y  C, R$ x" bs = pd.Series(df.Sentence.values, index=df.Name.shift(-1))8 r2 l8 j, r1 T( l
    s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()7 Z. Z5 h2 B1 O  ]9 o
    5 C) Y8 I& |! w/ u; q! z- G
    1
    " }& H7 A6 c. F1 [, V3 c* y2  L, ~" d* j% ]
    3
    7 D3 W" Z" E' r( N5 K( R4
    1 f/ g* ~+ z8 [& a2 J" k: V5) I+ ?$ |2 Y4 b$ ^2 |
    6
    , J% M: H# R1 B. n71 N5 Z, P& g7 O! J
    87 A- z3 E% X& I/ a3 ]) I
    9
    1 e2 R0 r! l6 m+ Y10
    6 u6 Z$ N3 s% ~6 [/ U* g11( Y& d' g& F1 Q6 K0 H
    12. Y& E! ~2 s" A; h
    13
    ) I  S! @; W& I0 I% {( R9 C14( @9 c9 F5 S! F( |6 l! W7 w: V& Z
    152 [( Q3 U" n) F; I
    16
    ) ^4 R7 g/ a% I17
      [, I& M+ f% E1 ~! B; ]7 `& y第九章 分类数据% e3 _9 G# o, E- ]6 x" ~
    import numpy as np
    + f, r+ R% c3 @' R% t3 Ximport pandas as pd
    0 J6 @8 o0 C  w0 L/ @9 m1
    , J4 o& n0 r* R3 N2
    0 K* @4 o4 M  R/ U7 }9.1 cat对象0 X  G8 W1 e7 L9 B! {
    9.1.1 cat对象的属性5 ^2 B# `( r" V' |8 [% J0 ~
      在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。0 ~6 o  q4 }1 g! M6 O

    + R; r  g7 t) q) V; C: m  kdf = pd.read_csv('data/learn_pandas.csv',
    " r: U6 B8 o* k  g$ \3 a     usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    7 x$ \# t: S$ \! c; Ws = df.Grade.astype('category')
    8 K9 `3 |* V" @7 H* b6 ?2 R+ y$ x9 e, f. ?2 c* y+ X
    s.head()! H8 o1 Z* ~  l" o, \. E7 l+ |
    Out[5]: * @1 ?9 w# a+ ^' M& a) g
    0     Freshman
    ) @, U$ ^& I( @# C: n4 Y) M1     Freshman
    ' ?' c( N0 f2 R& E2       Senior
    " @" t; \1 g' b! x- X9 |3    Sophomore/ {1 w0 f5 H- U
    4    Sophomore9 l& A! Z: ?# I" t
    Name: Grade, dtype: category- j" v( P0 N7 V2 Y4 c7 G
    Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']7 f' U5 h$ M5 |0 i6 g/ k9 I
    1; N  Q- |4 N. z
    20 W6 J3 o' @" W! q' `/ G* @
    3  Q5 s$ T- `4 l, D8 G
    4
    3 t4 F$ z3 \3 i1 F" T5
    ; w' k! X2 g" Z4 P6
    / l" i' u1 q1 H! s9 u- D! p7
    5 ?7 x' @- t8 K, E2 |% |8
    3 b8 _2 c. X* O( d9
    2 ~( S# B; k( X- K+ I101 M- e: B1 X3 n' X, w' E6 Y+ @
    11
    8 ^; r' S2 ^/ ?9 ~7 e) {5 B9 M12
    0 \) ?" x2 q2 `# g* O' V13* N0 e+ ~5 ?2 a  a$ ?( @! j
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。# D$ C  M& q2 N0 I: x' Y8 A8 D7 j9 D
    6 e+ A6 O) w; L  @
    s.cat
    ) N, H6 L& R, ^  R% |# x1 H; aOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
      |1 c/ Z# D7 i  x6 M1
    5 G: P' y  t$ y2
      W- m7 A+ V  G. Tcat的属性:+ I1 D! `# v4 p: \0 M  k
    * @; K/ K2 p0 J5 T* L* x
    cat.categories:查看类别的本身,它以Index类型存储
    4 b  f6 g, W9 i& ucat.ordered:类别是否有序* l2 U- p( B: w; x0 B; j  p) }- w5 I
    cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序# }! n5 D/ V& h; |4 y9 R) W$ G: K0 e
    s.cat.categories
    # C7 A" }4 X8 M, Z. `Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
    . \, Q+ E. Q# F* i5 M* O9 B9 y! E* m" K  @3 _0 [! u
    s.cat.ordered
    ( b6 W. q9 S* z% z" r, XOut[8]: False* q& {  y* y2 ?: y" b6 Y# y' F: U

    7 J" r0 c5 A9 |) ?# B9 Gs.cat.codes.head()
    ' b" O; e% E! l6 COut[9]: : P7 X8 _- P1 l0 m. N2 L
    0    07 Q5 M& v- E  o% U* k" N* a7 }
    1    07 {" _% r0 d; [) f
    2    2
    . f/ a9 Z  B4 i6 ^7 |- ^* i3    34 \/ V* G; u. |- V4 I/ J8 `! ~
    4    3; N# |; N) V8 O6 I9 g" I! m
    dtype: int8+ q' k: b  b  ~6 I* n( w! d: X" X' |* k
    11 V* [# \" g# b# P5 C# I- K6 j+ e6 O/ q
    2
    % n/ A& k1 M' H! ?0 ^! i31 c3 n( h3 ]# m
    4. A  k+ U% \6 V/ i9 J4 {
    5
    , I6 x6 ]; L, f7 ?* `60 ], \! j4 o% A- ]8 _
    7; s5 D, N! A2 |/ H6 q, f9 l6 w
    8& v, M2 ~8 O& K- R2 E
    9
    7 i* q1 T2 c+ S+ I$ M, p10
    ' e9 g% b+ _. S. ^7 O- g+ a11
    * a4 ?$ a, l( @) F7 U9 ?127 N4 N1 J; m$ p1 B- r8 X, D% c
    13
    ; f3 _. Z7 f: c6 W/ B+ i3 F7 G14& z; M2 T: Y$ e0 B) T
    9.1.2 类别的增加、删除和修改. K/ p; v; G( |4 i4 C
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?1 Y4 \9 I% \3 E' u  M

    5 ^0 n, ^2 `0 T# n1 A+ ?【NOTE】类别不得直接修改
      o( r( ^% ]- I. P! c, \/ }在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
    . s% s% r5 c3 Y4 S2 d: K2 m% M% D: Y. @) }# O
    add_categories:增加类别+ r! D( h: V- _% }  N" p' ]/ P
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别8 o7 d) m2 L0 f4 P9 Q/ d6 Z3 \% _
    s.cat.categories5 [/ A. F; _* ]% h) I3 ]

    6 n; M7 T* ?3 @; D' \: jIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    1 U) b4 _2 h8 G9 T1
    4 p. i/ F# E* x0 U: u1 Z& i. C2( v' q0 B( w& I
    37 @8 t2 o' a- n7 z# P
    4+ e: N) O/ F( ^2 \$ [0 g
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。4 b. L6 V% ~' w' C$ p9 ^9 }
    s = s.cat.remove_categories('Freshman')
    - y3 d5 R" p2 V& G6 s- F" m# \2 O1 p% h, M, Z( D) O
    s.cat.categories
    1 y  U# c* B0 B8 }) ~0 K- R. Q* vOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    ! g. ?  M) j& }
    . J. K3 q4 j$ r" \4 U! Xs.head()
    2 A! G( H& h/ C$ eOut[14]: 6 e7 i4 B9 _- a
    0          NaN
    : o! t% T& Q( J1          NaN
    8 D: \; l$ V9 q2 J# d' U$ X2       Senior
    ' [- B% q3 a) v3    Sophomore; }  b7 r- l, h/ O9 Q1 [6 w
    4    Sophomore
    1 A' R! j8 C, Y+ e# ?/ |( RName: Grade, dtype: category
    ' V' h* I  o( V8 C& |2 UCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']5 N/ [: a" f: R- d  e
    1
    # X) a" U1 D8 g5 ?6 [3 p2
    4 P. H: R5 y& R  l- A& d! r( v! R3* b/ A, q/ L1 |9 I
    42 d! o' |, d  [/ s* L
    57 e" A( n# ~. N# q/ z
    6
    6 k- O) z/ a" F* c- Q( v6 h7
    ( d$ l6 L) @) V4 i$ a, b% ^8
    ; R2 l5 H+ H' g: n: H91 Z' \( C! u" d0 G
    10
    6 `; c4 z# f* _3 ^) {11
    , d0 J- O! f) Q6 F/ D1 V/ V12
    0 v" D# T! q, H, P8 K9 T13' h" Y9 `- Z9 c$ K8 I5 R
    14
    8 h8 A8 Q5 z# {  v  u: tset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。4 ]/ ^5 ~% b9 S) u! K
    s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
    . v/ J5 S; `. \+ A) ^: C" n/ v6 b7 `s.cat.categories1 |5 K3 E- e  Y0 w7 f+ `$ Z
    Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
    ' c9 L! d- g; j4 c" I$ k0 Y7 F. y' m
    - U/ R! X$ }4 `! x- Rs.head()
    ! i& _5 L* a$ f; W' |" d1 s* KOut[17]: 4 J2 e% ^6 g5 @% r1 t
    0          NaN
    5 _: u. S/ R! }7 w5 d) L  ?6 M. \1          NaN$ W; Q$ {/ x/ }- @
    2          NaN
    . h0 A& I9 v8 S: X( ^! X0 q* R3    Sophomore
    - @5 S8 u. g: ]) T. F4    Sophomore  z$ g8 R0 F  x, b/ C/ i
    Name: Grade, dtype: category
    - E! J# l9 |6 x- BCategories (2, object): ['Sophomore', 'PhD']4 w) |: M% L% f* N/ T, o
    1
    $ i* Z$ x2 |+ }+ Y# j, E2
    9 Q9 i( {  Q* F% a( S0 `/ \2 G% x3
    - L2 F" A" h& C6 w  F( S7 \4% W- O3 e$ |* ?2 |$ u0 |
    5) N0 f; M3 Y% d) f- d0 Q. R
    6
    ) a/ `& J7 b0 v* g4 S1 u7. M2 Z% ^- K+ ~1 i8 A  K
    8
    ) n! C* G- Q# j7 K: {& u/ _9
    " E5 Q% a! w) a* I2 z5 s+ \10
    , v: s5 _9 T" E; o, Y6 R11
    0 k' U& X8 @1 r12
    . O% g- n( E' q5 [% m0 z0 O4 Z$ n" b139 [5 d+ {- ]0 \4 X7 a
    remove_unused_categories:删除未出现在序列中的类别
    / F1 k. q( X4 `: ~6 js = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    . W8 q& {- T( E7 K, a" D9 C3 i  @s.cat.categories
    / F$ S+ O* P) D  w+ m
      t; a! E/ C, s3 BIndex(['Sophomore'], dtype='object')' l- @! b" \  G
    15 _. _; X( |& |! N$ B5 E( D
    2' Z7 m4 L0 l- ?( a
    3
    3 M1 P6 U+ z, Z47 Z" S8 I3 U/ |/ m2 x
    rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:( j4 P( N" p) D1 \& }
    s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
    1 ~) P1 R/ e$ S  A* b$ S7 Vs.head()
    . Y( @1 A( W$ U* W. V  A0 K: e7 Z6 ?
    0        NaN
    , g0 a0 K! `$ D. A1        NaN) O6 Y% f% C8 ?. W/ ?6 H2 F
    2        NaN+ s/ h& j: ~7 C3 u# `
    3    本科二年级学生/ }$ N( a7 r: x
    4    本科二年级学生
    * Y$ B+ Y* x" L+ f! Y: {# IName: Grade, dtype: category! V0 @2 x8 C5 L1 g  D
    Categories (1, object): ['本科二年级学生']
    & a3 {9 V9 G( `7 c: F+ V: ]1
    . ^7 q) S3 K1 @26 D5 z* `, f. l+ q
    3
    + ^! p# X' A" W. A0 ^# F& \4/ @, P8 F% ~6 V# p8 G! A
    5
    ( J5 {- l1 G# C2 A68 [" s9 q: I2 _8 B* B# C6 @: c
    7
    5 T) v4 P) e% g+ u8 B+ {. q8
    ' y# [- F; S- I/ U  ]5 k" v' g$ Q9
    ! \* g  U  `. `3 y100 |% C; m2 K' \4 M( K
    9.2 有序分类* z) ^2 H" n9 q1 w
    9.2.1 序的建立  `) l$ b* h1 C' x3 }1 V7 p9 k% I: j- r
      有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:' W6 D* r3 N, G

    6 j% J, A1 A: v& t0 J  ~, I) U0 \s = df.Grade.astype('category')$ Q1 E, o- ?# e' Q4 n  b" v
    s = s.cat.reorder_categories(['Freshman', 'Sophomore',
    5 E9 n- h- h+ S% ~3 H                              'Junior', 'Senior'],ordered=True)
    % r- D" ]9 N9 z. \& U( Ss.head()& l: d) a2 o* k
    Out[24]: ; S3 Q2 V0 [0 `+ p7 Z/ ]
    0     Freshman
    2 j! {, q# w9 c' u: B( K3 S) |3 L1     Freshman
    * n6 {' m* H( S2       Senior
    . B2 W. k2 o8 Y$ [3    Sophomore
    3 }2 X( n4 J2 b, |  K6 F- a# l4    Sophomore
    - N3 B- v% h6 `: K$ g, KName: Grade, dtype: category7 F$ Y" l4 l$ P5 T6 T
    Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
    0 M7 `6 O' b' h2 z. S, [; V$ z& X& J
    s.cat.as_unordered().head()
      |* e  P  }- H9 dOut[25]:
    ( C' N, P* ^5 e$ N/ l9 E0     Freshman  [( |6 v2 [; j' G
    1     Freshman
    / s8 ~, t, x# g$ |# ^: ?+ L+ \) @2       Senior$ d6 I/ {7 p8 O
    3    Sophomore# N: S1 @  e. |" n1 H* ~
    4    Sophomore+ L  I+ }# _3 b1 n2 |$ o
    Name: Grade, dtype: category
    - k- f4 ^* q1 v& K% HCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']" `2 q/ m7 ?; a% w: z7 s
    ; Z, J0 B& Z6 ?5 Q* ~
    1
    5 v/ r1 m5 e5 {( c1 Y2
    6 `. A2 b7 n7 A7 X2 j* p3$ @! `: `  g$ K
    4
    4 a1 z' [/ h+ c5
    " S# x) I: d: |' P! f69 G" c- `7 J. B/ `
    7
    7 ^" M& ~/ h3 o9 S9 n+ ^& K/ Z2 A8
    + e) r9 `. u8 n: P5 ?2 G- [9
    " r2 J/ a* b6 v; r10, r/ ]: M% a' q9 k. ]
    11
    # @6 o, B- C  E5 \4 R: s( E12
    , `& e' z3 m" N) B1 f13
    5 Q4 m" ^0 O# C5 v' H14
    + B& d0 {( C2 Y155 K! Z; Z# |8 w5 l0 U* J4 p
    16" ^, O8 G1 G8 S9 _3 C! I7 l
    17/ Q! Q! A+ P& U$ r8 i
    18
    0 ]* N# C+ S% Z9 B7 a7 C198 R. p1 a. V) T) N3 J( \
    20
    ; W. V; J5 o. D6 d1 G21. A2 Q  P4 ~: l: Z
    22
    ( I/ w2 r! z5 F  G" g) C, E! ?  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
    # O& c) ?1 q4 _1 d
    4 z6 z# c& y0 {) j9.2.2 排序和比较: ~5 s3 f4 Z( Y$ x* |5 E
    在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    : O& D- h! p# d9 {
    1 d2 P" ?' F1 |/ C  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:. X( C# P; M: ?/ \3 A% ]
    : M" ?" h2 R5 z. m0 D& F0 F) K' ^! {& j
    df.Grade = df.Grade.astype('category')
    8 f( v( J& a/ a, Y2 v  |df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    + |! D8 J: o; [  B9 M4 a; q0 Ldf.sort_values('Grade').head() # 值排序9 P, N1 r3 X/ X
    Out[28]:
    ( V5 E8 b' `4 m2 A        Grade           Name  Gender  Height  Weight
    7 m+ J* ?, P' \, u/ W; H4 @: D0    Freshman   Gaopeng Yang  Female   158.9    46.04 K, Y7 W" f: X4 Q( l
    105  Freshman      Qiang Shi  Female   164.5    52.0
    ( K  W. s% P  H0 C% O2 o96   Freshman  Changmei Feng  Female   163.8    56.0& m& f, o$ H& O$ O+ G& w
    88   Freshman   Xiaopeng Han  Female   164.1    53.06 {: k& \' v" s& H( i2 ?
    81   Freshman    Yanli Zhang  Female   165.1    52.0& `3 G5 n, {. m7 P3 C- r- u

    7 t; ^" I# d6 F4 C( Idf.set_index('Grade').sort_index().head() # 索引排序
    ; V4 V- q+ {: eOut[29]: , P2 g6 B: D  t
                       Name  Gender  Height  Weight
    3 V6 ^6 c5 m- v- @8 EGrade                                          
    . B2 h- ~2 V5 v* W9 a2 H( qFreshman   Gaopeng Yang  Female   158.9    46.0
    ) {8 G2 R3 O6 E! A% c2 ~) N- QFreshman      Qiang Shi  Female   164.5    52.0
    6 Q- I7 F5 c! t& k4 S, I6 xFreshman  Changmei Feng  Female   163.8    56.07 y& Z% |8 o3 O) U! E( r; e
    Freshman   Xiaopeng Han  Female   164.1    53.0# F3 H* m9 I  k: u
    Freshman    Yanli Zhang  Female   165.1    52.0
    ' a( ~7 P  w' j- \
    / g" B- N* F, Q/ a/ J1
    3 v; ~& ~# s; s  J! |9 ]* F, q2, ^& q0 B! v6 S0 c
    3# R6 n4 X( `2 y6 e" j& M1 i) x
    4
    " P% s" ~3 {/ G$ D5* m+ m1 f- B1 J! v7 P7 @
    6
    & ~+ m5 ?' Y* h5 w- o" b9 d$ p7
    1 K& ^& `, c; E& a+ p4 D, A8
    & p( P" ?3 n3 E" \1 Z, _9
    . H, D( I3 Q7 f2 x10
    0 B; a' w3 f' V: A) q( f" A" [: X11
    ( M  M0 @& ?3 q/ U: I7 q+ I& Q12) p/ Z% ~) h$ j" M* O
    134 N( q* W. R4 j. i" I8 ~
    14  m, u, [9 O3 P1 B  K& e
    15
    0 H& l' N* F* N  _' s# N16
    4 ~0 [6 f4 I3 j- J7 [5 g1 ^17
    3 Z% V. e. y6 Z0 @; p- Z18
      W! g8 g# e1 Y, k0 n: C19: J5 m6 V+ @- X# B
    20
      n! ^7 O: r7 `7 s  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
    * q: W; O, U2 E9 [
    # D9 Z$ R, \! E: Z" r==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
    ( D4 ?8 k; g9 l: y>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。, P/ f- C- c2 ~% v
    res1 = df.Grade == 'Sophomore'2 h# u2 M0 z: j" Q; j7 k

    ) t6 ?' y$ b) o9 O% nres1.head()
    8 ^# ^! t$ j( x  ?Out[31]:
    # d/ d6 F7 P5 i$ ~0    False! _* V, R1 T# n1 `* B
    1    False+ {" I2 f' u; V7 t4 B1 D
    2    False
    5 n, t2 w, q! r3     True
    4 a$ z. e( S1 |! B6 k) L" {4     True
    / U0 X3 }% u0 e' H4 xName: Grade, dtype: bool8 I8 s$ j/ K$ U

    ; q. _4 V- w. O) {/ `* O. ^res2 = df.Grade == ['PhD']*df.shape[0]; s) \2 U, o& f. W- |* I

    - u& s: }+ O" u" h8 vres2.head()0 J+ {8 I8 _# J5 t( [& |' U
    Out[33]: 4 S- Z" ?8 x  A
    0    False6 Z6 m# B, [" y) ?$ ?" e
    1    False+ W$ X7 s+ _6 _. C, `2 I7 k
    2    False8 D! j" l* m, G7 D) L' K) b
    3    False6 M, s# ~7 P0 O: B
    4    False
    / X$ U" {0 a: y6 I$ o0 dName: Grade, dtype: bool# H5 C1 u- q: N4 [* @# \

    / ~% g" ]5 O# Mres3 = df.Grade <= 'Sophomore'  o4 G3 l4 L! ^" M# C7 k2 j; z

    ( E/ e4 Q0 S8 K  }" \7 S. t, t/ Xres3.head()% L0 ], Z' N$ Z. Y% Q% T
    Out[35]: ! w' ~# m5 H" L# K4 V5 D
    0     True. D/ `1 s1 D* Z% I( M/ y) s
    1     True
    9 w! o& @7 k' N  f& c2    False
    + l! A( o4 r8 D. @) y  }, x3     True
    % H* \1 h% F9 ]& K4     True
    ' n# W  k4 h+ s) b9 m2 F7 e' H4 q1 CName: Grade, dtype: bool) {0 f/ A1 J2 y& j
    6 _$ M0 ], z8 q
    # sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。9 _- `% x. y( e0 X" u
    res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) 1 |, K; J( y* z
    5 V/ a% B0 a1 j, L, \
    res4.head()% h! S3 O% {; a1 V% _. n  z+ [
    Out[37]:
    1 N+ H- X2 Y: b, l0 H% Z5 s* r* {0     True7 U) K7 P5 b  Q+ |
    1     True# M6 a+ @! u% N( Q
    2    False, q; q5 q9 N1 [* y) G- p8 a
    3     True
    5 m% ?- S+ {( Z: U4     True
    ( `0 ^7 G# c) [! WName: Grade, dtype: bool; l6 B$ N/ ~. e

    : r/ b4 W4 x) m6 `1
    & K: f1 p# ^; b+ U9 w25 U- b% `  y5 e* X6 E
    3% R8 z- c8 U/ a* c3 R& |
    4
    - V% W0 ^" n# I; S5
    ; V' x- n* h) d0 r65 L6 ^" ~# O3 A) Y4 s0 W: g
    7' v+ |; q8 m' \+ m  D0 v- T2 N# W
    8
    9 K4 h! y( C8 {9
    ; V/ O% f7 x! G& E! ?10
    4 X) \+ {' I2 o% j* Y% a8 b: e4 Q118 w2 ^" b, s! S( {7 L! O: _; |
    12
    ) N& m+ V9 r9 C13, f7 u( q; R2 J0 i6 J
    14
    * h3 l2 e1 s3 x! M15
    : N. P% [! b+ A+ Q" H160 [: g; X! \7 S0 G% ~
    17
    * e0 c: W2 c: l' r+ [" R% I! r18% \0 X5 E+ x$ I7 k. a+ j
    19
    7 g  W) V" x- A8 l20
    4 y  @+ y4 p: j/ ?5 F218 s) l* J9 h6 S6 X8 I, m" I2 N
    22
    3 G# Z  q+ Q5 x  z+ y23/ D7 s$ h; F4 g
    245 K4 m: i' {: H) Q7 ^& k
    258 [  N# s0 M2 s. Q- E
    26
    " E% g, V6 V4 e27+ ?1 A2 |% S& _. [5 D
    283 N1 c  e( @% k* K* l, |7 `
    29
    1 Y% G8 Z3 J- b) ]308 o4 a: z5 ~; n+ N) r; m$ M
    31
    . _( Z3 z, @) I! b$ b8 w32
    4 `' |+ V' F% \/ l  a  |4 j339 d6 F1 r, h# j* i: @% u. U- g
    34
    $ C# [* J" r2 ~5 y" F35+ S# M$ T# |6 J) n' a/ y
    36
    9 j0 ]( |3 w4 m5 z) c37
    + e' y9 }5 z/ u6 E# L389 s" {/ F7 B3 m/ M5 J
    394 k; v* @# ]- C$ i8 X4 M& z( m
    40
    ) B" @5 p" Q& P' D% E+ d& r0 H41
    . a( `0 T" I# B' N5 B" [42- I1 w$ U! U9 }( M0 l' i3 u" J
    43
    2 w0 r4 A: U. c1 K$ L: Z44: n" b% V3 O+ b+ ~
    9.3 区间类别
    & f6 U- O8 N: M2 ^9.3.1 利用cut和qcut进行区间构造1 Q" |) N$ ^' x5 `  p# n1 e3 V
      区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
    + X$ w, g; I% }, A# E
    & j2 R2 _* P- f+ r) J. Bcut函数常用参数有:" b# C- B# Q) x8 Z4 d4 g$ }! c
    bins:最重要的参数。' ~7 }' F0 p/ [  Q* b
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)2 L7 B3 b8 k+ Q: G; ]$ y
    也可以传入列表,表示按指定区间分割点分割。  C4 D4 _; v: O1 T
      如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
    & a  {" p3 _% P% O; g, a  如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
    / A0 t6 `  l, }) m* _& p. l% h
    % n4 C& m% w7 Z' C9 o% N" m- m3 y) gs = pd.Series([1,2])9 l  l5 s6 s7 V* y: a
    # bin传入整数0 R: T- l" Z( ~% J

    & U6 I; D- B+ x0 v& `pd.cut(s, bins=2)  g/ [- g; S5 l$ C
    Out[39]:   j8 ]2 f( ~: \4 t6 x7 @% z1 H
    0    (0.999, 1.5]1 E6 t0 r+ R  P
    1      (1.5, 2.0]
    1 P% J% Q6 E* u( o. Sdtype: category) a. g* [5 I! D' |$ p3 U
    Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]/ c! X% `" `  T* l; B" T2 o* {

    ! J2 n$ y5 z( _# }5 Z: U+ xpd.cut(s, bins=2, right=False)
    " A. B7 h' `) l. y( J! AOut[40]:
    / g+ V4 n1 n0 g9 D7 M4 {* T0      [1.0, 1.5)6 r) k3 c4 ?& c% e6 ?$ O
    1    [1.5, 2.001)
    + @# m; j/ x( l& Adtype: category: r% X* R: D2 T1 x
    Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
    , k+ z2 Z; f/ D- p9 V$ u: B
    2 `; b& n5 o7 j$ q; }
    % ^" N! [4 I1 W, s* a4 V# bin传入分割点列表(使用`np.infty`可以表示无穷大):
    7 Q+ k  H8 N# v/ O5 n! g- Q: Rpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
    5 n8 y; ~7 r& Y' a: G. eOut[41]:
    3 P' U  _2 i$ {1 q0    (-inf, 1.2]( O: Y8 @* p9 {- c  j
    1     (1.8, 2.2]
    ' {0 B+ P; h) U* F3 w  f5 idtype: category
    : Q2 T6 @6 K+ {/ S: L2 ]( oCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]* i9 y: Z4 K8 [/ C% _
    0 [6 e/ Q/ Y' Q: X4 H! n
    1
      U* ?# t; m% T2& S* N7 I" f( P( K
    3
    % p. X) S  U+ j5 t4
    ' T; @" d# w% L# e: i# j1 |4 l+ G5
    ; ^, ~3 Q* s9 ^, I- S$ r6* x! Z& Y$ J7 F0 I4 E1 m$ `4 \
    7
    7 Z( y, l  }  `# G% F, A$ }& [8
    $ ]) r+ k8 s$ ?& ^' b9' E" L8 d7 _; }2 N
    10& O& I! Y- C  W" S- [
    11* f1 v5 ~$ ?6 R: k
    12( |7 u$ F/ K8 ^! E5 O0 V7 l
    13+ K. Q# ?6 a# I# \" h2 g7 k
    14
    ) U; v3 O9 `: }" j: W15
    7 E8 @7 ?& a# Q2 q% n! P; A& k+ M$ {16
    6 ?" N% U' U( k% i: T) C; x17, J1 j: t* C- u7 q
    180 b% g( J3 u, I- f+ A. H. F  y/ w( `
    194 K+ L, n: a, z6 l2 M, k9 ]' O" Q
    20/ |+ t' J3 X9 v1 f
    21" X& H/ r2 U" X9 ^, j+ a
    22% e+ ]" ?) ?* {
    23
    8 [( q# T- r1 O# W2 V( W: o24: H/ N; `, w* m6 b+ p4 B" M+ V
    25
    0 w! m  y3 `# A$ {5 Alabels:区间的名字
    3 x" k9 [1 f: @, g; {! ~! Wretbins:是否返回分割点(默认不返回)" ^! \8 H* c  K; q% l! q0 K- s
    默认retbins=Flase时,返回每个元素所属区间的列表/ p, B/ F0 m0 _
    retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值& K( C, U0 L; n
    9 W0 O' z/ _1 t
    s = df.Weight
    . u4 B8 A' M& i7 _2 c+ Fres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)% r0 w9 o7 C5 v& |" X/ T) b
    res[0][:2]
    + y+ o- \' x/ F) R
    - B5 T& d$ u$ n, BOut[44]: * U' f% o6 a1 i) u
    0    small
    5 P3 v0 I3 D, x2 J' Q( G2 ^/ i0 v1      big
    9 w1 P' V/ ^1 w0 P' Qdtype: category
    . I8 _# Y( a; E6 N5 p4 {Categories (2, object): ['small' < 'big']' W( t* o  ?. C9 \* w

    6 L% }. T. g  |5 Mres[1] # 该元素为返回的分割点/ C5 y* p) [" m( N6 d. e9 t! C. f8 p
    Out[45]: array([0.999, 1.5  , 2.   ])+ M2 p/ q7 V+ A! |2 K6 K! j! E
    1
    7 S. t$ P4 X% l3 i/ p2 S6 ^2
    ( a3 O+ E% C" j4 j$ T* ]% b5 G5 m$ p3
    0 s+ P. a8 S9 X8 l4
    ' T, n! q+ x& C1 k7 G2 w# m- P- `* T5
    $ t1 }4 @7 e4 c1 a: B, S7 a1 q6
    ; z% B' P3 ~, |- ^7
    ; t6 V1 L2 I" x  A. C" b% A5 m83 x0 }3 _# o/ I
    9
    : X: D$ F' {' |( k& Q8 v! y8 Z! Q10
    4 r  U7 M9 n0 p) W11
    4 W, i1 \9 |2 s, \. ]9 J% a$ A12
    " l* h% S( h- {* }3 h1 q7 r) wqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
    0 O0 w( e0 n$ xq为整数n时,指按照n等分位数把数据分箱
    / D4 D* J  r0 g4 g% [6 V$ ]) Zq为浮点列表时,表示相应的分位数分割点。
    3 i3 W. R9 O* |- js = df.Weight
    ) i) g8 ~- [$ J' o. }& n
    1 V3 D3 j# E& c/ Tpd.qcut(s, q=3).head()
    " P' ]. D, Y+ z. p! X; R- lOut[47]:
    : M0 a/ x5 L2 P5 b3 w% i3 |0    (33.999, 48.0]
    3 i9 v  Q1 B- _, W: {. I# p- E1      (55.0, 89.0]
    ! h7 @/ G' t* Z4 c( \1 o* d1 d( {2      (55.0, 89.0]
    7 o4 b% ?" V. l* j6 m3    (33.999, 48.0]- y  V! l/ ]$ z3 ~; S( H( k) g0 e5 K
    4      (55.0, 89.0]  ?: C. K- J& q' Z1 Y9 j
    Name: Weight, dtype: category/ t1 u0 C; J# W8 y8 E% }$ L2 ^. u
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
    1 k1 P0 W+ Y' V4 Z- l8 [/ [! }+ G  i) F( F; _; j" c
    pd.qcut(s, q=[0,0.2,0.8,1]).head()4 {8 V6 U: J, G" N1 y
    Out[48]:
    ) _8 D7 k: {4 ~8 M* R/ B9 K! ~0      (44.0, 69.4]
    " X/ J3 Y1 ^8 A  s6 A4 `6 g1      (69.4, 89.0]: [* ~. p* V3 u5 R' k& R( h
    2      (69.4, 89.0]
    3 d9 v  \7 N' T; q% Z# U' F, g3    (33.999, 44.0]2 x* L7 }5 m8 Z: U7 G
    4      (69.4, 89.0]1 z' y! m0 L3 H: w
    Name: Weight, dtype: category" n. J- e. Q8 Y. B9 b
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]4 k4 j( K" s* F2 s. h
    ) D+ B; u2 m+ V' b
    1
    . i; P- y5 n* f% k& ?. J2
    # T" X" k* Q4 f7 a2 i39 F+ ~( c2 U) S9 y
    42 @9 d3 l- U7 ]" k- G
    50 J" j5 k  C0 A3 q! I% ]2 ?% X
    62 q3 r- h* ^" V( u
    7
    " U0 z& K) H- h8
    . F1 [6 u1 W, T$ {+ G' a, m/ |" P93 \/ ^. l6 j: P' D: {5 n# X
    10
    / q. ?6 U* z7 t11
    ) e) I/ P# @# p% N# j12" }9 q' L" n; X3 a1 U1 R) A
    13! t8 u( [! t6 J( O
    14; y; x' x9 Y9 y5 u" a
    15
    . V6 E# r# n$ Z8 M16
    0 |5 |  @% p9 Q8 A3 v( S2 k17
    & R8 W# t( X0 Y4 Z18
    ! f8 f9 b9 L! b! L% j$ c5 M19/ W" S" }$ }8 X+ F* t
    20
    ' B. B- N! s: ^7 d9 R21
    ) s1 D) u: u; e0 F2 x9.3.2 一般区间的构造
    * O3 u$ g, \2 F0 m, h. L0 n' I5 [  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
    & i; s2 _1 R' v$ u) }5 g5 d' \: f' }7 j) X0 V
    开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。+ [; M: R1 b8 O3 {) S# U
    my_interval = pd.Interval(0, 1, 'right')
    + o( m: K) {% l9 V1 _  h$ j- V8 F( c1 }
    my_interval
    # `% W5 e, c! N: U- _& qOut[50]: Interval(0, 1, closed='right')4 Q9 I# x' k& }- [9 B
    1
    ( q. A0 L4 k3 G1 Q25 o  q: A' f4 d$ h" `, f% A
    3
      b2 z! D) B' f' m- L: w5 Y4# K- K) a: y3 `" }. w2 M. g* i6 ~1 W( U0 W
    区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
    6 U5 T8 D" W* H; f使用in可以判断元素是否属于区间
    ( C8 {" j; H2 ]用overlaps可以判断两个区间是否有交集:
    ! j# I+ S1 ^) w6 F0.5 in my_interval
    8 N" Q% e$ I$ Q9 k6 M/ L* f* M% ^8 ~  y' s
    True* d/ {4 t. y; m0 N5 m7 K5 u
    1, [8 @+ B. H: O' x% N) a2 b. z+ o+ y' ~2 O
    2
    , ^* {3 h# w6 }; r& ^6 h3
    ; q% o) e8 _2 U& \, L9 i  \* K1 Umy_interval_2 = pd.Interval(0.5, 1.5, 'left')
    1 w( `# `4 e+ N/ _& ~' G3 ]/ v" Imy_interval.overlaps(my_interval_2): \$ ~- a" D* B" n! M
    0 S. k$ c; c4 t, s* K7 g# s2 J
    True
    ' X5 b3 G5 ^9 t; [1
    ! P% P1 A9 I7 `' K4 D! o. }2
    4 q  ~1 ?1 s  y% ^2 J% X2 Z3
    / S5 u2 j; E3 a5 s4) W) B/ C+ `5 r, N' ^3 n$ W
      pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:& Z9 n" z" k* r/ }
    + q& E4 |/ u; G$ B3 k$ s* I4 q
    from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    ( L6 v9 ?  e8 D, F7 B. B# i" ^pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')+ d1 z, m. ^6 f2 y' R
    * b1 [: p7 ?: W( _5 G
    IntervalIndex([[1, 3], [3, 6], [6, 10]],# a6 v; V2 U# N" l9 Q5 c
                   closed='both',+ Q1 N7 v8 ]- @+ o
                   dtype='interval[int64]')
    ( W6 n7 t. m. q18 V$ e7 i. ]" ]' e( N
    2
    6 Y6 O; `/ V# C& i: W, z( k3
    " f2 W2 ?# J/ J6 m: M; |" Z$ N. d4
    2 r3 T4 n, y. N& l5
    ( H, q3 U% v- ?( ^% N7 {from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:/ C! q' @  K7 ]' W' r0 N% ~
    pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')# Q+ {) u% V8 s

    3 ?, w, n2 f5 t3 L8 C: ^7 \IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],9 y7 d* Q2 ]6 _+ l
                      closed='neither',, A) l% ^: n/ S8 ?: O% @$ M8 a
                      dtype='interval[int64]')6 Z  h- [; a, s+ K+ V0 y8 J
    1
    6 N7 Q# D2 Z0 x- k' s0 @. {* [2
    0 o( r) o' k/ V9 P0 t2 \3
    / y# f) b7 h) ~8 @3 b0 D4
    % m5 ?+ Q" D# y0 I8 t5% u' l$ k1 g# c) ?1 L
    from_tuples:传入起点和终点元组构成的列表:
    0 d' _: F* u0 J! F8 j8 Npd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')3 z1 Z7 {+ y0 N" S2 h" `* s* Z
    " C4 n; F" b0 B$ y% n4 _
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    + T) c" b5 g9 T. d6 ~% Q              closed='neither',$ H! E; J# {/ j* l, x. n
                  dtype='interval[int64]')8 t) e# }- \: C6 }/ x
    1, ?' X. U$ e% k+ U( |' h
    2
    1 b2 _& A0 H" K7 k3
    & L2 `; M* U5 T* O% z( `/ W7 E: Z45 {$ w. Y$ x  G. R3 |  E, B
    5
    ) M& V5 ~: j" ]+ i5 k/ M: C* y& S- minterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
    9 n0 A# T3 b- E% ~- wpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数' j2 T6 X, @- ~( }2 h/ `( w
    Out[57]: + ?+ D% E5 i5 }3 G+ G' O* [3 z
    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]],* |- H6 @9 r" @7 C6 Z
                  closed='right',; H3 E2 D; ~" D  M7 b  p) B
                  dtype='interval[float64]')
    ' d( `+ {. `3 [+ h: u
    ( [3 p% [* H, x7 ~pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度" ^" \  W5 a% b* z; ^, ~6 Q# o
    Out[58]:
    6 p6 {! M9 H8 R! y1 A1 FIntervalIndex([(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]],2 u( L( Q9 S" K
                  closed='right',
    2 `. N" P; z0 o" D! K6 ]% _# M( j              dtype='interval[float64]')
    0 C% J+ n3 M& R" p5 B1$ \$ v, O. L5 |% X8 N7 v. R
    23 N, ^* }. ?% Z1 M! m
    31 p+ ]9 q6 `5 I, Z/ F
    4
    & `( L. A5 A  [% _( m9 Q$ a5
    2 s2 Y. V. r& B  Z; \5 o# _6+ J- w- M) X4 b' T9 S% [
    7" h# `% G3 Q$ q
    82 w' E6 Z1 |7 q" O# |
    9
    9 w8 b7 z) m2 C* z. p, \- K10
    2 g6 N5 x) `" C  k: }6 W11
    0 |; g+ s8 |9 [+ ^9 a7 e【练一练】- T1 m5 U7 x* P4 e. a5 ~  \$ y  l
      无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。: N" \- Q# N7 P6 M$ {
    $ f1 G9 ~5 l8 ?
      除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
    " J: U1 B! Y# N/ [7 M. U
    6 t  M: p& U. T1 wmy_interval* V8 Y: m6 h+ b
    Out[59]: Interval(0, 1, closed='right')4 e2 {& D  s. z" p
    0 u% A9 h3 }" v* ^" t+ i: |
    my_interval_2
    3 E. q, Z5 ]* nOut[60]: Interval(0.5, 1.5, closed='left')
    , U' t$ ]# `3 g& t- {2 H9 ~% N8 h; p% Z$ v# o& E5 q
    pd.IntervalIndex([my_interval, my_interval_2], closed='left'); N) n! u4 X3 z) q& f, _# }
    Out[61]:
      }' i% p, \$ ]0 cIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    3 h& S: F: @: @6 X1 r- g/ H              closed='left',) \! F/ F8 Y, s1 G
                  dtype='interval[float64]')
    - ?; A7 L( e3 R* f/ i1! R9 {7 a& h, ^4 `/ |: p
    2, ^( c1 P3 o! \; `6 h
    3* `$ p4 u! {) X- z
    4, U2 O+ R( X" d+ |
    57 \* K( m7 r7 H9 L2 E. y- V
    6$ O, n  e* T, E/ ^+ k
    7
    3 i4 a$ S3 w: n( X8
    & L( ?* _" h; F& ^9
    4 @( @! ]5 `, @2 M10. u" O7 Z4 j) C5 v7 i4 t! l
    11) {6 x) L7 S5 ?  }" W! }( ^3 F
    9.3.3 区间的属性与方法  U6 L. k& P4 k8 w& N
      IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
    ; {# y  H7 Z- H+ H2 S( K1 a
    , C* w  L9 ^* Es=df.Weight
    . B0 u# n9 @, s0 k5 x( H6 z! Vid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
    ; q1 f+ @* \5 x% [' D$ \0 _, Lid_interval[:3]
    ) }6 M$ K# [' n+ b* {- d& M. V' `3 a5 |8 m. C3 g' ]
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],  {8 u1 e2 E# }9 Y& k6 k' o7 j
                     closed='right',
    1 L; g$ b! Q8 P                 name='Weight',
    4 A, [( R0 |$ v0 G( R/ y                 dtype='interval[float64]')% H% B  Z* t3 F. c2 W
    1
    8 L  M9 ?1 p3 i( I2 W# h( Y  Z  X2
    / x$ s  W8 L( W  C+ n( G, w35 f& h' J" \: g* c* t: b+ v
    4- A2 i0 V- c4 S+ o
    5
    . Y5 u8 l  H* Y$ a8 ?; ^+ M+ V6" x, Y9 ?  ^7 L3 c1 t
    71 u3 A, `, K: y+ S- k
    8; R2 a  K9 X' Q) `1 v
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
    - h- [, ^' W! Nid_demo = id_interval[:5] # 选出前5个展示) ]- n( {5 \! T( m% ]; z  e- a
    ! L! S5 e% V0 g
    id_demo5 S6 W0 @# Z3 q6 \1 ]' z0 _/ }% U$ w. \( C
    Out[64]:
    + ]  M& \6 Q; sIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],# L' B7 a% R. \2 N
                  closed='right',
    . f2 C; m2 T6 A- M: F( R              name='Weight',! T+ E; C  c8 |# V; C
                  dtype='interval[float64]')
    4 q7 Q: f$ X1 C" c$ l, d( e$ R, @& v
    id_demo.left # 获取这五个区间的左端点' i- s% p; y; h5 O' e
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    % ^, F" r7 _4 k& l4 s/ \: c' L1 W' _# ?$ z0 Z0 I+ l' j( e$ L( k1 }* y
    id_demo.right # 获取这五个区间的右端点
    7 A; j" ~+ c2 V( F! F  T7 QOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')3 [) ^1 T" [* m" F4 N- R+ |
    & e; h2 O9 M8 K3 O% Z/ p& @3 M$ T# B
    id_demo.mid- w8 f: S* Z5 Q0 l% _  B! \* d
    Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')) k% [8 Z7 [" `0 `4 L: {0 v
    ' w; X* q( h- q- T3 P6 _
    id_demo.length
    % B4 q, ^& q1 C& X7 ^$ `0 cOut[68]:
    ; k2 M+ _' f" L: C8 L6 PFloat64Index([18.387999999999998, 18.334000000000003, 18.333,. A! i6 [  j; I4 B3 c  R
                  18.387999999999998, 18.333],6 S( K8 z: j6 D* O# }! [. G3 J
                 dtype='float64'); l3 T( |- d- C' ~
    $ z% O! A7 k' K" K4 H; E+ H0 |
    1
    + u  n6 c. C; c+ H/ w% c2
    2 I* b4 k+ S4 N8 e& D' z& J8 w3 B3
    + n6 a7 u1 W5 w. Q1 C. E( t% \9 v5 W42 l! C& S' z9 k. ~4 j
    5  t9 Y) P1 {- L5 c
    65 W) x4 T( ?+ {$ `+ G
    7  B+ p" L" P: E% Y$ r: c
    8, {% d* ]1 x* K
    9+ ?  s) L7 x) x1 [
    10
    - M) ^4 a/ \0 V( f) ]" `" D11: W' i# u0 H' E  J* B
    12
    $ c# m+ r- G9 _# |& j4 a& d$ h8 X132 z$ m! c  W% Q; S
    14' u9 t4 v8 y. o9 f
    15
    2 Z! H" q$ }. @# ^) u/ V: n1 t8 q16
    * r1 j$ I; Y2 V5 @) L172 O+ m/ W1 e9 I4 v0 ?; g8 N; z$ x" n
    18
    ' R8 u0 w/ Z5 A1 x5 W$ |" E& C! t! d19
    9 R) q* S5 P% _( B) {- g20
    , A1 o1 q# h) y* N7 |21% @( R8 u4 s: F
    22
    $ W' Y7 ?1 H8 \( W2 t$ S0 c  |23
    3 l" Q& h: ~: h4 `) b7 t( sIntervalIndex还有两个常用方法:
      Y3 z; Y! b6 n+ hcontains:逐个判断每个区间是否包含某元素
    5 l4 I1 y5 K( p6 qoverlaps:是否和一个pd.Interval对象有交集。" h% z/ ^9 W. K: {
    id_demo.contains(50)6 ?  V; N- d( ]1 ?; @. U, A; K
    Out[69]: array([ True, False, False,  True, False])
    7 k" L4 X8 M/ T5 @0 x
    . i4 J; Z% D0 y$ L; Bid_demo.overlaps(pd.Interval(40,60))
    : R- [: a  ]* p, g, aOut[70]: array([ True,  True, False,  True, False])+ a* l- U9 o' n6 l$ S* o: H- J  m
    1* Q5 P2 f3 _- X# m& S
    2
    # s/ L1 }. j+ _& @; T33 X% A' n* g- H; g( N
    4, u( r: ?, _5 Q$ j+ |
    5
    0 `! f( t. S6 f9 C2 }" S: Y2 P9.4 练习
    . ~/ q" k3 i* C$ P- J4 IEx1: 统计未出现的类别
      c8 y4 G" R8 ?  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
    $ L* J: r. p! |& q  B
    3 ^8 p- [" w! m( v3 Adf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})1 M. v( \8 X2 P8 v1 c( t& b
    pd.crosstab(df.A, df.B)
    4 N8 a9 n! P0 e0 K
    / f) ]( z1 F' I7 QOut[72]:
    ' L) p# j- t% y. O+ R! q) p. BB  cat  dog* ]" {, }+ u7 e2 h
    A          ) B2 z; F' k+ K; T8 c
    a    2    0% U" ~$ X) M; u+ r1 h6 R
    b    1    0
    - H# j0 B1 Y5 Sc    0    1" w7 N$ V/ e+ q  }/ e
    1
    3 j# `" r! {' J/ p+ H6 k2
    ; i0 _3 v; m9 |, P: e9 F  g! m6 s3" ]9 I0 ^6 m( j" J' C1 [
    42 r: ?6 p/ M7 v" D
    50 ]  O8 |% J& F5 H. p5 Y* t
    6
    $ C* q7 y$ f& U+ w7
    . G( S  |7 M( X5 s2 x8+ d0 D- l# K3 g& I6 s0 L
    9
    $ k% m" i9 o# j0 k( s8 x, z; V  但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:+ J( @& j/ F7 l& j+ U
    1 e/ _6 n: _0 M+ P; y
    df.B = df.B.astype('category').cat.add_categories('sheep')0 [  I1 U6 |" E8 x6 b; a0 x3 Z
    pd.crosstab(df.A, df.B, dropna=False)
    * H) j/ j/ p8 [) J) B/ l  N8 @# @, `8 {$ `
    Out[74]: 2 R+ v/ T. D9 k! [: E& z& V
    B  cat  dog  sheep( j/ U2 c- Q3 ?  k
    A                 0 H7 ^& W* M8 N0 \$ h! N# A/ W% n
    a    2    0      0+ E' ~7 f) @1 b8 H3 e( S& t2 s, j
    b    1    0      0  q; e4 r: G3 f0 X7 r$ r* f
    c    0    1      0
    ! a  d' ^; F1 _$ x; {8 T% z1
    0 N+ N& b2 I3 S) y% K1 Q' `2
    3 x1 |7 L" m: P$ P3- P3 w" o3 W$ P' l
    4
    6 p6 I) D3 e& X4 O& s* M5
    ! S4 i! b8 i. j6' D8 S* M6 F4 W: P' q' r
    7
    , o' G7 z0 h( P/ g6 ]+ P8  o$ E% f3 l6 c4 l# ?) O- ]
    90 U4 V9 M3 T0 h. a) {
    请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
    ( E. Q$ ?; K: E. X2 K5 H1 m- v  J$ f1 A3 E: V, }8 u
    Ex2: 钻石数据集
    9 g6 {7 B# e' e/ H4 P6 I  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
    * w+ U5 c* J) U. O
    . l  B0 r3 K8 T7 Z( X' Wdf = pd.read_csv('../data/diamonds.csv') 3 `% e$ s& p: t
    df.head(3)
    6 G! ^( H# o/ l% ?; e. I
    . [' {8 b, V6 T9 t4 ]4 z1 B) vOut[76]: + Y% T+ M. j; y
       carat      cut    clarity  price
    " a4 e7 h! H( H* S' O) Q0   0.23     Ideal     SI2     326
    8 f& q% @3 p+ B4 v; w5 N1   0.21    Premium    SI1     326
    & f# b' G3 G' O) p! R, l5 C0 c2   0.23     Good      VS1     327
    $ b; j) `4 z5 z+ f; l1. K6 c! a* E& Z& @3 m* P% W
    2. p9 w% M5 s* k6 a( a) ]% u
    3
    ) W& b1 M6 E0 x, H' n4
    ( d) J& ]% E# K, }% x7 y/ j4 X2 M5
    ( X/ c( d  B  v9 u6
    % K7 W0 k8 W5 Z7
    ! N6 n; i. H  _8( E  G6 X2 \8 W3 [
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    - |& Z" }% a; s' O钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。) s% P7 C: S! \1 w
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    . G. E1 m7 l2 `) K) _9 J# D$ r对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。( ?7 [/ n/ h  z
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。( n+ ^4 i7 z5 U' d
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    8 E/ o/ H2 y3 I  K9 P, j+ H" Z4 ]先看看数据结构:
    8 R% @0 [+ ~, Z% E6 Z- X/ Q6 ^6 Z( i6 X* z- P. c3 u/ E6 q. t
    df.info()
    7 t! D0 s7 M( [( q  Q/ ^& K7 y8 [Data columns (total 4 columns):
    - {0 K3 g. `. |. f/ n2 W* r  r #   Column   Non-Null Count  Dtype  
    , t$ P3 L1 Z- n6 U---  ------   --------------  -----  
    . {  ~8 }) F0 ^& t6 f3 ? 0   carat    53940 non-null  float640 b" s: Z( t3 u6 Z1 i
    1   cut      53940 non-null  object * Z. a+ P! ]5 _, _4 h
    2   clarity  53940 non-null  object
    1 c3 I0 S2 B( x, {6 B0 q 3   price    53940 non-null  int64  8 [: d2 }& D+ ?3 X) |# o) J3 q
    dtypes: float64(1), int64(1), object(2)+ I9 H5 ^& G: l* s6 P! z# {
    1; Q: _. {+ {- }+ |0 I- V; z
    2
    1 Y% b, @' w$ D5 `5 K, D3- ?8 w+ J3 I! }! [9 @
    4
    : T+ k+ ]5 [$ o0 ?( g1 [5 a. f51 V$ ^0 g* l  U: Q* ^- B2 i$ ^
    6+ t. d$ Y  j( C( s/ G. i
    7  x. w/ Y( z, Y
    81 w! |  o: V$ N/ M( h, @: e
    9
    6 N. F8 R6 i5 h; z: c. y8 G) d比较两种操作的性能
    9 J+ d; z% S. ]' c+ D, q4 a9 M%time df.cut.unique()
    3 F$ y' n( H' d" }  b) C4 |, S$ H4 _3 H& I3 I( g
    Wall time: 5.98 ms9 W$ ?' F& ~; E. d; T- \0 i% k
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)1 h3 J1 P4 [. T9 F6 s- M. s
    1
    $ d9 ]  u2 V% A, Y; k+ B2
    + M" O3 u1 G8 ]6 d6 S; t3
    5 T% _9 K! J( M! e4 n% I4
    # {" t* T1 D5 s0 D/ b; m7 x6 t$ X%time df.cut.astype('category').unique()* N0 V7 o: _$ c# x. F2 `
    + S8 B0 J) h1 y7 w8 q* H9 c3 n
    Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms* ?; b3 q* R9 }6 _. k
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']& b. t7 `3 {0 m" o4 h
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    8 |& e7 r. p  [) t$ t6 O3 k8 L1& |: T' s0 |0 Q/ r9 ^$ G' m0 s
    2" ]2 K; j8 r. l, c/ D( M% o5 O
    3
    ! |$ H7 E. J# u- y, |+ l2 [4
    * j# D$ W% Z3 ?2 x/ C7 l2 n5
    8 Q5 {" P/ h" Mdf.cut=df.cut.astype('category'): |' w5 }! s. I8 t+ Z
    %time df.cut.unique() # 类别属性统计,2ms+ c. ?7 z+ ^+ ]4 E4 C

    5 `" ~" ^0 w9 W8 o0 i1 @- @Wall time: 2 ms
    , V7 W; w& F- s: p/ e/ _7 O['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    2 F/ }7 U% f' MCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']! q* \4 _6 e' b) Q( F3 Q4 O2 |
    1! z8 F+ {" ]3 n! E
    2
    ( ^( o5 i3 z6 h4 g/ y! y0 d6 @3
    & r% ?7 W2 j; X6 x& }+ ^4
    8 G: ^7 E, p# `) e1 J. j1 H57 T, k) d- U0 _) N& O
    6
    3 x  ?, V, x7 S( N$ M对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。4 j3 T' c2 u! \6 @- P( z$ ?
    ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
    . z' o$ ]# ^( @; H" X0 Rls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']/ d8 F8 k0 L0 Y+ |! |7 \/ x# R3 W3 W
    df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换2 s( |! ~, {" ^' T  L
    df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
    # n- I  j9 r6 h3 N2 }% x+ h& W5 P, p: l' p( Q% N: x
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    9 c% G/ ^$ U, |) X8 k: I- [! F: |+ {" O2 F% A
            carat         cut        clarity        price
    + j2 F. U' n/ z: G8 W* C315        0.96        Ideal          I1        28016 N. U  G- K* {4 V5 L7 B
    535        0.96        Ideal          I1        28268 f. q& ~" o1 M; w
    551        0.97        Ideal          I1        2830, B: s# B5 r* D* E. ]; \4 }
    1
    ) T$ F* k0 `; R; o' G+ U% V0 D. x4 \2
    # R# Z" F! O9 W9 m0 j3 z/ {, `( F3
    # R& P& X4 X! w9 w9 \: w+ |4
    / x" K6 L( J9 J7 I0 W5+ q* M( C, P# R
    6, \2 j5 a. M2 J( d" t$ P. a6 T
    7. G7 r! b3 s% ?. o0 o$ [
    8
    % Z7 a/ E. r" z' X4 A9
    % ]  G" D6 \) E10
    3 s+ |3 w3 @) ~5 N- N( R11
    4 Q  I- s7 X: |7 W  Q+ W4 z$ N分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。" l, e5 K+ o+ n4 a1 R
    # 第一种是将类别重命名为整数9 c" _6 T. }. a" P/ }: s
    dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
    3 C/ G% P& y0 v( B, `dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
    + Z. p9 s# b- }2 t) o; |- c
    / |4 ]6 q3 s" n0 N4 \df.cut=df.cut.cat.rename_categories(dict1)
    . \  g8 c  U% [: O7 \0 ?" idf.clarity=df.clarity.cat.rename_categories(dict2)
    # h! o* ~+ k4 @2 S. |df.head(3)
    8 p) u3 c& D& T6 B7 v% Q# |( z# G2 U$ ~& T4 i- v8 @9 G, P- e
            carat        cut        clarity        price
    4 m. l: _4 |0 j% r3 ?% O0        0.23        0          6                326' u" D& A& h; q# k
    1        0.21        1          5                326
    4 k/ m( z6 e0 E( X% m) |2        0.23        3          3                327
    8 [& t! Z3 i+ J1. k( B0 }# l( `- E. [2 T
    2
    " `9 R" g1 c4 Y3 X- c# ^39 r: V* B$ M9 k5 p; B: J" t, J
    4
    6 {0 w' ?) ^5 S0 m& G/ K- @8 \5/ E$ Q- k( ?: w, P
    6
    ' ^6 O- u3 p4 c/ I7. U% N( z& A, d
    8' x) q4 k, _" x/ i  [" N8 V
    9
    4 o, l4 k. o% D0 B7 H' h4 B  U104 v2 R) ~( j8 [! x' W# y, Q) D
    11: Y+ }9 t/ @0 M
    12
    3 s' Z1 h4 @$ F3 Y& r2 E; ?; b* {# 第二种应该是报错object属性,然后直接进行替换
    6 y0 s: }5 `! n% P. g) jdf = pd.read_csv('data/diamonds.csv')
    2 \& q$ a' Z& u' ~* ]for i,j in enumerate(ls_cut[::-1]):3 v/ E+ w: q) j
        df.loc[df.cut==j,'cut']=i
    " \; D# P( a8 W. {( {, e* I( ]. a$ z. r/ f
    for k,l in enumerate(ls_clarity[::-1]):
      a" u8 P; a; g, A( w; e    df.loc[df.clarity==l,'clarity']=k
    ; F( c+ I% A# d6 f, ldf.head(3)
    0 b3 C4 l, e% Y( m8 }2 G) o) W2 O8 [6 A
            carat        cut        clarity        price+ z( W/ @9 p9 u5 Q2 \; K5 s2 k
    0        0.23        0          6                326+ Z5 {3 b* J; ]* l
    1        0.21        1          5                326' N4 G7 f( H/ E  ~$ e: q
    2        0.23        3          3                327
    ' z  o: K6 E8 \% K6 }. Z0 t1
    , x/ p" E! I2 n6 f) b7 u/ P% }2
    , ?/ a$ Y3 ^, [( I5 B1 D3
    9 V8 U$ Z) e5 B% y+ ?; T: s3 \4
    ( W' V0 y( R* U$ v& L+ X; G52 [+ o. f: q: J' `
    6
    ( R% j9 S/ i6 A3 l7
    " j, q5 R9 t! j) Y& F- ]/ \, w80 I  @( n# G4 p, T3 `
    9; S; \& B3 b% Y! f. G
    109 n' y6 E9 m& P$ F9 t9 u
    11
    6 ~+ ^0 u5 o/ p6 c/ F9 c7 i  p. i12
    8 X  F3 {" D8 \- O' Z( u13* G7 s3 W" _; N6 u1 w9 C
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。, I$ V3 v' R, B) E9 f8 {/ T8 [- n
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
    7 D' g2 h* ~" `( l# navg=df.price/df.carat
    6 ^9 z6 f5 x$ a) `( `# T0 M3 B: ^1 G" {8 ?7 `
    df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    4 c: n: m9 v' \1 V                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]' r# _$ J  N% f4 L/ \

    - O7 X# T0 c5 L  `5 i( M8 C, Ldf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],1 v6 o- F! x; k9 L& x$ M
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    ! F/ p6 {3 Z8 G" `6 \  ]" Z! Cdf.head()
    - H: m9 B1 u* x, F8 D' K  o  J% F9 d9 j# S# O0 V) A  ]- [  u
            carat        cut         clarity        price        price_quantile        price_list
    3 R2 Y- B1 Z# V  E$ ^0        0.23        0                6                326                        Very Low                Low: [4 q# i7 V/ d; z* c/ z1 h
    1        0.21        1                5                326                        Very Low                Low
    ) p( q! t, Y6 O0 o8 ^* E  d2        0.23        3                3                327                        Very Low                Low
    . Z; i+ ?/ N' R! u3        0.29        1                4                334                        Very Low                Low9 l# D. q5 x% `# J' G+ {+ S+ w9 ^
    4        0.31        3                6                335                        Very Low                Low                                       4 z& E1 q6 m( `7 Z

    & G* \; k. i  t. z) U/ v15 t; R. w! ~0 ?2 G1 Q5 s5 H
    2
    % B3 n: |/ d, |1 U0 [3
    ( c  a/ J! ~7 }: X, J( Y4
    $ J8 E6 ]9 T' R5: T1 j2 [8 `/ w* n" h
    63 k( w7 I4 u4 n0 Z% E
    7% O3 S" _; n! P) V, N1 X
    8- Q8 x- W$ r' V  ~2 \
    94 l0 b/ T+ s* a8 ^7 _
    10* a& m# v* v, K; A6 D" a
    11. E4 c2 J, q& H! Q
    12
    ' |7 h* V8 p8 I3 X$ j3 X( I137 }% Z* ~& x& A: r3 r" n
    14
      Z, \1 ?' G/ B( Q' b  y* U/ H# w154 ^8 v* r* d! Z7 H# M4 Z/ d% D
    160 E" H- H" x1 {' j+ t/ K- l+ h
    分割点分别是:5 {& h) a+ T! \5 B
    0 L4 ]- c4 S& T4 n2 Y# n
    array([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])8 R# T6 N( m* R1 d# r
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    9 w+ _! s$ W0 z. X- a18 Q$ V9 B( k' }  V% e  [) u% q
    2
    $ o2 C' ]- @6 `; Q4 u第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。0 D& V9 S, K  B/ i, _6 f: P
    df['price_list'].cat.categories # 原先设定的类别数) Q5 B, d1 W$ B1 C
    Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
    9 W2 u0 i( ?" p1 L1 A" Z3 e
    ) ?4 [; k: I4 e9 c8 @0 `" a0 udf['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别- f. O) E! H3 x5 e
    Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现0 F4 h5 Q5 b$ P
    1
    $ ?/ Z: D4 d9 V9 K- C& A2 V2
    ! _9 B: h) z: T5 R) A6 Z# x2 X: j3, l9 }2 q6 [0 C
    4
    3 M6 H7 w% l" q% M! m7 Q5( ^6 b( e4 B% T2 s; b
    avg.sort_values() # 可见首尾区间确实是没有的8 D- [* R7 \' H6 S4 b7 E; O
    31962     1051.162791( o2 i& R8 z' V6 L6 l
    15        1078.125000
    ) N  V' X# [0 b) j2 z6 M  ^% `* f4         1080.6451613 E3 @$ p. p  n
    28285     1109.090909+ G3 v8 ~/ F! H3 J* A" P
    13        1109.677419, P; a* N+ N) l+ G3 Z: \
                 ...     
    4 z! g2 H$ T, a  I0 N5 L6 f  ~3 y26998    16764.705882
    6 n( W+ A6 W( |/ ~! [4 D* u: t1 E" t27457    16928.971963! Q* H4 X/ d; s' F0 v
    27226    17077.6699031 n/ M' p: f* M$ G( w
    27530    17083.1775708 c( R) X  U* j  G
    27635    17828.846154
    , V8 Z; ?, a) K9 G0 z+ V11 z3 V, R! X( t# Q6 ?; a
    24 i2 w) X, M+ O6 t: _2 G  z- ]# Z
    3
    2 J/ {6 |* b. E0 \" g4; a4 z) A& @8 R: R; S4 A
    5
    9 [$ a' ?' N- x# U& W. t6( e3 T" G) R5 ?0 b
    7; Q5 k$ N9 \1 [+ s  Z
    8
    6 b: o2 e  H7 L. R- _9# ?- q. ]  _" o9 n$ z
    10
    3 n& F3 J. V/ e3 ]; e2 D117 S% |0 K* `  q$ h$ c
    120 N  s4 x# w. {
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。( B4 u$ }' H, |. E# |* V3 J
    # 分割时区间不能有命名,否则字符串传入错误。9 R& C; u. y+ M
    id_interval=pd.IntervalIndex(
    / ^' j. j9 ]) }- [2 p& v- f8 Q6 i    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]- I# h" o+ x* F8 _8 E  ^
                                )
      x" N' a% l0 Z7 P  sid_interval.left
    * d2 k: n  P$ I! ~+ f8 Z6 Gid_interval.right2 Q: O* B7 Y7 G: z, \
    id_interval.length                           
      O9 r8 W4 n, M# C  m1
    6 @! A" z, [+ X& n9 {" [- n% t21 H2 B6 m- x, G! P
    31 r6 @4 T: z) b6 r8 ?
    4
    & r, J4 O9 ?% _50 O) n& a4 x' x" p! h* m: n# |9 M
    6
    $ D: {: l4 x9 P0 y( s8 B, p1 w0 {7* G. t- z! f/ p5 \
    第十章 时序数据0 x5 M1 V2 }+ g# D% A4 n5 i
    import numpy as np  C- V/ ~% n8 X1 R' G. {7 \2 Z
    import pandas as pd
    " h  Y9 U7 o2 K13 j5 z0 N! T! y1 X; T2 ]2 K
    2
    : \  i5 _, S; _( N2 m
    4 o; g& l1 N  d% M3 s6 U/ Q+ F% w0 R& o) J" Q
    10.1 时序中的基本对象, x* W" q0 T) N0 @6 G& W7 a
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?6 D& [1 A$ ~% W% @1 A8 g( {

    / ]7 ^0 `1 Y/ E会出现时间戳(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的简写。; h; ~) {$ c$ K* G$ L' ]

    + v1 `8 N: D5 V2 R$ Z% u# J" X; X会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。7 P# }% m6 T1 P

    ( Q' t2 T3 G1 |) P; \) Q5 V" G) ^* M会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。( X0 N  ]7 u9 Y" V' L

    $ y4 t/ d) O8 m- r2 R! k会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    3 M. l* o' {& V+ t
    3 u! S& y% Y" b1 d" x; T  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
    ; b9 ?% I9 y8 X9 @' D; j8 ]2 |3 J0 P: P" @. {# [6 @$ i+ G. z
    概念        单元素类型        数组类型        pandas数据类型( j4 Z1 D0 B2 X) M% O
    Date times        Timestamp        DatetimeIndex        datetime64[ns]
    7 M, @: [, |) \+ K1 xTime deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    8 J/ v' {/ r# z# Y4 k# vTime spans        Period        PeriodIndex        period[freq]
    % u! Y9 g" P( x/ QDate offsets        DateOffset        None        None
    3 S3 _, y/ a! i: w  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。7 h( _; `. b9 d' B) o
      D! [# Q1 g/ E# t: v) g
    10.2 时间戳, r) z3 n8 g3 g; j( [! l) q
    10.2.1 Timestamp的构造与属性
    : s. Y4 F& A3 {5 Z% L2 d+ I单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
    : n- [8 W! z. w8 Z9 p) h5 a6 T# e9 \7 Z" f
    ts = pd.Timestamp('2020/1/1')
    # [, V+ e& s& B6 j. _% j; ?4 \* w! H& c* o. x5 W
    ts
    + K0 ?% t* f) P0 u; Z! T& dOut[4]: Timestamp('2020-01-01 00:00:00'). \( f' R3 P. y
    * U; U- I6 p$ L( S& ]
    ts = pd.Timestamp('2020-1-1 08:10:30')
    ( d+ [9 P; u2 ~/ J* y, A( W( t4 q2 ?+ t
    ts# T1 J8 e% `0 w6 ~3 U
    Out[6]: Timestamp('2020-01-01 08:10:30')
    , L% Z5 {6 F3 w6 W! G  V. o+ V( x1, s" s+ g# x7 Y4 M' w2 B
    2
    6 w7 T) z- a+ F' _* M& G3
    " i' S  n  z9 [! p" e! Y5 E! g% `  T  `4
    9 |. ]* Y  q; F$ m+ Y1 m- {5
    2 p% @* i2 J% w. L% W/ P60 `% F4 u9 K; D. H6 G
    7( {6 P" L# R) g9 E8 |, c
    82 J4 l+ U# N$ I4 ~9 T- R/ q+ I
    9# `  D5 j5 Y# N' a7 }: K$ @
    通过year, month, day, hour, min, second可以获取具体的数值:1 N2 v" q4 i5 W0 H
    6 \/ E" s( \  @- E' v. b, J. c
    ts.year% R3 F7 I& D/ V' N7 b- q
    Out[7]: 2020
    ( d# M2 M+ `3 c! X7 [
    ' r( S; E6 Y/ V2 n0 Q7 _! d. kts.month
    + L: K  q" X1 i' I$ ?; {" C! _Out[8]: 1
    ' f# I% W! |- Q" `- q' n; z" K% n; X' V0 G
    ts.day
    * m* d- T% j' P+ A& M" ?Out[9]: 1
    " r- v( G; |( \0 L& W8 P7 o" }5 \( j# I/ k( X0 Z
    ts.hour: `3 [+ N2 D: p
    Out[10]: 8- h4 V' J4 L3 k

    ' I! D6 w+ u7 `8 Z0 I, C& g. X8 \ts.minute
    % j& k9 i" y% ROut[11]: 105 ^$ V) `  ^/ ^4 `9 w
    7 m% H8 L  I7 w, m& \! H' C
    ts.second; C4 b/ G9 O' E# _
    Out[12]: 30' n) H* D: d5 K5 L5 J, z

    1 \+ @: P- r8 w# \$ V$ Y1  e+ a0 ?9 F/ X5 ?% B
    2
    & J% H+ g( U( T4 H3
    9 a  i/ l9 d1 U2 f; x) _$ b4. b  H/ D: {/ v" }' K+ m7 C0 p3 b
    5; V0 j6 V( i" @4 J
    6
    " E  o+ X8 C' o2 z; s7( y5 S9 ~1 W( ?/ U* y% F
    8/ f1 a) J# k# P! s; _
    9$ t/ d1 k0 f/ |$ H  G3 D
    10
    / Z1 ]9 G  W- M$ x, D11+ L, W9 g; |4 h0 r8 A1 T, |
    12
    % s% h. t% N$ b* q13
    0 _/ F3 L# b+ Q14
    3 m/ n3 `/ q: [- T15; M6 T4 G5 i& y8 v
    167 I* W- P# n' `# j
    17
    % f# C' T" A! V- u0 o: ~# 获取当前时间
    $ a6 k5 w' M8 W( r& ]9 H2 tnow=pd.Timestamp.now()* Q4 S! }* U/ V( `1 H3 x
    1
    % s: x% w3 {* l+ D26 o% n! ?! g& }: X( K9 z- X5 d
    在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:0 J: L5 z+ f% t! @% _7 W
    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)
    ( C+ j3 U) P; C2 U: K* ETimeRange=
    # a' q, u5 X3 U$ G; U5 _" @10
    5 a' i$ }" \/ U& [92 G* K) t0 S  }, W5 d* t: V% e
    ×60×60×24×365/ i! l9 y/ {# x
    2 : l& c) R3 S. Q* W6 O
    64
    + m, r5 ^) ^2 K( K+ S3 A- Q
    ! E# h( a4 B4 b6 b3 B0 {, V: K" M/ q7 I9 K* e0 h9 B
    ≈585(Years)
    4 S- J0 H& t! i: ]4 A2 O- A! F& L- i8 T$ u
    通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:( }2 w& T7 T/ W& u
    ( d' n% [; B) z/ z% X) a) Y# W5 {
    pd.Timestamp.max
    & u4 i# X$ S; W+ e7 V4 N  j! KOut[13]: Timestamp('2262-04-11 23:47:16.854775807')
    . f+ P# y) ~6 H2 K- e7 c8 T& L/ C' t( o+ `% [% Z
    pd.Timestamp.min+ i8 V/ J6 a  F/ a4 V! i( o- [0 J" l
    Out[14]: Timestamp('1677-09-21 00:12:43.145225')4 t$ y+ E9 W8 x# Q7 e8 \' |0 k

    2 o4 i% O( K7 j$ |2 V" Vpd.Timestamp.max.year - pd.Timestamp.min.year) h% z/ y, F7 ]& i3 F
    Out[15]: 585! w1 D( t- L9 i. T5 E3 y8 [9 o
    1
      T+ v( @' f+ a5 b! p, q! \" `24 q1 J; S+ }4 @; h
    39 R6 Z3 i, K4 \$ G6 r; V
    4
    * l5 w& h% }; Q: ~0 v# P5 p+ b  H5
    + x2 I# X, Z: C$ b  i, W" F6
    6 `( Y6 J- Q) j1 N- ^1 l4 L4 X1 A75 U5 t; U. v) k9 Q, ~
    8" k7 C1 ~! N2 t# N
    10.2.2 Datetime序列的生成( M$ Q& w- i# b. T; N6 T
    pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,. o( l, O1 @! }$ B. [' K+ s2 @
                                      exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
    9 I6 {3 [& t; V9 g( }1! f% f4 s, {" L: T) M2 J% _; n
    25 m  M, j6 ~. G5 S9 k( J6 z9 n
    pandas.to_datetime将arg转换为日期时间。
    1 P3 ]6 t4 B, q1 z6 c
    : U: g; c# U6 u+ F. qarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
    % y( P: f/ ~* Derrors:
    5 ^5 T. `% ?. B. L" ^# V- ‘raise’:默认值,无效解析将引发异常
    7 M3 i# l6 v2 b- ‘raise’:无效解析将返回输入
    7 u/ l" t' D# j! x- ‘coerce’:无效解析将被设置为NaT
    , n3 h3 v8 ?" P5 K5 Fdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
    6 h) B# s5 G6 h& d1 c# M7 X4 T# xyearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
    " ~4 i# ~. A" F9 d: B, O2 Y+ a0 Nutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档9 }! E% Y! f* T5 w
    format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
    ( R1 c5 c" b7 }: j7 }5 ^- punitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。# v- R$ U0 @8 d. Q$ l5 G" {
    to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:0 m3 N' G2 H! X6 [. ~  b  C" {* B8 X
    pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])0 U% V/ L# w1 `3 Z2 t% ^
    . W) {! H" V& K4 x
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)  o: ~( a5 D: r- m; @* w& b
    1
    ; L- d! k6 l8 L- L/ i0 Q2
    ) u/ q" J0 E. m! u$ V6 S7 i0 \6 V3
    4 @; x; [" s8 {9 v! J3 H在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:! O: Q9 J4 V2 J0 [* u* M6 @  s: E
    , c( B! E5 H! y& z3 P$ [- }+ R0 q
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
    , x' x, V' k9 P, u' a0 b% }5 Htemp. P6 o- i% A; o0 ^, J

    - B: I" n. t" U5 l6 NDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    & ~0 f8 `$ S: a2 T9 Z1
    4 I" A, I  _8 G* g2 h" ?% b7 ]2/ l% W7 |1 N0 J& ~" b
    3
    3 ?, ?/ H; j( W( z+ c4 o+ N4
    6 }, |5 k0 Q" o" _( U  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
    : |0 d5 f; ~  C- n: U- A& w' r' ]; f' a; }3 f+ e
    pd.Series(temp).head()% h# x4 F7 m" t, K" n) h) F; h
    ) A5 i# z- s" k3 {  l3 g( z
    0   2020-01-017 C6 ?7 _/ m/ a; ~0 H  [! N; M
    1   2020-01-03/ b* k7 O& P/ d1 }- Q' `
    dtype: datetime64[ns]
    & |; i# y9 i3 F0 t" v7 R+ l. t1
    ! {) s! k8 e2 M/ V3 h2
    & v) M  A) n; {0 U8 y6 r3
    - z/ D( ]% b$ y/ f46 @! l' I  N& f: a3 N" m% Q2 U
    5
    3 a- g6 N$ b9 g. h% S下面的序列本身就是Series,所以不需要再转化。) ?. F" G$ C2 X0 }6 G' z/ P

    8 p: o/ ~# c" C1 Hdf = pd.read_csv('../data/learn_pandas.csv')
    & i1 M7 j  H5 O& J: ]* `' {s = pd.to_datetime(df.Test_Date)
    7 D  V* z  M; m& B  T# Vs.head()
    " C" `+ J# y( f% P
    5 z: i3 e+ D  l9 w' w+ `- \0   2019-10-05* v2 Q3 U- r' y$ h
    1   2019-09-04
    ' [6 V9 G, l7 ~' x/ B6 _& y' n2   2019-09-12
    8 x6 L# v+ n; i2 ^  Y) o& x: |$ X3   2020-01-03, K0 h! c. s2 {/ d7 g
    4   2019-11-06
    - U8 }$ H4 O6 I0 bName: Test_Date, dtype: datetime64[ns]. c; J( f% n4 `+ N3 x
    1' c3 j, w& x3 j- c' Q8 w, z
    25 `  V/ t; F0 m$ K4 _1 ^! R
    3
    $ E3 S- c% m  s4
    3 z5 U' Q$ [- b! B5
    5 K+ T! Q0 A3 G3 g/ {( x6
    + B% a  s5 ]+ g* U& x1 Y7
    8 J5 V' A4 r& Y8
    5 U7 z1 Q/ |0 a) `6 ^- I9
    . z8 @2 \% t/ ~9 |10
    4 X% V% x5 w4 z/ N/ t( H8 n8 `把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
    ! S# ~- U; g9 Ndf_date_cols = pd.DataFrame({'year': [2020, 2020],
    $ P4 _1 _- \0 W8 Q+ D2 o- |8 a3 I$ X2 t                             'month': [1, 1],
    # B! d; r. j$ {9 m; U; Y                             'day': [1, 2],2 T, ^9 Y: e! o+ z* g7 _
                                 'hour': [10, 20],% Q7 l" U) B0 V7 K7 t
                                 'minute': [30, 50],$ w3 j9 E5 C. N8 ~
                                 'second': [20, 40]})2 K/ d8 k( D' N- p; [
    pd.to_datetime(df_date_cols)- l  j8 ]% P3 u. L+ u( F

    0 l* l# V( U% V! k4 {, c0   2020-01-01 10:30:20
    $ ]6 o. p1 ]* @" y: Z1   2020-01-02 20:50:40
    " \# }) K2 M! s8 L  t  i+ g; W) Tdtype: datetime64[ns]
    + @# t# A' B; m/ d1
    ; c5 c) J: f+ @2
    8 M2 l% T1 E$ ~- L; T3* S8 L1 I7 L6 e! O* p
    40 m0 u% x  v" ^0 z1 }
    5: R; `% u6 Y3 `
    6
    , q2 I& z2 b' ~5 ^' o/ K7
    7 l! A' B* N$ `  `! B8 M  y) B: F8
    7 ?$ \  v; G0 D! V) \1 b. u% e0 A  @9
    & G/ x# l$ ^+ D10+ t4 v% j  I" V# H$ c9 I
    11, v0 [8 N) T7 E( w+ ~3 q  X
    date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:8 \5 X' \+ d8 X7 y
    pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    ' K  R( }3 l; Y( vOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    ( {/ p' n  f  N( u6 d0 U9 x3 R! ]( L9 k; V  S& X+ q
    pd.date_range('2020-1-1','2020-2-28', freq='10D')( m0 h! d2 O" Z4 H+ u
    Out[26]:
    7 m4 I% U" D5 M/ P4 q/ k! U5 nDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',) V  O  g5 j- ?1 ]/ t
                   '2020-02-10', '2020-02-20'],
      U. v2 _4 s" R! H              dtype='datetime64[ns]', freq='10D')1 q9 H! @$ Y8 ^6 f, k
    - u& x, Y* w$ g9 Q
    pd.date_range('2020-1-1',( ]5 N% p& t* B% O; k
                  '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天5 ]/ U5 [6 j) g7 Q/ h" H

    . Y1 O4 C! c" O# gOut[27]:
    & z, x) Q( c' Y; v) [$ x* T. |DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
    + G0 P; g/ t+ t/ P9 S" I               '2020-01-24 04:48:00', '2020-02-04 19:12:00',
    2 s7 o3 y+ C( J2 p3 q0 V9 k0 R               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],& L) }; w; \& v5 z/ m4 K
                  dtype='datetime64[ns]', freq=None)
    3 @2 B+ r  x1 S) H6 q0 L
    * L  b# l8 @1 f3 E1
    / l$ {$ x* `& _2# C, y2 M$ f+ E. o. |3 f& g( L
    3& A( Y% F% a1 T1 w) |
    43 H) P) Y( G" M7 T4 P
    5. Q; ]7 W* a& l4 y6 a% X
    6$ i7 c8 H+ q: x- ~
    7
    9 f$ X' P! x' {7 u8
    ; |; g" s6 e; x2 ~7 \# u1 e90 @# S* C0 K& o2 D& _% o
    10( D& L0 c& ]& C+ q. g
    11
    / ^. @' y% [( z, \: Z% w: E/ X12
    * z6 L- u  U8 e) ~, s" B1 Q13
    7 F. A9 G+ d$ @5 n: y14
      V2 c. u( T. F8 f9 B15
    : i  Y" F: F. x! e. ]" |16$ L. ~  z( K( U- O7 b2 c
    17
    7 p: `) L) ^2 c% A* S- C这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
    / [1 Y- W; Y; I( q' H$ X
    # [8 O# y' Y4 U【练一练】) z# H$ p4 r) s& a
    Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。* _# ~! t) v; T4 b
    , O+ c! z- y0 S) n1 m
    ls=['2020-01-01','2020-02-20']" I* y% ]" r0 ^, H  x8 N
    def dates(ls,n):
    * x$ J: X2 p9 G8 v3 @% X9 h3 S    min=pd.Timestamp(ls[0]).value/10**9- f4 C6 k2 ?  P" {% ~
        max=pd.Timestamp(ls[1]).value/10**9. p; \& P( K- r" L- C
        times=np.random.randint(min,max+1,n); C$ b! \# H4 [3 s) B
        return  pd.to_datetime(times,unit='s')
    ! M& }# ]1 p3 fdates(ls,10) 7 h$ T9 C9 v2 T

    6 s3 t- [  m8 s) a0 [0 CDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',; \' D7 D8 |, S! p( b  k' q# P
                   '2020-01-21 12:26:02', '2020-02-08 20:34:08',- V/ _. A; l' P: j. @! |
                   '2020-02-15 00:18:33', '2020-02-11 02:18:07',' D, I) X/ U9 l9 `# `& t: W- N
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',
    5 T0 {0 S' v+ W& S2 G* e               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
    . k6 O2 w' F4 g, z              dtype='datetime64[ns]', freq=None)
    $ a8 B% ^7 c# p3 F0 G! a) E; t' f1
    4 P9 b- q6 Y9 I/ _1 c- M2
    ' {2 q2 ~. b, \9 ]3& j1 S# C: V" Q( x1 R
    45 z8 s6 g) R5 q% M) R  L
    57 s% Q# V9 x% X$ O3 f
    6- H+ t8 c2 I0 ]  x/ r
    7
    3 F+ F; ~" U2 D8 ^  z4 x8
    ! n! ~0 u4 A6 s- L. X; ?9
    + g7 ~: ]4 v; {10
    2 x: F) `, @5 K11* i) ?1 o. R1 c
    12, k$ |  ~7 V! C6 |4 @) Y) d
    13. }2 Z" w+ S+ u" P6 Q/ d
    14
    5 `- [. x2 o1 M% a( kasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
    2 u2 q7 Q* n% M; ~8 I3 Js = pd.Series(np.random.rand(5),
    ( r+ L6 C9 Y4 B+ o, Y4 M! ?            index=pd.to_datetime([
    6 P: [  I! v3 E6 Y- b; e' [# G& x3 g                '2020-1-%d'%i for i in range(1,10,2)]))4 C# a7 |* V# Y- e5 O

    ! ]; [1 c) I6 t5 K  {, [5 _
    9 Y: R3 z$ J' q7 b# M% Fs.head()
    7 E9 _- ?5 o7 _Out[29]:
    8 c$ X, m, b5 c7 q4 U+ m8 o, m2020-01-01    0.836578/ p6 h8 C  M$ N) J* c% q( S
    2020-01-03    0.6784195 [2 i9 M% F: X
    2020-01-05    0.711897' S! l7 t& M  Y5 |2 O  |9 f$ E
    2020-01-07    0.487429
    " e+ F. Y" k' w0 N) h  G5 L2020-01-09    0.604705$ G+ m+ {4 d# R2 [
    dtype: float64
    . |. b! n+ C- e9 B
    % I6 N* U) _- E. ts.asfreq('D').head()
    8 l: l7 ?' s( P: ?! u% C6 nOut[30]:
    4 v8 I3 m* ?( q7 M& N2020-01-01    0.836578# Y7 C9 v( H) _1 x) `
    2020-01-02         NaN3 a, d7 P7 R' S5 s. D
    2020-01-03    0.678419
    1 _1 P7 @/ i* A6 ]$ M. Y! x& j. |2020-01-04         NaN/ f( c: f5 Z4 i& z$ ]& M
    2020-01-05    0.711897, |* p& w3 R, {0 R3 ]# z* u, c
    Freq: D, dtype: float64
    & A7 ~! i4 ]+ X' _3 E
    ' Q% j) s! w; M6 s) Us.asfreq('12H').head()( k& O. J( D7 W( O1 o" A8 C. ^
    Out[31]: ; l5 H" t7 M- |4 }) v
    2020-01-01 00:00:00    0.836578" p" U: Y4 I( _( U& H! s$ L
    2020-01-01 12:00:00         NaN
    / e  M& N( ?7 S; d6 ~0 n2020-01-02 00:00:00         NaN
    9 S, p' T2 R- b- @; R2020-01-02 12:00:00         NaN. @# u: d  U" L. b9 s3 L) @1 G" Z" N2 v
    2020-01-03 00:00:00    0.678419
    6 m) i1 c, o3 _" J: S. u4 BFreq: 12H, dtype: float64
    " b- a1 q+ P4 @" N
    : T/ A: C" T" f, z" w* X1
    + A2 F: k! _* p7 L" ]2
    + o; I+ p* b. J# ]6 Z35 t( l) p+ S# J3 E
    4& }: ^, t, t7 J8 {
    5+ [& U7 H" q  Y0 ?  W4 b: I
    6$ u9 P1 P: A  {7 n, G( h
    79 T7 N4 l& l6 J5 p7 b- u  Z/ D$ ^
    8) x% v. t: G+ B! |, e) E
    9
    2 i& {. K/ k4 k7 v8 G10; [, E& x/ A" R% X. d3 h! L
    11( O/ f& A3 g% J6 _; O& m$ x
    12
    1 U1 j* A' K. Y, D" y2 w0 o13- I. x* Z4 Y$ `, l$ R
    14
    2 i! k- n( M  T$ a) ~15* M% R' H# e. u2 @: b$ i8 H
    16/ ]) [, i, w# ~5 T6 r4 T; f
    178 B+ Y% _! r6 o, j# q+ z
    18' m+ D* E/ q& K% b/ x& Z0 h
    196 G" k) d" j1 B% `# j2 C$ M
    20" f& D9 K! V" f) i# x( q
    217 ~" @# s3 O4 U$ m; J; p# M" W
    22
    8 Q: ?! V% ]) d* v6 o- Y4 I9 x23. W$ t, X3 J) @0 r9 g9 y
    24, v5 z, ]6 q8 G# {' U! B
    25
    8 Q7 W  y& T/ h: I+ T26: x/ P4 m2 t; m5 |* o" {
    27
    + M* v  Y' `( s7 G7 Y289 u% n& Z' V" y+ x6 H
    29) b3 \* b4 z- u# ]  E7 {
    30
    , b. k8 K8 Y9 _( O9 |. S- V31
    ' H$ x2 M5 A# ~+ l【NOTE】datetime64[ns] 序列的极值与均值
    4 b; f( m: a$ p; I) y  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
    % m2 n# k6 ~% n! N% d6 o. L' [3 K: z# R+ p: O. g5 s( Q
    10.2.3 dt对象' p- Y6 ?' s$ d+ J4 Y
      如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。, t' l+ y: I  Z5 |! x
    ' s, o, Y$ k' M" t' o. |! m
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
    7 D2 w) r+ z  ?2 ~) Ws = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
    ! a3 [3 x& E3 T# [7 E$ H
      U- X; B! J8 i0 b( ds.dt.date
    ' U: z8 F0 v9 I6 _. q& qOut[33]:
    ; l7 [; }( g6 L0    2020-01-01
    ' s2 z  M4 @) m7 v6 y+ R: m1    2020-01-02
    2 ^3 O. w* C" }9 i( S# H5 y2    2020-01-03
    + [! y3 p, H0 P9 b6 Tdtype: object+ K& l, ?1 |# B% }* x  t* J
    7 F" L3 M# h9 K8 O
    s.dt.time
    $ c9 a0 M6 ?3 ]8 a. n3 }Out[34]:
    7 d6 d" q+ k9 e$ ^2 S0    00:00:003 ?+ `5 L7 ~' C/ v5 I4 Z0 L. a
    1    00:00:00. c" `: y4 u( K0 D
    2    00:00:00
    " \2 R* j- r; _7 r% p$ mdtype: object0 F, g1 V$ G$ N; P  R2 h& J, a
    , W- E+ _% R0 c' Y- e: ]
    s.dt.day
    $ D0 `1 {2 K2 i7 k. s8 o/ K; lOut[35]: 9 l5 O7 a+ _. s( i0 J7 i
    0    1
    + l, A0 L! {4 G- T! i* s7 Y. t1    2/ M9 B5 a/ E. t' [3 H& |  q1 I
    2    3* w5 B( O% H( {7 H( D& o
    dtype: int64
    % j  J1 S6 k$ \* r6 v& P. O% t+ x) [6 ^' t9 u" y7 Y8 L
    s.dt.daysinmonth
    2 p" A" R( R3 W1 dOut[36]:
    , M/ j- x9 A) x9 G0    31* }2 J% d% R3 p
    1    31
    0 W7 H9 V* `. C/ }, f1 Z2    31
    # y- \: n' u# [, N, v' k0 y" F* fdtype: int64
    ; ]/ B/ t  Y5 m0 l* B8 z7 F9 r" c8 M. d0 H& F1 t1 o
    1
    ( J1 c! `3 h- m" A  {2
    , a# S1 b; v7 C$ h# o* |3
    " H& I' Y9 o" @9 F' K! e4* C+ L1 ?1 g! s' ^& W7 R6 y
    54 L. R5 H) p7 B, f7 a9 W, `7 }
    6
    * R$ M" b* e( f, d# S7" J8 w7 u/ N+ {& h% f# x
    8
    * D; R* W" A  W. T8 w: t' P91 ]/ m4 {: b4 G" o+ {/ i
    10
    1 p7 ?- }' c1 O11
    4 t; U9 {( x# R- ^+ g12- M8 g/ ?( F- b
    13
    ; \! Q$ G5 ]3 a% c& ^  C1 `8 B14- Z; q0 H' a8 [! K
    155 h! z# o2 V7 C9 G3 T5 a4 T: e  _
    168 b5 m7 p& _+ g! u9 ?7 c
    17
    + L' R. O$ e0 n0 _  x) c18
    ) C+ _+ g) }6 n5 U: Z19
    , E: g! K3 w# |208 P2 H- F/ }3 g6 B' j
    21
    7 s# [, N- Y  H8 K7 ?22
    # n" Y' x, M$ k23- {1 N& j, P+ ?
    24; v# i! D% m: p, ]2 `) ~7 o
    253 R8 |( V6 `' v6 @4 G: P  q
    26
    9 v7 u- \2 G, `% q  N1 u27% s4 U3 D$ M' o, F) C
    28$ e2 `; N3 A' }% J1 l4 C
    29+ [: f+ ]9 c4 S& h6 m. z
      在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:# [+ Y, T$ J; g- s" v

    1 M0 U; C1 E. o4 P; vs.dt.dayofweek
    - \( N+ \) N$ w1 g9 j9 p( O" u. A8 F% POut[37]:
    : H, w  p3 Q1 T9 O0    2
    ' n* u0 m  j* V) ?1    3* f9 |* o  [) n! e" P$ N
    2    4
    ( L1 f/ V7 e3 b7 T$ Bdtype: int643 c1 _3 o# y5 K! t

    ( E$ ?+ T# h9 v) `s.dt.month_name()
    8 `% ?5 b# D& E$ D# h5 g+ U, ^Out[38]: 6 s8 r% `" K, y; D" m  U) ~
    0    January
    : d( {4 [" E: ?( `; `! }1    January
    " @- L$ e: y! f, V: Y/ |" ]2    January" h+ o% x. b- B
    dtype: object1 G+ Z( ^( R5 _+ s
    $ `7 c( B2 ~3 V* R+ K
    s.dt.day_name()$ Z# {! k! n, G
    Out[39]:
    + P0 b" U% F& i( y0    Wednesday
    3 f% h9 g" x5 b) r1     Thursday
    $ J, s  Z+ w6 {4 t" \! W7 {2       Friday; s  N+ p( n7 Z; q( E
    dtype: object
    & F( L; W0 J2 g+ F
    0 _' M# x% c; F1; e+ f1 e5 P6 U! B) y3 w( z
    2
    " u( H2 H  w$ J5 O5 r5 ], o3
    * n2 N5 z( m( l7 s  c( K8 b6 t4
    - J' L1 X2 k- m5 h0 `+ d- n! Q5
    # S  d. n6 ]  S. N61 X; \+ `, p% a0 P" K& H: i
    76 N7 V" \' ^2 ?$ f0 l* j: S8 q9 J
    84 u/ o. j9 A. ~* V- C- ]% Q
    9
    ) H7 J( w( Y$ r$ a, n6 |4 @+ z108 e$ d: n% B* |% H; P0 G$ V
    113 V9 e0 p- Q& h/ g: R2 Q
    12
    0 U+ X" X- R6 _7 }0 j6 O* P13! Y! n; r  F0 `6 D: J
    14$ @. U0 V0 U- R, ^" e
    15
    $ L$ D  x5 H* t1 q  h3 z. e16
    ; n. r/ d3 c2 ~+ I174 {. L! j& I1 n8 a3 ]
    180 x2 @. D/ j1 k6 p( q8 I
    19# {8 I. Q  L" L4 T% e2 N6 G) s
    20
    + K6 B4 D% S! R- H" Q/ W2 S第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
    ' x- V" K$ {- [4 M( ls.dt.is_year_start # 还可选 is_quarter/month_start& Z* p% x4 @7 |! P: e  y" s. D
    Out[40]: ) s! A6 q+ H: U# d- L! T6 X$ T# K) C$ L
    0     True
    . d2 r# b; }7 ]( f3 _1    False5 g4 V1 W; m) u/ R
    2    False1 A1 F9 U2 L6 t
    dtype: bool
      C4 H- q- d5 V; Y: w* S% m- I: ~, c. F' H. F! M( e4 P  J
    s.dt.is_year_end # 还可选 is_quarter/month_end
    7 R5 [2 ~, A. e3 `4 X* |Out[41]:
    9 I5 M; v3 P1 }" i8 e0    False
    ; V# U5 a: E4 @4 Q2 A( f5 i7 {1    False& t) H% Y# n, z
    2    False0 x) C' Y. I9 N7 u
    dtype: bool
    1 _3 Y7 L+ e" s/ s9 }1  }, y* R& [) V9 x
    2
    & H: e; T' W( |: K( Z3% J' G: y' D% K2 H
    4
    - g# m% y( U. J0 f56 D4 B) p; j6 _
    6# m& `7 t4 _( L) o5 X
    7
    ' m! d: C/ m# T0 e4 c3 G: \& v8
    & B: x, G4 N5 i3 f  y9. i0 E( Q  I8 l/ E9 O
    10
    ' _- U: H) u8 P  b6 W11* a- o7 x/ L0 D; I& V( b  F5 ~
    12* v8 y- e) ]/ l, v3 E  ]" X' c
    13
    $ i3 o" h6 g# w# J第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
    % x# _7 G4 c" }, y4 Y7 F: ws = pd.Series(pd.date_range('2020-1-1 20:35:00',
    - d5 \( D! F! l9 v7 @                            '2020-1-1 22:35:00',
    ' z4 P' y0 L1 r1 q* d1 x                            freq='45min'))
    9 r8 P8 y6 M. C* e
    ) q. V$ d" V( ?7 ?' m9 g5 K* J; j) m/ y5 H" }* s) y8 {# [. ~
    s  g9 ?& N, t" N( l
    Out[43]: ( ]& j) _+ r& w0 n; X
    0   2020-01-01 20:35:00
    0 e3 |9 L* ~* [- \0 K9 ]8 H$ d" h1   2020-01-01 21:20:00/ `* \" S! H" O4 q; @( P3 n- }# \4 u. S- b
    2   2020-01-01 22:05:00* D. }) g7 {* v1 R6 c
    dtype: datetime64[ns]
    % _1 e4 i. h6 ]% G. W1 x# V
    0 }* K0 [6 b1 n8 v  L) Vs.dt.round('1H'): r% P; ~1 y3 `
    Out[44]: : e2 u8 i5 W8 w9 C% y
    0   2020-01-01 21:00:003 n; G! T3 X7 P
    1   2020-01-01 21:00:00
    ' u3 g% f6 Q$ k$ g$ e& K, \2   2020-01-01 22:00:007 B3 M* W( o# n: A0 G! X0 {2 ]
    dtype: datetime64[ns]3 t, u: a1 Q$ q$ l, e9 P

    + y8 ^( {; j0 J* Ks.dt.ceil('1H')
    ; Y! g3 J. C0 o0 bOut[45]: " Z* C9 Y6 @3 T$ m
    0   2020-01-01 21:00:00
    3 O0 [/ I8 t$ B4 Y" w5 G3 S1   2020-01-01 22:00:00. G1 `# n& L) J0 Z* J
    2   2020-01-01 23:00:00
    % {7 T  K7 m5 J3 r( {+ y! Adtype: datetime64[ns]& X* f$ b* m6 h. `
    , P$ I& Z$ r4 L! S: f
    s.dt.floor('1H')
    - P% P& Q' f) ^6 \4 r& aOut[46]:
    1 |' _" p# a4 w0   2020-01-01 20:00:00
    2 \0 G$ W5 P1 F1   2020-01-01 21:00:00( Z* V3 t! _1 O8 v3 `4 \, ]
    2   2020-01-01 22:00:005 o$ l! @3 }8 y! _/ C- i: V" }
    dtype: datetime64[ns]6 I" e/ k9 o! m0 Q. c3 i

    + v9 v. m5 h6 }" }' j7 Y' D1( n7 H' K0 a' @; P+ ~5 I* S
    2
    2 ~9 z' c0 ]2 X9 m' {/ S9 t39 K$ v& f" X8 l, o
    47 Q4 l; Q6 k: Y2 O+ a/ |% g
    5) r/ z! T# G6 d2 @2 _5 Q
    6( }7 n7 R8 x$ p
    7' a2 S9 `' h- {6 ^) w$ i
    8
    4 G# d1 H( t/ f/ h3 w. `90 }# b: A: V: \
    105 `0 S9 d1 T% @9 S" G  }6 M
    11
    ! T8 E& F) G" N* h- K, w12& [/ A) n! |  r
    135 j' e1 n& v7 {* _) d" [  G
    142 e! d3 H6 f- G% ]. Y7 ^+ d, h
    15
    4 x4 Z) a8 y1 q! R- t. u' h169 T3 e" b: ]3 n3 \$ N
    17
    6 p( s' t4 |2 E* o* b2 W( N18
    % K$ u1 G7 E6 a: l194 a) D& N* R7 V: |' z2 T, ?
    20
    ; n, r( a# |- {( y& p21
    , w6 \( J* z7 i, [% `( r4 f- N22& k2 Y" H2 J- U7 V  E
    23
    ( ^5 [& j' ]+ F- r: j24% s" f6 Y- m- Y( L
    25
    8 R, \; G1 t# X: l3 A26
    / g% ^! K+ x( [4 Z9 ~, V27- r- F0 J# E# N6 m5 L4 g" a" \
    28# z! K& J  W: B
    299 I9 k/ |1 g& M9 w5 u- z$ t. l
    300 h( \3 i2 [5 A% o4 y0 P
    31  L+ M, u8 m3 B2 q- f
    32  z$ P5 X. j+ Z+ {  P# z7 l
    10.2.4 时间戳的切片与索引
    / U' f% p6 N" ^/ `' O  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
    2 A6 ?4 x3 t% e8 G5 j9 i: _' e5 k+ N& }0 U
    利用dt对象和布尔条件联合使用( l5 j, m, K/ A
    利用切片,后者常用于连续时间戳。% W8 M+ A& Y, k% {; n
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
    # {7 A$ e" H  N7 B) ~idx = pd.Series(s.index).dt5 W- K+ s( P( ^4 ?/ ^
    s.head(): Q" @; g- T1 k- ^

    3 @% a3 g& s& P7 W0 N9 `2020-01-01    0- r: U1 {, [. V( w
    2020-01-02    16 f2 Q5 t/ p; T7 {5 @. k& B: W
    2020-01-03    10 C: o8 S0 }1 V+ o, F* h2 O
    2020-01-04    06 O. B+ [$ R) E7 X, x* Q- u6 U# O/ u
    2020-01-05    0
      Y5 G# P4 A2 s. X5 G4 _% LFreq: D, dtype: int32( M. X2 q( @& Q& Q: G
    1
    / x4 I/ y+ u# d- B9 N& G+ x2
    - r7 v  j- {, o- Z; t3
    ( O. d0 L/ u. m4  v  `  `' n3 @8 a. o1 E, c9 @% H  G
    5
    ' G. z* K' R: h4 g; P8 Y6
    * {' S" N4 G. G) N: e7% u' r- p' K  o1 Y: X7 N6 ]3 h9 E
    8, h/ h3 m, I+ u1 Z1 y& G4 v0 J# d
    9: Z6 o( e; `: f2 [: F+ l+ n/ j
    10
    ( J1 y* P# M- B5 w5 M) _Example1:每月的第一天或者最后一天7 a9 t8 w7 z3 L5 {8 u8 H
    ' M: \4 h6 L3 [' w0 O
    s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values, ]; S8 o6 F( v5 {
    Out[50]:
    3 q1 f& V) j* X9 G2020-01-01    13 z, {& y) }* Y4 A) N1 H% `
    2020-01-31    0
    ' g: P1 J  H) q6 h% Y2 R2020-02-01    1
    9 j6 r# M$ S1 Q0 ?/ h" D2020-02-29    1: I* ~/ D4 r4 q9 F5 D  }) x
    2020-03-01    0
    ; F; @2 Y) d" J& kdtype: int32
    . w# n# p, O* K& g; {5 j1
    8 W9 d' p3 e# |! O( b2$ [2 q3 v8 x1 n8 e4 U. D
    3
    ! j1 m9 H; r) r3 {/ K2 {9 p4
    9 y! @) r% o) [  L1 J5
      c' G- }3 v3 S  I) c& Z62 b% _7 t# i4 g* N& {/ o. X
    7; Q! J7 I& \1 c$ m
    8
    0 H+ z: l& y: s; W4 r" A' H: ]# eExample2:双休日' H# a( Z, @9 ?6 ?3 l% Z; l& y; y

    7 P- U! b' `/ A* c: K1 w$ l, ]s[idx.dayofweek.isin([5,6]).values].head()
    4 _6 a  D4 e6 |Out[51]: % c# }. {& c3 L' `, D
    2020-01-04    1
    8 o2 }9 @% b4 g) k2020-01-05    0
    " Y( P! @( p- C9 h5 S7 _- Z2020-01-11    0
    ; D/ D3 v' ^; _! W5 d5 {) ]2020-01-12    1- F" {+ E0 F( ]" }7 A
    2020-01-18    1- X1 v6 h1 Y9 s. W2 H
    dtype: int32
    % n: m" m+ n' O( j( t) y  B1
    $ F. `2 i3 i: p% z2
    / M8 s2 T) T3 i  m0 e3
    3 e% P- Q1 d: T; E, U% e4
    : ^1 D  Y. c$ D. n  u1 @, T5- S& d& v5 S- M' p4 |
    6
    1 Y% a1 W3 H" l" o3 I  i  Q7" A& w9 F# B: f* B; b
    8' ~' t( S0 j( D
    Example3:取出单日值+ s7 g: ?9 i# U( r9 a- h' s9 J9 c  ^

    + k3 m% y& v4 v, G& v: l8 B& Ns['2020-01-01']
    ) |2 s( Q, S9 C1 C$ t8 k) MOut[52]: 19 e& x( ^& w0 [' b7 c# I- @" b4 u8 s
    , p! Y0 L' F( d" ^
    s['20200101'] # 自动转换标准格式
    9 p) X( @( `. y: K  }Out[53]: 1
    5 K9 X7 x; T' a1
    6 q; k7 X- c$ q6 g  v6 v. a; b5 v2" I; {% j  Q. ]2 ]4 B1 W
    30 X% V# Q$ U7 j. L! r) ?- W/ ^
    4% Y  d0 E* X1 {' E& s
    5
    8 D3 ]; Y* C$ p5 [2 B$ g) V7 h" HExample4:取出七月3 _) Q) S6 i* }' R  A

    ! P" o( A5 @# r1 Y6 P* es['2020-07'].head()
    7 L6 ?/ Y, {  X1 Q8 f. C8 XOut[54]:
    % J6 S) y2 Z8 o1 y3 W6 v2020-07-01    0
    ) z( k3 x! {7 E% u. G" m2020-07-02    1  x. a3 p. y' T% ~2 f
    2020-07-03    06 z6 m8 p; S! z" [& Q( t( S
    2020-07-04    0" D& c( H. f8 o( Z8 N. N2 q, y( J
    2020-07-05    0# K' v, c  Y/ R* P! j
    Freq: D, dtype: int32
    # }. O' ]. i" b8 @) h+ U: K1* ?1 _) h) v9 q$ c& N
    25 ?. g" G. U" q( T& p2 i8 }5 _
    3
    - b6 L7 }7 Q2 f4 C% R46 X; v: N; S6 y) `! r7 G/ X* A
    5$ e$ g8 p8 B; n9 U1 K+ r
    6
    / s, U1 ]5 Q6 \. ^" }$ s  \7. Y- F  T& J7 n$ {: v% K: g& g0 v1 B
    8
    6 X! e) Q# ], ZExample5:取出5月初至7月15日: Q2 `+ t6 N( W5 t
    * Q( Q4 s% e# \7 @4 s! E5 T9 }
    s['2020-05':'2020-7-15'].head()
    , Z7 F1 _2 ~9 u. lOut[55]: # c  _6 N$ @) C( r' e
    2020-05-01    0$ \3 S$ y8 \8 D' j* D7 y2 e- r
    2020-05-02    1
    & O& R! ?5 @+ w- z( |% o+ O2020-05-03    0
    4 q! ?5 B! G8 u* c7 Q2020-05-04    1
    - r$ D2 r9 ~% |8 ]2020-05-05    1
    7 s5 B" o8 j5 n( }; CFreq: D, dtype: int32
    0 M; y# J0 Z2 W' b
    8 v+ U7 q/ F& L7 Os['2020-05':'2020-7-15'].tail()
    4 Q0 o, D( q$ _' M. {Out[56]: + R, M: B& Q" G6 r/ {- a. }
    2020-07-11    06 i( V/ Z0 R  ~8 y& n0 s, v8 J
    2020-07-12    0
    ' K/ j$ N4 y7 Z& o" p! j2020-07-13    1/ p5 v& _( T4 o3 f* x& L
    2020-07-14    07 T8 s1 h7 ^" |  }. }2 A8 _
    2020-07-15    1% Q5 e4 Y) ~! f7 L1 e
    Freq: D, dtype: int32' A  P# h% i4 C  V: R  k) O

    # \: c) x# i+ x. K* w5 l/ b1  g; ?& M% d; L/ ~$ C
    2( s; k3 o4 d2 Q
    3
    * v9 R+ ]: K6 |: C- g9 B  H( j1 a4; l( ~$ [0 d3 F# R4 j
    5; U; O6 V/ f! G5 [, Q9 ~* D
    64 K9 u# [1 x4 k/ q
    7$ b( ~; a5 I% G6 i! M
    8
    ( S' \# f' D$ F" z4 S9
    , [( |; u# S7 v- H10
    8 P- Q( m& ^: e. _/ O: u11
    . A/ s$ b6 }- \' p) X: ^" N2 p12
    5 b! W/ x* w( w! b13
    9 k4 W" r4 \) ?1 q14
    : [2 Q* [  W- v6 h15
    ( v7 d9 ?- n0 h# c$ G9 d: F16
    & v& A$ ?* L! z( _& W: u- B- Y17+ f- c8 f+ \4 t$ j( [& ^  ]2 c
    10.3 时间差: h' l9 ^! A: M8 j8 F/ `' k! f
    10.3.1 Timedelta的生成
    & w/ F3 b: u/ H- ~: R4 X5 r" spandas.Timedelta(value=<object object>, unit=None, **kwargs)
    ' r) m4 @$ K4 b, P7 ~  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。! [8 L8 O2 M6 y1 Q% |
      可能的值有:0 H8 ~+ Q2 L7 M- f! ^
    , L  p" p5 _2 t& O9 B, L
    ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
    $ @- w4 ?" z, j+ q- k5 m‘days’ or ‘day’
    ; R% B! {1 Q% v& |‘hours’, ‘hour’, ‘hr’, or ‘h’  K) v# ~7 i" e
    ‘minutes’, ‘minute’, ‘min’, or ‘m’& F# A! @' c+ \' @& O
    ‘seconds’, ‘second’, or ‘sec’/ Z" M* V7 j4 a  Y" X% J1 ^
    毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
      B. c) m1 x* ^- K0 P( y" H4 T微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’0 \: H5 i$ z  f% n
    纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.* `( F& L$ R- T0 f- M% h8 G0 T
    时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    ( T+ D; X/ O& \2 D& V' m6 Npd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
    + V  V5 q$ l+ p$ E2 a# {) S, KOut[57]: Timedelta('1 days 00:25:00')& f+ r. ]0 M6 T; q! U, Q. V- ]
    . H( O  E. |% T4 v7 q% y
    pd.Timedelta(days=1, minutes=25) # 需要注意加s  `& q) c- C. Q/ u7 N' l" Y
    Out[58]: Timedelta('1 days 00:25:00')
    + C- D0 |: }/ s3 y* V( b7 Y8 V- y. ]$ o3 U) G( w
    pd.Timedelta('1 days 25 minutes') # 字符串生成
    8 e0 X; }3 G; T6 d% X, a, k/ xOut[59]: Timedelta('1 days 00:25:00'). h9 T/ o$ p; @

    8 L. c2 S% h1 e: n/ d  z% x# }pd.Timedelta(1, "d")
    $ [6 r/ X2 w3 f% R2 AOut[58]: Timedelta('1 days 00:00:00')6 L& w% Q) V: y8 B% g$ e
    18 x( v4 H9 d' q& K+ A
    2; ?1 N/ a/ {7 @/ f. `, |- E
    3& \  R' z" B) R' ^. i
    4
    5 t  e7 V6 y5 \! C; I5
      c- M* h; G4 M  a5 a, W1 Q* {64 ~$ G2 j3 m$ x! i$ {  u  R8 Z
    79 M# W, @7 F0 \' P7 _0 X
    84 o; S4 C5 c/ R5 ^& }4 T
    9
    1 T. q" f' R; _# D101 K* l. [' P3 J6 H: ?
    11! ~& K- {& \1 h$ W4 k) a
    生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    2 U# S' g! A) k9 Q  Us = pd.to_timedelta(df.Time_Record)6 S, @7 {) {! M# R9 s

    2 f; z9 M4 e8 H3 {5 x. e9 Vs.head()
    : t( E7 j8 w0 L* ~  L. m. sOut[61]: , J. M2 S# x+ d0 r& r. P' y
    0   0 days 00:04:34
    ( J) c7 |1 m$ J4 B$ \1   0 days 00:04:20, C" M; {% g* ?- ]2 f
    2   0 days 00:05:22
    5 D! z- C( `& |! X7 R3   0 days 00:04:08# K1 b1 h7 c; u4 X( t
    4   0 days 00:05:22- l% o, I, A# O( O2 Q- O
    Name: Time_Record, dtype: timedelta64[ns]& C- ]1 W$ D- `* j2 t5 ]# _1 g
    1
    * L, D1 e5 K% p5 ]2
    7 m4 n- ^( T# N# }4 p2 b8 j3
      B2 u* |& w: ?7 {  T. [40 ^) [# }. B! S7 ?- w
    59 ~2 P8 @( j) f" ?1 `. I& @
    6
    0 L4 s* _: K. R+ }' T7
    * N8 X3 I+ y* N6 q/ t6 E1 Q84 P) v, _1 f  f" ?2 s! g
    9' C1 ]8 n# q5 I8 O  o" {7 ^% U' j- X
    103 @/ i  j2 T$ _  Z4 Q+ z2 j( H; a
    与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:4 z& C) i" }1 L: V/ V+ b
    pd.timedelta_range('0s', '1000s', freq='6min')  i1 k- `! m9 ~( T
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')8 V: g! o1 v6 t6 N& z2 y$ U: A, e
    5 C1 a9 I- g* V; p. p7 [
    pd.timedelta_range('0s', '1000s', periods=3)
    6 w5 j3 w8 N) ~' O) k0 GOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    2 u0 S+ h' i" c  M4 n$ Y7 X: {6 e1
    ' Y0 }6 ~8 [5 ^$ |$ G2$ o7 U8 C+ |; V) S* Q% E, s
    3
    : ~0 p9 j8 t1 r1 }3 e42 l" I: t. C# l- a8 h
    50 x1 }  V1 s- Z2 C
    对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    + i0 U& }* g- ks.dt.seconds.head()
    . O5 f/ _0 r% QOut[64]: 9 C# j- E6 d4 w
    0    274
    / c$ i: b0 y* p' `: X. @1    260
    ) |* P* k$ Q* P/ u; t2 B5 Y2    3221 L* K5 @! U& N$ [, s5 @
    3    248' |6 N9 {4 \( {% \3 D7 S8 m
    4    322/ F+ Q! ^. E8 z0 h; `2 n) Q1 v
    Name: Time_Record, dtype: int64
    # T2 Z5 X- C; G4 s7 V7 d1
    9 r* D2 `/ d- [+ {6 I29 ^# d3 \4 I! ^% ^  d2 p7 ~' D+ i2 \
    37 ]: @2 r: i- R# O
    4
    . O0 L' k+ O% \2 b; o51 i; c2 O7 r; l
    6
    2 [- k6 l- o/ I7 D1 C7
    3 h& [, B+ Y5 w6 ~' s" f80 o, _  U: O8 o6 e
    如果不想对天数取余而直接对应秒数,可以使用total_seconds7 r: I9 u& V2 e: Q( L

    $ K3 `3 B) N- f- F9 S' J$ rs.dt.total_seconds().head()' p. _' f- \$ C# ]  o
    Out[65]: 2 D% s3 d7 T6 ?. W! s4 U$ c( f8 T
    0    274.0
    ! |- x. U: w$ K3 i1    260.0
    # j" e; D/ }% T" L2    322.0, B1 x) O* H! p8 M
    3    248.0
    ' x2 X  Q' h2 y& d1 A4    322.0
    , V4 o0 N. @7 Q0 VName: Time_Record, dtype: float64
    % J. E. b: I9 c1  e* [8 v/ Q! q/ @$ Y5 H
    2
    . V9 E, O0 H$ o" b% M2 c* J37 Q; O& Y8 S) ]3 N3 `$ p9 [
    4; t+ C1 t3 X) v1 Q' G" d3 Q
    5
    % F/ u3 V$ k9 j- ]" m0 Q6
    # Q: y5 g( g  b& P7
    2 d0 B: j9 ?" x. x8 a84 H/ ?3 M9 ~0 ~, y8 M
    与时间戳序列类似,取整函数也是可以在dt对象上使用的:( o! e! _4 W& O3 S3 b  ~( A$ b

    ( L3 N0 _" A; Z8 Y, I3 }& w. Dpd.to_timedelta(df.Time_Record).dt.round('min').head()  H) O+ a* E( ~5 T
    Out[66]: 0 i: c1 I4 f1 \0 R2 c% q
    0   0 days 00:05:00. E0 S! Q- _+ {& J) B$ P
    1   0 days 00:04:00
    0 E; n. {8 `, H! e$ F$ w9 U. a2   0 days 00:05:00
    " v. x0 A% Y* b: T% b% d( O3   0 days 00:04:00
    0 v- N7 A& P$ Q5 u' Y2 Z/ A3 l4   0 days 00:05:00' m) ~$ l$ x' x) [( _
    Name: Time_Record, dtype: timedelta64[ns]
    # X3 T) Q% E5 A8 D8 @! w2 l4 w15 x: |: F9 N6 a) p& G3 d% b
    29 e* B$ r3 ?0 c
    3  P; n, W7 D: }% w: g0 t' M" M8 _
    4
    8 {' u3 F/ {. h4 B! s- w1 O$ b5
    $ ]$ _2 f3 I# ]6 f63 q5 G. `2 l7 K1 q- B2 r
    7
    - S. u  }" q6 P3 C8
    & X' z. ~$ u0 G4 `0 m10.2.2 Timedelta的运算3 I! x) v% p+ c! @  e7 @1 A3 {9 J1 S
    单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:( p# Q) y: n5 f4 A
    td1 = pd.Timedelta(days=1)
    , e1 D9 ~! p8 S3 rtd2 = pd.Timedelta(days=3)
    9 t+ i/ R+ A5 t+ kts = pd.Timestamp('20200101')
    1 t' V: G/ o5 {- p* E! e5 `: U
    - U. _6 r" g( a5 o" O4 ~td1 * 2
    7 e; e: y- ]1 c; I, S: O) V$ HOut[70]: Timedelta('2 days 00:00:00')
    ; d' a/ l+ Q. n) }4 W% m4 z' q- X0 |: ]4 c% K, k9 g6 K7 Q0 y5 [
    td2 - td1, q1 }& F0 W0 l( c1 D$ T( m. w
    Out[71]: Timedelta('2 days 00:00:00')& J, ]% ?- k6 w- @4 F* T  Y
    $ o' i/ y  E% m# X
    ts + td1
    . C$ @6 l  E6 e5 _9 r0 gOut[72]: Timestamp('2020-01-02 00:00:00')
    * v& M" Q# ^+ D; |  v! v) }7 i: q" {5 m0 S  F0 g* ?2 ]2 d, N
    ts - td1
    ! i& r4 V9 U5 z  x3 VOut[73]: Timestamp('2019-12-31 00:00:00')
    + c% o# W4 U: C& A5 p# Q1, q2 m1 ~9 d& {% R6 F
    2
    " C+ y0 m1 d; ~3" l0 t5 Q1 \; ^: j  P, _2 m
    4
    , t5 P" @( [5 K1 f5% U% {  y! ~8 l0 \+ ^* d5 S9 \! ]
    6
    3 B( e3 f" u1 E9 c! ?& Z( K7 m7
    0 X/ i' n' \9 b) w/ w8 o, K- _86 A2 _% K! M1 {$ R- G
    9) w3 N. l' j1 V3 l1 w0 i
    10
    . Z- `: r& l# p, ^: ]' E5 q; B- v114 x1 i4 |8 z' x; O
    12
    + E; i- j8 |' R$ h+ N& e137 b6 C. e3 L6 \( ^0 t
    149 ?3 p' j, Q& H" l, O8 d4 d0 D/ l
    158 B5 r( F- _  |4 w5 g+ b: p
    时间差的序列的运算,和上面方法相同:
    ' d: G0 E. d" I9 M" G6 u% l; _: q2 \td1 = pd.timedelta_range(start='1 days', periods=5)
    : d. Z# |: b' H4 K2 j2 a! O  dtd2 = pd.timedelta_range(start='12 hours',6 J1 e* K& Q, R6 S
                             freq='2H',
    6 b( z4 ?& K+ x8 v4 K( _9 ]                         periods=5)4 {7 r, s2 Y0 _# r- r, d/ J
    ts = pd.date_range('20200101', '20200105')! H: d% e3 Z# q- g. \5 d  R
    td1,td2,ts4 r5 O! i: C$ I" c" T  g9 X
    ) h8 h# |7 t, R" O0 o% ^
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')  H% e" M' B! H2 G1 F$ Y
    TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
    3 p4 K1 m2 q7 R2 i6 C/ c                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H'): Z7 y% ^, a  a0 `7 }8 K" r
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
    # D. q, ~; w5 ]9 U               '2020-01-05'],$ i9 |9 b& S/ V. C% y* I9 C5 K5 q
                  dtype='datetime64[ns]', freq='D')1 k" ]1 U2 e) h) p- X
    1! A# u2 n3 g' d' D; e% w
    2) H) {7 o: B: g1 Y5 J
    37 O$ J- \3 F9 g( X, r3 M2 J" s+ j) O% ?
    4
    & I& U. K( }- l57 {: _! J* S; z% W7 c$ ~& j
    6! W) t* O4 Z8 e! x6 t: a. L9 B8 h
    7
    - I$ R+ [1 P  ^! s8
    3 ?. z1 K. i3 R$ o7 `9/ ?1 Y; V4 R. j# N4 t4 g; X' w) A! M
    10" L. m$ b" q# e! z5 M
    11
    ! @  ~! r* x2 _5 w12' v1 E# Z5 r; @' w
    13
    - u8 Z$ ?( {- Etd1 * 5# I( G' w6 {1 [% Y& @. U
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D'), S! n% i0 I0 f/ U1 o6 V

    9 H. |/ w5 r! t- [& ?td1 * pd.Series(list(range(5))) # 逐个相乘
    6 W7 d5 }% x) \% [Out[78]:
    + ?/ e6 ~% Q' E* z0    0 days
    * H5 `4 F) k; Z- B6 Y. y0 ^' D6 @, b+ o1    2 days+ b2 c6 j: c# _4 ^4 a7 n; k
    2    6 days* [; J) I3 d6 n: I# L: q
    3   12 days
    ( _! E+ I$ w* a4 y  i9 w9 ~4   20 days
      M/ L$ y' y8 y+ l+ ^! D5 zdtype: timedelta64[ns]
    ) p$ c8 @* J% ]" R7 z: H0 Y; _$ s3 Z5 l4 P1 V" }- r
    td1 - td2
    ; {! ~5 v3 x* Y& {! mOut[79]: 4 v; _+ u9 g: Z: u+ D
    TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
    # m+ d! P6 q% U5 W7 m                '3 days 06:00:00', '4 days 04:00:00'],
    0 H" b& O' k! X! s5 l               dtype='timedelta64[ns]', freq=None)
    3 J; @. x7 c+ N" I, V" n% M+ M5 M
    3 w' ~; j6 k, G$ ~. S+ Q3 `8 Jtd1 + pd.Timestamp('20200101')
    ' o) k6 }; Q# B9 D& v$ P8 i  F7 [Out[80]: + N* J7 D! m( R1 I; W0 m9 L
    DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    6 |7 i7 k1 d- S8 X5 n               '2020-01-06'],dtype='datetime64[ns]', freq='D')
    6 l3 h$ _1 Z& X
    7 O/ V9 ]. q& V6 Itd1 + ts # 逐个相加2 S  r$ s: m: @5 t" m+ i5 u! I' H
    Out[81]: 9 o1 |. P. c0 ?) R3 t
    DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',8 c1 B: U  u) K
                   '2020-01-10'],
    2 F4 H, _0 E, A) G; o5 a              dtype='datetime64[ns]', freq=None)% N3 Q" e1 R0 @, f+ \
    / V  J5 u2 Z; S* N7 ]' J' {
    1
    . O* X; [9 d. }) l$ n2$ v! k5 i8 s# g, B3 E  n' g
    3: \5 y7 @/ I  P# k- _' N( M) C
    4
    : I) ^( y! z1 `! A3 w. l5
    2 b1 u3 D9 p6 h) m# o" g3 y6
    - G* `  T0 P# W' U& `7, A; {) t7 i8 v
    8, K$ ~, E, e& U) `" A1 z
    9
    : K7 o& E5 s( q6 C) i+ J+ t10
    / d) y0 B. ~, S0 y11# l5 |4 C2 L* o3 ^! L3 d) a
    12
    0 l" M6 e' {8 r1 u& z& V% m3 |13
    ( A. q: R6 u$ z' b3 i+ u14; H2 d  I9 B2 y( \( l
    153 C7 _, x+ ~: M+ e& v
    16
      Z; x9 [( X1 L8 I# X# E178 Y2 U4 t7 X0 q- V
    18% f3 E8 A* s1 M* z
    19
    8 g- s8 N6 ^6 x5 G6 s+ b. b; I20  Y1 f* F: q9 N
    214 Y: A! `' H, I! {# C) c
    22
    ( G+ S+ r' q5 d  h23
    2 _7 x! L9 S( f24" n) `* r' u) X+ x9 _! o
    25
    5 t: C3 _0 M2 G! E+ I& `5 t2 k26
    9 B" q) W  H( c' o/ K27
    0 m9 Z3 q) ?% i6 N: {$ r2 d28/ a, X; A% q% t
    10.4 日期偏置8 ^) M/ c5 R# B/ F+ [' }" g! I
    10.4.1 Offset对象
    3 `/ R" u: i9 b  日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
    . K/ @' W4 x& G; M0 G
    5 Q: ~5 e; }3 \' [' a0 QDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
    4 {' {5 Q9 t, `3 C: ^; B, ^8 r
    " Z( t! A: n5 G+ ss.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本+ G+ Z8 s8 a; m! D( i
    s.kwds:{‘week’: 0, ‘weekday’: 0}1 v# Z  D9 W7 W% v: ~
    s.wek/s.weekday:顾名思义
    3 v# C: `6 G% v7 z4 \( P有14个方法,包括:' ?9 g5 l7 X9 @

    & m' J9 k, _- O5 P- n7 m2 T. j5 R6 MDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    ) q) t* L) h, e8 ?0 m+ [# hpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。7 {  t7 m" p- B2 m4 z6 g: ?5 Z$ c3 W. Y

    ) F1 ~4 `8 K+ Q& r6 g- S$ E有两个参数:( X4 A) c+ H2 J5 p
    week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
    ! u8 D& s. U0 J+ K* T. Jweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
    * O# ]9 ~3 L1 w: |7 epandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    5 q: `; @9 }8 b& H/ j; ~( N9 d' m
    pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)7 u9 ~2 A* N8 W, {! f( ~
    Out[82]: Timestamp('2020-09-07 00:00:00')
    9 b; E* x0 f. |  c2 E0 G
      j) l# b. O' N: S6 Kpd.Timestamp('20200907') + pd.offsets.BDay(30)4 s  k; S# b! N' w1 a* @
    Out[83]: Timestamp('2020-10-19 00:00:00')( w& O. ^/ ]& d1 {) h( L
    11 l: m' @- ?1 V; h3 Z1 C% n0 m
    2
    8 w& d* q) R$ N- x' t. ~+ J3& G0 {2 R- F+ }" D
    4
    / W9 p; ?; R/ ?* t$ T/ t! g/ d5
    7 v3 y' B6 y& v  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
    7 N+ X' q( d0 e7 {/ w5 n; b! K0 y1 S* _: T1 o, h: Z- S; w) l
    pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
    2 y, W  q2 D9 ~Out[84]: Timestamp('2020-08-03 00:00:00')
    8 @" |2 Y. D3 y+ q" G/ }0 W
    . {- F* v! h2 q1 _pd.Timestamp('20200907') - pd.offsets.BDay(30)
    0 ~: ]: B" S8 W0 l2 C6 NOut[85]: Timestamp('2020-07-27 00:00:00')  B7 `0 D9 Y) f4 v% x2 J
    , |, X/ F3 w3 |% G: H
    pd.Timestamp('20200907') + pd.offsets.MonthEnd(); \2 Q. y: T( J0 K
    Out[86]: Timestamp('2020-09-30 00:00:00')# s1 C8 j: L8 {% I0 G
    1
    5 `* z  K# I/ H1 a7 y( Q* s  E. ~2
    1 x2 f$ I- }3 R5 a32 ^, A/ ~" @2 `4 N7 O
    42 T8 O, s7 O' \: L, x
    5
    8 p5 B3 ~8 i0 e: ^  i, e: F6
    6 x  J! P- |' m2 O% S* |; \9 h73 N# G0 ?  P: |1 @2 P. y* T3 o
    8' Q; R) y2 q5 h/ i
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。+ [9 [, I* N: H9 @$ w1 `: c
      其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
    , x8 o( g. O$ b7 P/ H
    . f3 \8 H9 r( h* mmy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    $ i4 a3 i) r# h1 q) p4 Bdr = pd.date_range('20200108', '20200111')
    3 T6 o8 Y3 ~5 h% m* H4 P  U
    ) j0 m+ |" V( Z# @, ~9 j; Mdr.to_series().dt.dayofweek
    & f6 R  P1 M7 D+ G" z  DOut[89]:
    0 D4 g/ w8 E/ E* Q; I" {7 `2020-01-08    2- |6 h3 u3 W; U9 w/ o; X5 z
    2020-01-09    3. y* e  p6 n6 E4 X
    2020-01-10    4
    ' x( P5 a& Q+ s6 {, q2020-01-11    5) U& d4 L! w, b# ~
    Freq: D, dtype: int641 x( x; [1 k; N7 T: I
    & j5 _) k' W% A2 l$ g
    [i + my_filter for i in dr]0 d& C/ B/ `* T5 r. R8 c
    Out[90]: 4 n7 D! l) t9 k3 P+ [8 v5 p) {7 I
    [Timestamp('2020-01-10 00:00:00'),
    8 t( M' i# [* q+ w4 `* } Timestamp('2020-01-10 00:00:00'),
    8 Q! _6 P- ^( h  [ Timestamp('2020-01-15 00:00:00'),5 Q9 ^4 D3 s/ E# X7 Y* _
    Timestamp('2020-01-15 00:00:00')]' r$ ]3 d1 G* v- N+ g$ u, _

    ) c* Y, R: b( M) N: r1
      Y5 V3 Z$ i& j* f  P  Z' x1 O2) Y) Q; C: p2 N' Q7 }6 A
    3
    - C* U6 X$ {5 _: B' D$ F5 [7 }! C4$ m" O6 M; G; F1 I- i
    5. ~$ A. t5 j8 h/ a
    62 g7 S0 f0 d) p& @
    7
    / J* J; T. q0 m6 Y) R7 H4 B+ n8
    5 B7 C: Z* T" ~9
    ( ]% i7 b+ ?: p" W: I10' u$ D7 b( O. q& k  O
    11+ Z6 u5 Y: m+ W1 k
    12
    , E: @# X5 n) V4 X13
      J5 }) _) ]% g' l$ x14' ?& ]/ q. e8 [; h! f5 u1 N4 h
    15
    ; W3 `7 C, }6 X: O$ P: W168 e& S+ [2 S* ^* H$ ^1 `
    17/ i4 t$ y# e1 E, t1 z, h
      上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。( v3 K  A5 N- B) w( N4 p

    # }" `+ h  a# f: t& @【CAUTION】不要使用部分Offset: R+ i- @* U6 T5 z8 g
    在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。" G2 O* h6 t0 `6 W2 H
    " P- Z, e6 A) S. P2 y) W5 q6 |% r
    10.4.2 偏置字符串
    $ W- W. p  |2 \. e; A( ]  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。  O- a' E" P) G8 D0 g* W9 O6 j
    # Y8 X8 `: [. Q
      Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。$ T4 V3 ?* v4 Q9 [  s! X8 [3 [
    ' Y) X/ c7 a8 N* V
    pd.date_range('20200101','20200331', freq='MS') # 月初
    2 }' G1 V6 ]" y7 Y  H4 K3 @Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')* ]" L3 |* a; p
    ; g" ^  |. w0 {* ^8 G
    pd.date_range('20200101','20200331', freq='M') # 月末% k% ]" z2 b5 M& ?/ F5 i$ n
    Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    / |9 Y: r' @# l! M+ P) q; z& a0 ^, h, J4 y$ n# c" z8 a
    pd.date_range('20200101','20200110', freq='B') # 工作日
    , P# m/ A0 J! F; `7 _$ M2 X: DOut[93]:
    2 \6 J( g1 O. h9 pDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',' E4 Z3 W0 B, Z& s; |
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    $ F% H/ F7 ~0 i( V              dtype='datetime64[ns]', freq='B')- v4 c/ }9 h" D" Z' ^. L1 |

    ( b1 g# b1 j4 hpd.date_range('20200101','20200201', freq='W-MON') # 周一
    ; J! g) ?- f& ^3 c& WOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    ; c1 a3 I1 b% s1 T$ l- Z
    ' ?/ {* o! F5 T" X; rpd.date_range('20200101','20200201',
    ( ^0 N+ \& l  ^; J+ Q1 I              freq='WOM-1MON') # 每月第一个周一
    - I: w: d1 a1 y. a. ?
    ' h# Z) W5 c; o  M- j+ r* NOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')7 ~/ P. ^  e7 G  h+ h( C1 ^) R

    " b3 @$ @0 u" k! M- v1/ k3 ~& W7 ]& r! q( J
    2
    5 y2 ]) g! ^. P% ~/ K) o, V' r- j% Z3
    # m$ b( E- q( k7 v" O40 J& b$ A2 ^( F: m
    5
    2 ~7 r& M( n$ T8 @: l; F68 o/ k; {# v: e  q  J- }
    7
    3 E$ q, g' |) D7 B1 ?: \3 t3 a8 Q8' n" a* f( q- S5 d; d- }6 X0 U
    9+ k; Q* y7 e* D2 _8 u) P! Z( {- B
    105 w$ n' \. S& R  b" v7 {' W
    11/ W" F. o( E4 q0 m5 l
    12
    8 V3 n: E& N2 Z5 Q/ [13
    0 E: U( e# F. S14
    9 B! L0 ?2 z3 Z( M7 E/ n: q158 s1 r; z8 y+ U" M" D3 t
    16
    / x$ Q+ e6 g. p( L/ R$ D  P17; N0 `: d# H# y2 g
    182 t! S, N) @) [8 w! q
    19' u) k: Z0 }+ v: d
    上面的这些字符串,等价于使用如下的 Offset 对象:/ T; h3 c5 j' N* m

    ( ^6 e4 i' w( j) c4 K0 ypd.date_range('20200101','20200331',
    3 \) k. x% j  T+ s" b% O              freq=pd.offsets.MonthBegin())0 b0 ~) V2 @; U% p4 k

    & o. c7 c7 G$ c* w. qOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    8 {8 K. }. H; n! G& e- ]0 w
    $ a+ ~& x1 @! N1 }8 Zpd.date_range('20200101','20200331',: S3 B: F2 ?4 ?0 A( F. l; n, o
                  freq=pd.offsets.MonthEnd())- H6 B5 `. v  Y( G/ ?% `

    ( D: v" Q) V) s) G7 q8 B1 ~& Y% iOut[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M'); o6 Y/ x1 t% ?# s7 y" \0 u. o0 Z

    ! b# |, C# ^1 M( _pd.date_range('20200101','20200110', freq=pd.offsets.BDay()), z6 `; x# p8 N7 J9 _6 {0 V+ e% Q7 y
    Out[98]:
    . T! F, y# t( J$ D( B- O3 O+ RDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    , V0 y4 p1 H1 \' H; A               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    3 Z6 M. O( z- n1 \: v              dtype='datetime64[ns]', freq='B')
    # w" s, z, v5 I& \
    3 Q) A0 l1 j, r- l2 Xpd.date_range('20200101','20200201',8 i3 u, h1 k0 m, L% Y. j* v0 e
                  freq=pd.offsets.CDay(weekmask='Mon'))) b4 R5 M/ a5 |' f: b3 {

    6 T3 Z, A3 E( [1 t0 N7 POut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    4 n6 p9 b, x5 V' x8 p2 G6 g( c- Z( m- q$ x* ^
    pd.date_range('20200101','20200201',
    + g" G$ j8 g& F% f) E/ Z              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
    9 U/ r1 L0 ?) Z. j+ ^( v) z9 C* ?7 M" v+ y- K% P$ G7 {
    Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')& |; L2 g- K6 G5 D3 ?

    4 Z6 c; c; s+ @# T! ]& Y* K1& {. A: q( Y0 K* v/ q- K
    2+ d6 _  L4 Z; s% J' e: \
    3( k- a/ H8 ?* M, H" |- \% x
    4+ T- R4 k2 P. e6 N. j3 {
    5
      P) G( T( i7 R6 \( s. n6
    ( L3 X5 _/ S' V/ M2 U4 A7
    * a) C8 {' E* U# ~: F7 ?& ^87 ]( t/ @! \7 b. e. ^
    93 G( i% [+ `+ S% w' E$ @9 R' E
    10
    1 T. _* `' |1 ?1 f% t! \+ R11
    9 e; Q7 s6 S; M% {8 R3 t12
    + b" G/ D7 F! c, o( a( [& \/ B13
    ) O# U9 W  y* U: I5 L14
    , v7 V$ N/ ]8 E, a2 ?5 j; j3 Q15
    $ I) Z/ R  p6 |& E3 W0 f, Q3 b: P16
    9 p. o8 W! c. T( p5 W: Y1 h1 _17
    - t3 p' k/ [& U& b& z18
    ' J% ]" Z3 `, W+ o) f, ]" I197 g4 m: |. d$ l4 Q+ ^$ w
    20" r' K% S3 }3 i9 \  f
    21" d5 W9 R( a' G+ V$ {4 F4 x2 A
    22
    7 X- f7 A& u% M7 V7 P: F! g23( [  |) n2 z/ `" n( h
    24
    7 {; W; w# ]. C. p255 Y# h6 E, `( h
    【CAUTION】关于时区问题的说明
    ; I  m0 q8 P  o1 |4 F2 ^  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
    8 X' d) c0 i$ f1 _7 z  {! Q
    0 A" X# G: L* F10.5、时序中的滑窗与分组
    2 ^- `+ {8 T. H# C8 F. H7 y, s1 V10.5.1 滑动窗口* A$ }0 H$ R% ?2 {3 i0 P
      所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:5 l3 z; e7 t* K6 h! {& }

    $ u" l4 g6 f9 Uimport matplotlib.pyplot as plt* \9 m6 v6 l6 K1 G9 V% z( M% v
    idx = pd.date_range('20200101', '20201231', freq='B')4 ^" X1 \! a) s6 z/ D
    np.random.seed(2020)
    8 f4 W' Y( o3 g* A' @. N' s# _9 k+ ^- l: e0 _
    data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加" R0 q" `' l& D8 M% t; F  }
    s = pd.Series(data,index=idx)# f! p! T0 F! |: ^$ m
    s.head()
    5 S) t" t4 Y$ L$ dOut[106]: , i+ Z) D  Y: n# L. S
    2020-01-01   -1+ }+ @. u. s8 o
    2020-01-02   -28 \1 P3 U) C. |+ j: d
    2020-01-03   -1
    3 O2 d2 i5 U) e& I$ c2020-01-06   -1
    . n* F: b( }. w- N. K1 y) u; S/ ], G2020-01-07   -22 f  f( @$ C0 L
    Freq: B, dtype: int320 z/ n& l# c: {
    r = s.rolling('30D')# rolling可以指定freq或者offset对象; o1 R+ T7 t6 l- ~
    3 m6 j6 C$ z# U& v8 N
    plt.plot(s) # 蓝色线
    ; J( r6 K" b' Q. ~' G7 y6 t/ HOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]+ H$ T0 O, J' o0 G2 {' F
    plt.title('BOLL LINES')
    5 T) @1 r$ X1 R. uOut[109]: Text(0.5, 1.0, 'BOLL LINES')' Q; V8 P7 q$ B% W/ D
    $ g4 }" j" W  C, ~% h1 L$ M
    plt.plot(r.mean()) #橙色线
    1 H% J9 Y. `2 q) QOut[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
    5 ?8 P' a  |* @; Y: x3 g
    7 b9 S: h8 p% ]* ]- U- f1 eplt.plot(r.mean()+r.std()*2) # 绿色线- J; ~9 v' v" a, f" m( O
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]5 @; O% ]% e( f! l5 \. \+ ?
    - E# S5 }- b# f+ I9 f  ?: F* t
    plt.plot(r.mean()-r.std()*2) # 红色线3 G4 H  o. G1 T; w
    Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]8 [* Y8 C' |  W( y: J

    + }: T' _+ Y8 I( h) x& F1
      K8 h9 E  h( U, ^; p0 p0 J2 x2* _5 c9 J) b% s" o) g
    3
    $ i3 B# c. z$ M4 h  V, r46 u7 k3 b* C0 l% M9 W% b* h
    5: S, Q9 z3 h: b' A0 a9 @. P
    6' z# x7 |  [+ Z8 a3 S" \" Y! f
    7
    2 }9 f, B6 L9 }( y8, C/ y6 a; {' {7 V- J) \
    9
    3 q0 A2 D: `) J& l- u5 f10  g/ ]) L+ H' L! N' `0 U  U# P0 W; I
    11
    9 O$ L2 M: G, [; ^' m0 ^121 J5 J* g5 q3 b$ G: f3 I$ c6 f, @9 M
    139 X* [- q9 x+ `  p
    14
    - O) O* O3 Y+ m. }/ l15
    4 @$ P0 g) l8 S- m16% w- e" u$ _4 p
    17
    0 B& L! o' x( r' S& z6 X18& a- p" u" L; C! b! O/ w% D, ?- R$ U
    19& M$ m) X  {. l5 `
    20# r$ I& b4 H, p1 P
    214 [3 H; v: Q6 j
    22; T& T9 A8 w1 M8 C, _1 ?- W+ \! e/ G
    23
    ) O/ k( H; ?, w247 U' u* y+ z6 h! `( n
    25) I% {5 ]3 V- [+ t3 {" g
    26
    ' ^2 R: A. ~) C, j; v: U+ K! t; U/ {27
    ( b2 ?$ _+ E. Z, Z& o28/ H+ g0 u3 Y' `+ m9 v! G! }
    29
    ; ]" m# G  e. }. j# \9 r
    : H% i1 z0 |' _8 J; g2 L   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
    3 n8 k) ]. h+ |5 U' {2 B   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。4 k3 `* w2 x1 O- V/ y- E7 p
    : _; L: w1 z( [9 a2 M0 U. n
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]' ^! J# d- z" v1 u( e. N& y
    bday_sum=select_bday.rolling(7,min_periods=1).sum()6 ]! G) t, Y: d+ d# t9 Y% f$ e
    result=bday_sum.reindex().ffill()
    8 c9 O, o5 n( g+ k, R& {* hresult
    9 _! r0 Z7 z$ J- u% z, \
    ) V- X/ [5 M+ X& z2 J2020-01-01     -1.0' z( ^+ ~( ?) m+ g1 C4 j
    2020-01-02     -3.0! J8 G. \3 p  _) k
    2020-01-03     -4.0
      n4 H  u  Q6 w4 }% ]3 A2020-01-06     -5.0
    1 ^( o7 B0 F$ g9 v, t  M2020-01-07     -7.0! ]  I1 X# `, j( s. C0 N
                  ...  - ^, q3 _# S2 o7 Y, `* U
    2020-12-25    136.08 M5 s# J9 n1 O
    2020-12-28    133.0
    . Q& r* T9 l5 b( J0 O6 d& r! I; q2020-12-29    131.0
    6 ]! X' z& v% m! g, a7 t- K' I9 R2020-12-30    130.0/ u5 j5 @8 V8 E% N
    2020-12-31    128.0  s. ?+ H2 n$ P3 J0 }
    Freq: B, Length: 262, dtype: float64; U% O4 _  J: r6 Q. l: X
    ' |9 L7 f. W$ Z1 a" h
    1
    : E! T+ p& I, e2 s1 P2* ?' d' s( _& W; e5 j7 i* C& h
    3. y1 C& O' P. N5 i1 H* E& _
    4
    / B9 v% T9 n& p' q+ E* d5
    2 D0 g8 {0 u( g! I6
    1 J% `, ?" [/ w3 s+ _, o( r6 z7
    ) i0 q. q( E9 S7 c. O9 ]- l) V82 k+ d- y- G! s+ U/ W/ o7 i
    9( k4 W9 |2 }4 _* g) T* L
    10
    7 V" ]- ^" A% z3 L, M9 Q11
    $ E  y4 O$ T" |2 V( `" I12# `# Y9 A1 [7 Q
    13
    / W3 p  Y, N" G; _5 A' v" h14
    7 b8 r3 V8 [8 Z! b152 G! I0 h8 B, r/ V/ F& N! R
    162 M" u* J% S1 k0 D1 h: A
    17
    : h7 s8 m! w, v5 y8 ^  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
    9 B3 l2 B8 Z. d7 n* J' Y% j5 F, r- g# ?# J( T% y
      对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    : |; k& o$ e! v6 D8 }2 F* O9 H8 w1 z5 B4 f8 Z8 `! Y% p/ k5 g
    s.shift(freq='50D').head()5 J$ \0 p2 q1 T% [+ e8 }1 U( ^
    Out[113]: + ?& k8 w+ p3 K- T9 j
    2020-02-20   -1
    7 G9 ]3 H7 x5 `; u2020-02-21   -2! T/ a' K6 L5 h# O: j- B" O3 t- G
    2020-02-22   -1
    / U" W& j8 ]8 O* j' r9 d4 d8 @( b7 s. B2020-02-25   -1
    - ?0 S4 @, S- y2020-02-26   -22 E! Z4 U/ z- ~' h0 s9 x
    dtype: int32, e& T1 \7 j7 I9 @$ M" a
    11 K( i2 ^( c) N3 G% ?
    24 Z8 w' V4 M5 D# ~
    3
    / z8 v' e2 c( n6 y2 R) O4
    3 o5 {' T& Q; ?- u, {5
    ) H: @' z, D) T6 h1 ^( L: L3 G6
    . _4 ^* L( d, c8 R+ v! ^7
    9 Z  o) N: O' {7 e, c9 {9 b8" B' Z+ O5 J& O5 t& I- ]0 i+ n) F
      另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    / Z# w" b, U# z2 m- [1 r2 ]9 T4 B* n5 I
    my_series = pd.Series(s.index)
    ' d* R$ Z4 Q/ o2 imy_series.head()3 q- d  e  |+ P( M$ L( j
    Out[115]:   x# i2 a7 `4 l& O6 b1 K: u
    0   2020-01-01
    7 b( h% h! N: P0 V. Y- B* E1   2020-01-02
    - F. U8 H: A: J+ ~2   2020-01-03
    7 S2 }. s/ d2 Q& w! _3   2020-01-06
    ' k6 I6 H. ?. l0 G4   2020-01-07
    1 {; [* ~8 r$ b( a9 [- edtype: datetime64[ns]
    0 p1 n' I4 L- S8 M1 C, g5 E) K3 i6 {2 y
    my_series.diff(1).head()4 `: i  E  f; I, G& n: j
    Out[116]: * E! C+ g, h2 V
    0      NaT
    8 H6 P) e6 _* U  @1   1 days
    ; k5 L" c1 J4 I" x4 e. u9 ?- P: ^( c2   1 days
    ! k2 A  v- E( k) [/ t8 l3   3 days6 S& q9 H5 s- y% C- {( h
    4   1 days
    ; e, P1 S" w" P8 Adtype: timedelta64[ns]$ t6 I& ?& W" z$ s) `1 N4 `; c. b1 z

    3 a. i$ A4 f. {: w+ m, p: K$ B1
    6 W' t7 u4 B! H- s1 O! S* _4 Q2
    * M6 g4 J0 `, g$ z  K' J, ~( P3
    0 q% u4 q' T+ K) \, c) X) `44 U) T0 k/ R# K, d# U
    5
    8 Y9 F- p" q2 R6 L: _5 d- F# N! W1 @) B6" T! [0 z  Y% Y# S) L  W7 E! L' a
    7- L1 U4 t' y: Z* B; M  r2 I
    8* X' [  F1 o5 ?4 [
    9# l- y9 W; y7 u8 ~7 O1 p
    10) W/ X3 Q+ h; s7 H
    11
    ! m8 U9 G( B  o2 L0 C* Y+ B/ [12
    / L$ Q& C0 r7 D5 u0 e8 O137 x, F% ~3 }+ m, H% i
    148 P% H5 d1 s" J6 c- L$ ]
    15
    1 X% b& v5 |7 q) X/ c9 m16$ d$ e; a& ?" p8 t- E. d! m3 U; a* A! P
    17
    : \  K2 x+ J1 H6 S. p; ~; o18- f$ A6 s- }4 A4 j. ^% _3 E
    10.5.2 重采样" o1 O! c% `: [4 l* p9 o
      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)
    0 h, c- F" ^, L0 p; r常用参数有:
    ' f# n: F3 _* S9 y/ z' y! W& c
    0 X% q# [/ U6 f0 U! Drule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    - o( i' n1 E3 I5 u% Uaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样) B, V7 a1 I8 X5 \' G
    closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    - ^3 g& p0 R8 ~# i  ]  slabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。+ X! [- P! S! i* E) |# g$ R0 G
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。/ ~1 s! |+ V  x' \7 d( o4 G& _
    on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
    ; U' M# r. h8 _4 K2 @$ l$ Slevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
    ; V9 t9 B: ^) k2 x9 z9 Korigin参数有5种取值:
    # f# \5 B" x: y6 m  F! I& T‘epoch’:从 1970-01-01开始算起3 I/ Y+ ?$ [' t
    ‘start’:原点是时间序列的第一个值
    " `* G" y+ F1 R. y* w: f‘start_day’:默认值,表示原点是时间序列第一天的午夜。
    6 O0 u: ^2 q# a6 ?" Q# b! [/ a'end':原点是时间序列的最后一个值(1.3.0版本才有)3 w# y; H! `7 A6 w  |4 g$ N9 F
    ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)/ I. L9 }9 C1 c$ y9 W5 l
    offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。: `  \. \- C" U
      closed和计算有关,label和显示有关,closed才有开闭。
    # n% A  w# x; ^3 c6 v- S  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
    7 q, A# w1 Y& f0 @9 X. x3 c/ w$ \
    * C9 N: z) v9 b4 J重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:- B: i! X9 S$ L. g% i
    s.resample('10D').mean().head()
    ; A3 B; p/ |8 f  ~Out[117]:
    % n& y8 I( u, X  u  w8 T  o' ?2020-01-01   -2.000000
    ) J9 C* e* Y4 C+ v2 p5 |$ H4 \2020-01-11   -3.166667) ^1 n1 c+ `5 X4 g
    2020-01-21   -3.625000
    ! K  v1 W& r% J- V6 c2020-01-31   -4.000000% |, M) Q$ S/ n4 c- N
    2020-02-10   -0.3750000 g- O, G* z9 f5 F( S+ p
    Freq: 10D, dtype: float642 X& ]- V/ R% f. r" p9 v
    1
      u# h0 n7 @9 w. @2# {8 T9 d* R9 g* Y! }: Z2 U
    33 v. I2 c' M( D& o$ ~; k8 Y
    44 P* x* k; u8 }4 W% v
    5
    3 E, F$ h+ F$ E- p( ^7 W6; |5 p( b6 T$ j! R
    7
    $ R4 S: W' ]- u6 [8 ^& o  }8. Y- p: q- |/ O1 O: i7 r0 B1 F
    可以通过apply方法自定义处理函数:
    $ K% j% w) F3 p2 Ks.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
    , m: E- S) p/ U. C
    & M1 f" V/ _; m- ^& [Out[118]:
    - M3 P, s2 h% w2020-01-01    3
    ) @  [& c" r, ^% x; J3 J- M2020-01-11    41 f0 |& b  ~& O. q5 G
    2020-01-21    4
    ( }( f1 G' [6 h2020-01-31    2
    3 s, T. R8 T+ E# m* b& K; a4 \2020-02-10    4+ H  u7 c0 N# H# A% ?0 Q5 I* g
    Freq: 10D, dtype: int32
    7 V# Y1 `( }4 _, e8 G( P1
    5 D( j+ ~7 P) v0 T2
    % Q3 r* c% g$ ?/ D1 c2 G2 J, Q3
    - [) z' _% a( [, M4
    & L# a- d% U$ X5
    4 {) V: I# ^; v8 y- C$ h2 i6
    3 x# W- r# N; k, [) O  d7 \5 c9 X; K. o9 O7. }3 L4 }$ [6 R! s+ c! Z4 w
    8
    : y( J" F% w' o. |9% [; z+ p# |- b5 Y
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
    ( |$ j( x4 q+ v  Q4 b
    # P/ X: e8 H  s7 Oidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')1 j9 O& f: G5 N0 R
    data = np.random.randint(-1,2,len(idx)).cumsum()- x4 _6 \6 x. U1 w! q0 e
    s = pd.Series(data,index=idx)
    5 Q2 _2 s& r/ ks.head()/ B# i# {# f( |- C& Z( i
    + m; l! t( Y" d; Q, {& F" ]
    Out[122]: , J% `1 p/ g$ \
    2020-01-01 08:26:35   -16 C2 f" t' @7 D7 T0 J: o! Z
    2020-01-01 08:27:52   -1
    ; X! Q9 z! C/ H! c( I* K2020-01-01 08:29:09   -2* G  r+ T: c% c+ z: C3 J( c  o2 @
    2020-01-01 08:30:26   -3. k+ B. h& t5 T! j/ G3 i4 n
    2020-01-01 08:31:43   -4
    , s$ x5 l8 t* b7 t' ZFreq: 77S, dtype: int32+ q+ Y5 ~% _, X7 y4 z2 a
    1
    7 p2 g  P6 e' l* X5 k6 ]2
    ; f* V0 S8 n! P% v3/ E7 I4 O5 I/ Y: v0 I
    4" N5 p0 Q  ^: ?( z8 r
    5( o, z5 R' u  x# T$ j
    6; s+ L- J% H  _0 s" D0 ?' y
    7- N: V1 I/ q9 M
    8
    % F, P7 {1 I1 E1 W8 ^9/ L2 W1 G( r3 D! R* m/ e# U) D
    10
    7 i2 E" a# ^3 }8 V3 B# S11
    ! a$ O: n  B3 J" t12
    1 }: j. O7 i) ^2 A3 `! M7 g6 B" Q% o  下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
    3 l9 X) S0 @( m, ]2 c) x5 `% \5 V6 z7 U. L8 H% o
    s.resample('7min').mean().head()
    1 ^' t% @) q1 P* ?9 q! c# a9 K  ROut[123]:
    & H/ R9 P9 f4 d) H, U. m2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值
    0 ?+ q; h7 F: n2020-01-01 08:31:00   -2.6000002 {% m( [7 ~0 B+ J
    2020-01-01 08:38:00   -2.166667
    ) p3 ^8 F" [' I% Y4 C$ h5 B/ }5 _! Y2020-01-01 08:45:00    0.2000000 d. p$ Z$ t2 O( {: T
    2020-01-01 08:52:00    2.833333
    % k# _5 C, h, {: x6 CFreq: 7T, dtype: float64& l# C2 z5 q: [7 v4 A( M4 n2 I/ a
    1; h# `9 P) P/ c3 l! @
    2) K# O: r) K" u# ^
    3
    & q& I2 l7 d2 L3 O' b4) w- Z; q' c7 Z0 i! g1 I
    5
    # H) u' G8 v2 _; y$ d/ L9 p6
    # K: I& g2 I1 K5 a" v70 S: S6 o1 K0 a/ a; G9 }: I
    8
    & J+ O, _5 G6 z$ \* _8 _  有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
    0 X& d; [9 y6 {& n2 V/ w, A
    7 l4 P1 A) \/ a+ l8 fs.resample('7min', origin='start').mean().head()# @& z8 |: q5 |, H! }9 r9 a
    Out[124]: ' {5 P* I* x# ?) U- n
    2020-01-01 08:26:35   -2.333333
    ! O- Q0 d1 W! a$ ~2020-01-01 08:33:35   -2.400000& k2 |* [' V9 h3 h1 `
    2020-01-01 08:40:35   -1.333333
    ; B9 ?  _5 H! v, N, {+ c2020-01-01 08:47:35    1.200000. {& @" n& o# s" ^# x
    2020-01-01 08:54:35    3.166667
    * \- Y- @$ |/ [$ `  ?Freq: 7T, dtype: float64' `7 X/ m% |. A2 Q2 R
    1
    8 X) S2 l+ F7 }* l. y2; u# |, i% {, H0 ?1 p
    3
    9 p7 ^  x$ ~5 h& ~( e- t: ]) \4" `5 \5 k. N* o% C4 Z
    59 ^, F. L) n9 U2 u9 c3 M. O1 a9 S0 t
    6
      E7 s! x; y1 Y# I8 l6 ?3 E! a; G  g5 H75 P5 x1 ~- R1 X, k
    88 z  d- m7 B7 P1 r2 s9 a. w0 @8 m
      在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。. g6 v; c3 C( D4 @: R& W6 Y
    . U& L$ i3 P1 ~0 w
    s = pd.Series(np.random.randint(2,size=366),0 R$ [7 O4 P1 X! h" v
                  index=pd.date_range('2020-01-01',
    4 I- P. p" t; Z2 A4 F% n1 {                                  '2020-12-31'))& O; A& u1 W. B- N2 M0 G- \

    & `$ p% q9 [( I: g
    . P' e6 ~4 z& L5 b% h5 is.resample('M').mean().head(); P( R7 ^% W& @0 f7 h* ~% Y
    Out[126]:
    4 o& W+ B; A; e' p$ ?2020-01-31    0.451613
    0 F! r9 y( ]8 K4 z& h  g% s  U2020-02-29    0.448276
    + ]4 _) k9 x( x5 W! q0 V2020-03-31    0.516129/ D& [7 {- X5 z  V3 W
    2020-04-30    0.566667# y/ H2 s4 \* O+ l. r; K
    2020-05-31    0.451613
    + Z/ R; d2 @' A( J9 O" k: GFreq: M, dtype: float64
    3 C$ V# V* Z" K& c+ Q1 r2 y2 }  M) g" @- M) o( W7 E# h) ]+ M) Y
    s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样- b( [& |( }( g4 \. I
    Out[127]:
    , e4 @* Y9 B6 u+ t: r3 C2020-01-01    0.4516139 b9 h6 k* X' }, G
    2020-02-01    0.4482768 Z0 A0 m. s. [& K7 n( ]
    2020-03-01    0.516129
    . Z0 D8 B! N0 L$ y: I2020-04-01    0.566667
    + i) j4 x% |+ t8 c+ T9 d  K: }/ z% U2020-05-01    0.451613
    . n9 m' T' a3 m& L0 [" [Freq: MS, dtype: float640 }+ ~+ a, }+ L. m( ?  T3 U
    - ~( b8 H' C1 U" D% L2 H. a
    1% w# H; {6 Q$ Q* Y5 Y+ O
    21 n: V6 {  q6 [# w' |7 y' J2 S2 W
    3
    ) l6 e7 T. |& r, l/ t) J, z* z4
      c& u9 l9 V8 {$ R59 p) i+ B, ?( ~: l' `8 P
    6
    9 ^; y* W: w7 o' B, s$ U% Z' \7
    ; n( n7 R) j) J  ]2 r5 z8
    % W' U, h" G7 j( q0 @* u9
    : r. V3 @9 S6 U# C* @10
    ) K! S6 ~! d4 d11
    1 ^3 r) e: h1 P3 y12
    ) H9 ]$ T1 ~) ~, Z7 i13
    # I4 e! f8 {" E" B& f14
    2 l# ]2 C) y& K9 @5 Z5 E% k15
    : Z! @. T/ M, W169 v) }0 l% k: R
    177 h1 B0 E2 e/ k5 \/ z# t, W: U
    18
    ) R2 `8 {8 e! Q/ `19
    ! X9 ~+ b! C, D- X20
    ( M( U5 h5 u) v  e; w21, q; ]" ?6 k: H" v6 F
    22
    ! R: Q4 J2 S# j1 g对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:' u* J2 g7 A$ ^! H, }4 O
    d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],- d( ]* P8 X. I7 _" ]" H$ ?
         'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    * Z0 g2 t6 e  D1 kdf = pd.DataFrame(d)7 _5 ^" ?$ Z* s/ n
    df['week_starting'] = pd.date_range('01/01/2018',* k! b  P1 _$ u2 G+ E. B
                                        periods=8,- k$ e  {; l5 O) L0 M+ z( u
                                        freq='W')
    9 I5 u8 H1 M6 Y& L0 x( b( Q8 H/ ~df- E7 V4 j; f1 s0 O9 F/ m
       price  volume week_starting
    5 s: r- I8 ~# |& H0     10      50    2018-01-07; s6 U. f( L  X3 P
    1     11      60    2018-01-142 P6 F/ e- x& Z3 f$ t% M, M+ v$ H$ ~/ k
    2      9      40    2018-01-21
    6 i% z, B8 k9 I% C3     13     100    2018-01-28
    . W8 W$ W) D' w8 w4 {4     14      50    2018-02-047 k4 b, ]$ V6 i7 b( C% h' j2 K9 q
    5     18     100    2018-02-11+ T& q8 B6 x5 p+ X  Z" l
    6     17      40    2018-02-18: H/ o2 D6 e8 B9 D) X
    7     19      50    2018-02-250 t+ H3 O+ o( i% [: f
    df.resample('M', on='week_starting').mean()/ d. [. a: S5 z% a/ P
                   price  volume
    ' q% F' [1 o4 J. Q' Vweek_starting
    ( A, l* A6 Z- ^2018-01-31     10.75    62.5
    % z) Z# U4 z# e9 l& c5 ^, G1 I2018-02-28     17.00    60.0
    4 ^2 n1 ~2 ?. {; V2 G# D5 @( T3 R) q
    ( c! Q0 y! [  ]5 ~% @% E, ^* m1
    4 i: V/ t  E. X! ^2
    . {- I! F2 Q+ ]# ]; t5 ^0 H9 g3
    & l+ P2 e  X7 x$ t3 l8 M4* [  }( k0 U# ^& u$ ~+ R5 |
    5. W' c" a1 e% Q  w2 z
    6. P6 Y7 N+ Z# G- f) P
    7
    $ K! ~. B4 j3 K. ]+ \. W8
    ( a' v7 o" `. z7 N9+ r# H7 K9 ]" ~% ]
    108 D+ y+ s4 t' ~, \
    11
    $ e) g( L2 z# R# P- ?# G, k12
    8 ?. u+ m$ C0 ^. P+ o13
    / t! `  `4 Z9 t/ A. A$ Q+ w5 m6 ~14$ Z! H5 t8 f7 ~5 I8 S6 W: G
    15; O2 M3 v8 Q( V) {5 G6 o  n- m$ P
    161 l$ J7 U8 g3 h7 Z0 F5 _
    17
    ) J& U: s- T- E& ~. ^18
    8 [1 x0 C: s  t% m19
    + |: t& ~: m8 q  U5 E0 J# N! `20
    ! o$ x* b4 P3 y4 {1 q$ P217 G2 Z+ I# y/ Z' K2 O
    对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。' S$ R+ |/ h8 _
    days = pd.date_range('1/1/2000', periods=4, freq='D')+ Y5 A+ |% }/ }
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],5 m% Y6 @' i3 v
          'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    9 I4 w. Y1 [' z) Odf2 = pd.DataFrame(
    . l# J' f3 [' D0 Y* q% }" m    d2,7 m2 W2 r, [) L. }! ?% l
        index=pd.MultiIndex.from_product(& F5 x/ X. t5 Z& z9 O8 N
            [days, ['morning', 'afternoon']]& `& z  v( X) Z$ Z. j
        )
    2 l* r, S9 I6 E) Q; b/ P. P, v)
    ' K2 ~8 c/ u: Z7 udf25 q) M$ f" b' E1 s: j
                          price  volume
    - Q9 ?7 j1 ?! }' W$ p4 K, z: E2000-01-01 morning       10      50- C% l# L1 {* j/ U/ f9 _  r% ^
               afternoon     11      608 c! L4 d1 F9 w) c4 C  r1 N
    2000-01-02 morning        9      40
    $ p. t8 s) }% n* ]1 O$ n5 B           afternoon     13     100+ P4 e* l/ p: R, O4 @1 t4 g8 X" O
    2000-01-03 morning       14      50; `* {# j. E7 `5 a, l9 W
               afternoon     18     1008 z6 _# ~6 q* ]& G# z4 r5 b
    2000-01-04 morning       17      40
    7 T5 P- V3 A7 p! j! H& X           afternoon     19      50
    & T9 A: F7 t2 s- }7 Ndf2.resample('D', level=0).sum()
    $ q; H0 R1 b# E% U# E  y9 z& b            price  volume
    2 Z! C1 T+ h4 [3 ?2000-01-01     21     110
    ( w: t2 `4 x8 a8 {2000-01-02     22     1408 n, O( C' B0 g; A+ D! `
    2000-01-03     32     150
    / U( w6 n" M1 P9 s5 T2 [2000-01-04     36      90
    $ l) @& ]" G7 x& {1 m1 w
    0 X. Q' @: T! k* B0 U& t1
    ! h0 `9 I* K1 H; q2
    9 c" ^) |6 d" z* b) o6 E: b0 M3
    - N% D0 T, J- ?# ~4, T$ r% m8 j! U0 @
    5
      f$ X, v  s- O; V: }67 d$ x' g0 V( Q3 {2 i- Y. q
    7* u* ?1 c) ]& J  z
    8
      w( H, _) E9 l8 x; K9
    6 h, l2 G# I4 N" m. ]. ~107 l3 ~( T: ^, Y. Q5 S/ E. [% w# f2 |
    11
    3 p2 a) f5 f/ l4 k, @129 h. `4 j: O7 d5 a8 i  J
    13
    , D* F; E* ^) C# c) a. D; r6 ?- U14
    # C# w2 ~$ k0 H5 t4 \' g15$ X. E; [2 c5 y" [# Q2 E, s# A- n
    16
    , H! t$ @3 ]2 i8 l# v' x+ [- \0 t17
    / C, q8 O, a) T; ^* _! \18
    , c/ C; @) \* |# z4 h7 \" k19" F1 y! K1 B# c  H
    20
    1 f! E7 [7 ~+ f8 t$ O  @! {3 r21% C* ^( J* Y. \# i! {
    22
    ; u3 K' j, Y  b  u; P% E0 U23( T, D* f" u7 x4 ]4 ]/ i
    24. C3 [7 w9 y  \7 S$ X' i3 s
    25' Y' k% W( x# u. A
    根据固定时间戳调整 bin 的开始:
    7 c, z- R7 \+ dstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'8 A; K/ j4 v0 [$ b( y
    rng = pd.date_range(start, end, freq='7min')
    ; _1 ~2 }) _6 }' n! i$ wts = pd.Series(np.arange(len(rng)) * 3, index=rng)+ C. m, G* z  ?! g+ n
    ts
    % g: u0 {7 z: g! d1 g1 p2000-10-01 23:30:00     0
    ' `* o5 _/ G6 A8 K2000-10-01 23:37:00     3
    3 u8 n0 `5 g6 X5 z/ {; _3 Z0 Y2000-10-01 23:44:00     6
    ' s5 W% k4 X# g6 X/ r! ?2000-10-01 23:51:00     9
    4 c7 x& b& K! I4 P. M& Y5 U- g% r2000-10-01 23:58:00    12
    " e9 ^0 X# ?# [2000-10-02 00:05:00    15
    9 o6 {2 W2 w0 N6 c2000-10-02 00:12:00    18$ P9 j5 t% H( c3 }9 n9 C* X7 O
    2000-10-02 00:19:00    214 J, F! E8 P) p" D
    2000-10-02 00:26:00    24( q4 X1 m/ _6 x: Q
    Freq: 7T, dtype: int64
    * x0 c! s; e. Q+ M! B! M) C; q7 Z9 `7 W! J. U) |3 t; Z! T
    ts.resample('17min').sum(), j# ^0 B+ H# _% `2 x( ~7 I6 f
    2000-10-01 23:14:00     0" Y5 Z& U9 [, l* U$ c
    2000-10-01 23:31:00     9
    0 z  t9 T9 O0 N2000-10-01 23:48:00    21
    . H/ D6 G0 J+ W) u6 E7 B6 `/ o2000-10-02 00:05:00    54
    ; g2 U7 y% N1 K% d7 i2 f2000-10-02 00:22:00    24
    ( M4 e# {) s8 b/ d: jFreq: 17T, dtype: int645 j, l, u1 I4 q, `1 }( e2 d
    ) I( R; c0 G* b1 Y; P: [
    ts.resample('17min', origin='epoch').sum()
    2 ~- u* b3 `- |# j( t8 v, O2000-10-01 23:18:00     0: l4 j& x8 L" |: p& ~$ \5 [! v6 O
    2000-10-01 23:35:00    187 C+ W8 h8 |: T; c1 j6 E
    2000-10-01 23:52:00    27) A5 i$ ?) R6 j" j$ t7 l
    2000-10-02 00:09:00    39
    & W2 E2 n) x8 p$ M2000-10-02 00:26:00    24% W, b4 M" U3 _
    Freq: 17T, dtype: int64
    # y# V+ z" i/ w7 d: [/ B1 r9 K3 e, I) y; }; R  s6 U' Q
    ts.resample('17min', origin='2000-01-01').sum(): _) l, W3 \6 O$ |' |
    2000-10-01 23:24:00     3# p" `0 P& y- Q0 E5 n9 A8 Y3 V
    2000-10-01 23:41:00    15# J0 @6 I+ `) ]  D+ s
    2000-10-01 23:58:00    45
    + f( L0 W) ^1 k( h- h5 ?2000-10-02 00:15:00    453 B& I. q5 v" D7 h; r
    Freq: 17T, dtype: int64
    1 H; H( c; \/ c2 K8 c
    " l5 `5 T# u! K9 f  x1, _: W. k! h7 V3 e
    20 t; |7 ]: M# P  j* d
    3
    / l8 S; z+ K8 M% w% _1 w) s43 q) i$ o4 d# B' c3 `
    5+ F& f% f& d% J7 J4 g
    68 R5 W0 x( O2 R9 _) B0 k6 r1 V
    7
    " a# Z1 L, z% T, u- [' z8
    ! O4 C4 u- I0 G# k2 ^6 k! h3 P; s9' H: _" O4 ^2 T/ V8 T
    10
    7 b7 h$ }8 J6 I) _4 S11
    / D7 q7 ]/ T' {4 V124 u& ?, S* X' w
    139 C5 H$ O7 ?3 \5 u
    14& P3 r% L1 I8 G% h( p
    15
    6 S. J& [% T8 A% b" O16  {) V9 r+ r- B- g
    17
    . o; h/ r" t4 g0 z6 x& p& O5 N( f7 l0 f18+ ?' k$ j5 o) W
    19
    . b) r; H0 k* S5 L# [. ~20( U" e5 T3 I# B+ B
    21
    $ L' ?# f# x5 A$ e# P22. ^" O, Y2 H# s, w4 G$ Z& R
    23
    8 I+ M4 }. D2 A7 {24
    ( v: l5 F. W( A9 z8 I25# J' `# E  x$ n2 t" p
    26" j# C. C  h" u. Q* s
    27
    6 _& ^+ `3 K% m% g. W% c287 m8 a6 e5 A6 F
    29' M" g: B1 N7 H
    307 J; l3 a4 H, L/ {$ W+ f0 G. S$ E5 Q5 n
    31
    1 s9 V- |4 f+ t, s, G32
    ' O$ l2 ~% P& R9 j- N33' Q1 ^$ k) c% c, C# Q$ u1 Y9 b5 `
    34
    6 S: L% i& [- y* k# }. b/ e35) ^1 Z4 T5 L5 u3 X% P" [
    36, o& I' ]: [/ @4 j# |
    37
    9 O1 h0 v" S, ]6 q; S& G" s8 ~如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:7 i! O" v/ P: K
    ts.resample('17min', origin='start').sum()3 Q1 `/ B; e2 Q! U6 s5 E" g: E3 F
    ts.resample('17min', offset='23h30min').sum()
    8 h8 ~# q% G0 Q# C5 I2000-10-01 23:30:00     9
    * `, W, ?+ }. O4 g3 G7 C2000-10-01 23:47:00    217 f3 @$ ~) s. k+ m; W
    2000-10-02 00:04:00    54, s& ^. Z" {/ ~# Y0 C2 l
    2000-10-02 00:21:00    24  p6 P: K0 T% ^7 o. G
    Freq: 17T, dtype: int64
    & Q7 W2 E5 L, e. J* J; U& h* R1
    & `+ ~3 K: ~8 V2 j2
    3 s6 _, o; I3 [0 ?" j# D4 S3: L. \$ {8 v% `. E7 C/ V# G5 _
    4% h; {: \% ~% O/ t' ^) [
    5
    $ c: c/ Q" a. y; u$ F4 v6
    ' V' J$ \/ {2 A5 r8 \9 P. [! C7" S3 Q  @+ b: c, f2 j% V
    10.6 练习
    ; {$ k2 E* G4 o$ r* r4 `Ex1:太阳辐射数据集
    8 J; [, ^4 [+ _2 {现有一份关于太阳辐射的数据集:
    2 v# N# e* Z/ u/ F3 ~3 W3 }7 |
    . b" p: L7 ~! B4 @2 D. ?df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
    9 c$ U. S9 \9 {df.head(3)- N. i* j+ R' j7 x
    1 S' S" O( `- D' o, N3 v; w
    Out[129]: & Z4 N9 h, C7 h
                        Data      Time  Radiation  Temperature9 p7 x( w  [1 t2 d
    0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
    5 r9 _4 z! R# |5 n1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    3 }9 N* \% r6 h2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
    ) Q# G5 t/ a, @! w0 z) ~, [+ d1
    0 a$ N6 t9 n3 {2
    , \3 [' B! \+ ?( E3- p9 ]' I6 W# F# h: H
    4/ m/ B3 c9 G7 r# \8 z) j+ b" ^
    5$ v3 x& f% ]- l2 i; f. _' [
    6# V% Y" Z% C7 X4 c
    7$ J4 X  c& |. H
    8; J: M% t, i3 Y
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    * T$ W/ D# p* e* v" z/ S3 R每条记录时间的间隔显然并不一致,请解决如下问题:0 N/ w9 i* h8 t) r; @
    找出间隔时间的前三个最大值所对应的三组时间戳。& [: I" f/ o  }, D+ y8 R
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。! E9 r1 H7 U4 R' l) ?
    求如下指标对应的Series:
    9 a# `! H4 V. F% i8 K. C+ }温度与辐射量的6小时滑动相关系数: ?& a4 e. K% B$ b* U+ q2 a3 l- m
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列8 ]3 i/ ^! J" M4 U+ S- e
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)8 }) V4 d, j/ t* b% W% c
    import numpy as np
    8 I8 m! n1 n+ dimport pandas as pd2 o! N. c# [+ S! ]" M" O
    1
    0 I3 @' i; V) j! n2) _7 W' b0 E) C- ^
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    8 m0 T% I9 J8 O1 f3 A6 J. Sdata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
    7 ]2 H" G4 K# ~1 Wtimes=pd.to_timedelta(df.Time); t+ T6 H; C6 n" j
    df.Data=data+times: c7 a9 ]- X, p7 R) W& ?. h
    del df['Time']2 m/ c9 m, \* G: e* X
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留. N; S, l, v" ]
    df
    4 p$ b' V8 P$ H4 ~                                        Radiation        Temperature
    1 j  r/ Q& K8 F4 h4 U" |Data                0 L& v; I$ D% R
    2016-09-01 00:00:08                2.58                518 T0 J. K) o; N' o  |& B; v; _
    2016-09-01 00:05:10                2.83                51
    9 H2 R2 r0 C4 E9 `% _4 }2016-09-01 00:20:06                2.16                51% |: ]' H( ]2 o/ Q4 M8 x" t
    2016-09-01 00:25:05                2.21                51
    : H* Y7 }1 b0 `7 s  P& `2 `2016-09-01 00:30:09                2.25                51
    # @, r/ `6 F& G6 {% J3 d6 H( @...        ...        ...
    ) `5 o5 P  Q6 u# u5 _# T2016-12-31 23:35:02                1.22                41
    - ~' |/ _- F3 D+ j- h1 n6 E. r/ [6 j. s2016-12-31 23:40:01                1.21                41" J/ r( q; W6 b7 @2 y! U
    2016-12-31 23:45:04                1.21                42
    : B, A% Y: J9 T1 F; e: G0 N2016-12-31 23:50:03                1.19                41* n# N; e$ i' d4 l4 _9 T3 E. `1 X
    2016-12-31 23:55:01                1.21                410 _* _# U. J* ~2 b' T0 v
    + S0 Y; ^) S. ^; V+ j
    18 r/ \0 W! Q! {" \
    2
      S- ]8 A8 w- U( {; V/ l3' B' m5 e  Y' l! C) v5 N
    4
    % H. X) u- k4 d5 i3 I5
    7 Z  k" A) P) _" u5 ~/ L+ M62 V4 M0 ]; b% F
    7; @1 p* M- I* X* r9 L- e- E* D
    84 H7 l. R9 @) U# C/ S' h
    9
    + F5 T. g: u) A/ j107 K4 k7 \1 B: w  t0 c& P9 w
    115 J1 F* \2 O6 Z! D+ ~$ z4 ?
    12
    ; u, h% y! ?4 \) B$ S13
    2 c* r3 A8 ~2 C$ Q. x! z% |5 h14
    , a) X# s  a4 C$ v- E: N6 {159 \; j# d( `8 c: F& L& d8 k1 w) `
    16
    % Q8 b) E, a+ U: V5 i  B17
    1 `. z$ A! V9 W  l! R185 [  n; O1 D- S9 r& g; f
    19# g' J- `9 g$ v( _
    每条记录时间的间隔显然并不一致,请解决如下问题:1 I1 M, s3 T* Q( h; r8 F, J
    找出间隔时间的前三个最大值所对应的三组时间戳。5 B( N  L: r( ?9 j6 y$ D
    # 第一次做错了,不是找三组时间戳
    ' q* K& |* N( k# tidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]& w; [. D8 [; w  |
    df.reset_index().Data[idxmax3,idxmax3-1]* i1 A- N, h& ~! |+ @7 g0 _

    0 C+ `! r" x: g& Z" ?25923   2016-12-08 11:10:42
    . _' N4 [9 U$ H1 _, [5 L, r24522   2016-12-01 00:00:02; K1 i8 n8 L7 e/ V, I
    7417    2016-10-01 00:00:195 @! ~3 r  G$ |$ Y8 I
    Name: Data, dtype: datetime64[ns]6 u. p" B7 h4 `' D- U6 O3 K* g
    1* F* f. K) [% z
    2( R' d( W3 ]4 l& L) E/ P
    3
    % P/ L8 y/ P/ A4# ^2 y3 z/ B/ n: T4 W" `
    5" u$ a% Z2 H0 {, F, [5 J( u7 |( c
    6
    6 Y% Q* j. M: m7; {* t: E1 [' N: k7 j
    8* l% G+ \- Q+ y9 M7 j% x  V* e# d$ h
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]$ n, e7 h  D; N$ x- w/ V
    list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
    ( \( X3 m1 b9 r  F$ n0 [* {1 n( S6 B1 @3 \; |
    [(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
    ; ~7 D' K6 D/ }: ]2 m (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
      L0 K; d$ n/ x1 G% ~$ Q6 r* ` (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]4 R0 Y/ W% q" e6 ?* V
    12 f0 F) k5 O8 P( H4 X& b
    2; @  q* _3 b& [/ L
    3) b3 {' O) h$ g! W" A
    47 Q$ L/ ^( o& |
    51 k& u3 V% z- O7 z$ D/ L! P, V
    6
    , n; D( _7 v, `3 b  W, ?7 _参考答案:
    * X3 S  p# Z6 O9 Z* o( R
    4 T2 D# ~1 {1 J+ N9 X2 o# Z9 vs = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()0 m1 D5 {! K8 ]9 n
    max_3 = s.nlargest(3).index4 y. w, g, T8 b( H0 V) B/ w: E
    df.index[max_3.union(max_3-1)]" i& \' c7 O1 X! z# M' [' g7 s

    ! o9 ]7 k% _# B4 POut[215]: , k4 }$ F+ X; J0 H9 d& }& c
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',5 g+ q1 H+ `, K9 C! R
                   '2016-11-29 19:05:02', '2016-12-01 00:00:02',
    6 D3 v: O( }* F: D3 E& z               '2016-12-05 20:45:53', '2016-12-08 11:10:42'],2 h3 q8 v5 p" D: F, z9 L
                  dtype='datetime64[ns]', name='Datetime', freq=None), Y7 J8 G# ]! E$ @4 ~2 [- S  G
    12 Z# u8 d9 H6 H/ D
    2
    ; w# j5 y$ F) m' X8 ^, F4 A: v: ]39 |0 f( @8 ^" H) J2 w. m
    4
    ! s5 m* u1 v& _- a5) g  ^" t. e* c* K- S  O& y
    62 E# R; D/ w' N5 g- j$ ?/ ~
    7
    # N0 W3 `3 s8 U8
    9 F$ t% B2 |, M3 _9
    & D3 i* ^7 Q+ n! z9 R' B是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    : j/ H# ^+ z4 A) X0 P2 r) i# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
    0 h# v: z" j7 M0 e4 Ls=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    ( k/ B( a- ^; ns.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)- |) r  b7 j% y' A' s. s* a1 \+ V. {
    6 \/ ~- V) U6 ^" a+ C' o: e
    (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
    7 Y4 M- Y* P* ^1
    - \6 X! u, g1 M( Q# b2
    % w% p# L+ j, `0 a4 D3  w% w( _% l( d) D2 I! s, L
    4
    # E  b9 _+ h' n% j  w, L: s# h5
    2 f1 X9 d+ ?, Z$ M%pylab inline1 a( G5 Q; K# \7 [& X* T
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)( T5 r+ e; Z9 C4 X
    plt.xlabel(' Timedelta')
    7 l- N# z+ R. b" [8 Q/ P1 X, lplt.title(" Timedelta of solar")& r, ~1 F' {  t7 p
    1- A5 W- S- {- D! E7 a
    20 ~# c- s0 Y  R+ \
    3
    8 z$ X5 ~6 w* m. ^. _3 w' d4. q- a& Q9 I/ B
    # m& \  o( B' p, G# c
    / N0 E2 p" Q2 f
    求如下指标对应的Series:
    0 S8 v( v- W" s温度与辐射量的6小时滑动相关系数
    * V6 ?& o' W- l! a, k0 N以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列  z3 X. t6 Q6 z' W0 d
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)  e6 F" {7 H( Q. `" ]0 Y
    df.Radiation.rolling('6H').corr(df.Temperature).tail()
    ' m- U9 D# X7 R& v  w* z5 a
    " V, x4 C5 k8 K2 ^! {5 XData( S+ w; x" P/ P- |* f& v
    2016-12-31 23:35:02    0.4161875 h" ?4 K  Z! L
    2016-12-31 23:40:01    0.416565. z5 K& b+ ^' o5 W
    2016-12-31 23:45:04    0.328574
    ! t7 s5 V# O: T7 W2016-12-31 23:50:03    0.261883, s3 |0 L7 d( W& U; K; j
    2016-12-31 23:55:01    0.2624068 F8 m$ ^6 L) d# |
    dtype: float64
    1 d8 v2 U; y7 Q4 P4 T2 p$ B1: J1 c' `/ d- P. \6 d/ h
    2) ]7 z3 T$ a9 b2 v6 X& S
    3% }2 L0 I6 A6 E8 j6 D; N2 Y, X
    40 X1 Z- p# q3 d0 `6 G( L( I0 N
    5' i$ |+ g; i5 Q) I
    6
    . `6 m% U. o6 h* v! ~: ~7
    : T: X  e! d' _% x$ y* i- k8# V" Z: _8 ^8 h' R8 L+ ~6 B6 t
    9
    ! @4 w6 U2 @, V1 X. d7 M7 ddf['Temperature'].resample('6H',offset='3H').mean().head()
    2 m. ~# E# |" r0 Z& n% R- B1 \$ {; |* u: ^
    Data5 G/ }7 |. q8 h; K4 l
    2016-08-31 21:00:00    51.218750
    0 v; i, g3 E3 p$ J: `2016-09-01 03:00:00    50.0333330 [5 ?# j7 f. D
    2016-09-01 09:00:00    59.379310
    . w% B1 o& W- m* M1 H* F8 }$ o2016-09-01 15:00:00    57.984375
    0 N* V7 p+ d, V2016-09-01 21:00:00    51.393939
    . B4 }1 ^0 [# gFreq: 6H, Name: Temperature, dtype: float64; Q3 |. E; _2 e6 P) Y
    1& `2 p) [' C  ^* y5 M  f9 F0 `
    2/ q' l( f1 z: I  o- |7 D
    36 x2 ]9 Q7 Z$ s5 ^6 w2 r
    4' {. K4 `& \: ]5 F; ^
    5
    ) s  m  \5 Q. o4 N  w, M: t& c6% w! J8 [+ _, I( _5 @
    7
    & u% y  F- b) s) f9 b# g( W- O" w8
    # O2 A3 S* K5 n/ J4 c3 I9' q: G; T$ l, e8 q5 j
    最后一题参考答案:3 G9 v8 ^4 K0 k) }5 b0 W

    * w7 U: T. g* T0 l9 W5 V# 非常慢% q1 W! J% `8 q9 ?8 ~5 p8 B4 H! }
    my_dt = df.index.shift(freq='-6H')
    ! `" W+ l4 M- ?" D5 Kint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]7 o+ K# e9 n5 P- v& |& t1 h
    int_loc = np.array(int_loc).reshape(-1)* G% R# w; d$ U* c2 C% S$ \
    res = df.Radiation.iloc[int_loc]
    ! o2 s4 j" o2 L% {# wres.index = df.index
    2 {0 O4 W% c; d- M  `0 W* Hres.tail(3)
    ( P$ @9 q$ _0 o0 ?1
    7 W, s! r6 P, `) |9 F* b: ~2
    . d; y1 M8 v5 a5 X3$ i# h0 g$ S# o1 `- j8 A
    4
    - S  c* S9 ^, d5 v8 ?1 U5
    ( w& S  l/ t$ }& G6: G/ K( H. \  M  X& a8 P0 B
    7- i4 s: I/ c  H# N
    # 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级( C! C0 N8 D: q9 M
    target = pd.DataFrame(
    2 ?  ~# i* t* q/ o6 H    {
    9 C- x# s: U1 j% d+ Z  z" [. b; N        "Time": df.index.shift(freq='-6H'),
    * x- S- `$ K& ?$ P& ]5 `" S        "Datetime": df.index,; J6 W" U# B6 v/ \2 ]9 m$ p( G7 m3 x
        }
    3 E: d7 N9 }% O" })
    5 h  }2 H3 U: z- j
    ) {7 y# w5 B+ T4 L1 L( ires = pd.merge_asof(9 K, J9 ?  s# \: ^6 l" P
        target,6 t9 a5 g! k/ T! K
        df.reset_index().rename(columns={"Datetime": "Time"}),
    ! Z7 e& l& }4 Q- B, |) r- _0 {    left_on="Time",
    ; z) B0 t: Q  R+ D- F    right_on="Time",
    9 @# p  V0 C9 c( z    direction="nearest"
    5 F7 n1 I0 o" {).set_index("Datetime").Radiation
    . V, a5 N  I1 L7 W. V  C
    ) P7 f. k/ X" {res.tail(3)
    # E( i$ y4 R2 g: `Out[224]: 9 T7 y8 a( P3 i
    Datetime! c( ~- x7 H& E& s6 @9 k
    2016-12-31 23:45:04    9.338 t% J/ u9 \0 y, C+ a8 R
    2016-12-31 23:50:03    8.49
    & u0 M5 h0 g. Y. P5 k, n' A- t2016-12-31 23:55:01    5.84
    " _5 |3 E  F, ?4 {% ~- q$ n& XName: Radiation, dtype: float64
    . Y) ~9 O# G0 K
    2 J( L) E8 ?% Z! _1
    4 l# \# _5 G, Z5 h  S  Y' f2
    + k$ _. r& I! s) C* X; Y# ?: D3
      X' n4 _5 ^, G* U) q4: ]/ \1 n* _1 A  u: ?& T( W( ^& E
    57 i) {# ~- R+ p7 ^3 y+ i/ S! J
    6
    3 D7 c/ A& T+ d/ N7
    - C; T# c; U" y8; j' d, v0 Y" f3 \
    9. ?7 O  K0 J2 f& i$ @
    10& q# Z. h) c0 Y+ `( j
    11
    ; f/ x6 a$ N* P6 x12
    ! x' j8 f  I, Q: t13
    8 |3 F% K# f+ `% |$ o# e14
    / i" l( R/ B4 w( J% p8 ]$ M7 l# b157 E$ {- |6 S: l& {8 p
    16
    5 [2 Y5 k+ v. m9 Y* ^# g& b17: f# b( b  s. M7 r$ p
    18
    ; Y, q' h6 t; ?# Z' f19- y8 c* E: Q1 }7 \( b
    200 V7 M6 ^$ B  o0 [: K) `
    21
    6 w0 i4 j0 J. ?22. Z2 k' B' K; U! R' t) C" J
    239 t; R) Q+ @( f2 ^7 V+ f
    Ex2:水果销量数据集. D; q) k0 @2 @1 F  n# a
    现有一份2019年每日水果销量记录表:
    ; @, m" Y4 h7 A
    * F5 i3 d2 K  C" u: e5 F' Ydf = pd.read_csv('../data/fruit.csv')
    # \; |% Y9 H3 O6 P. r! Pdf.head(3)9 `" I% z3 o. m! a- q7 }

    + n! X5 O, _+ b. `1 W/ Z# |- h6 _2 COut[131]: % x4 L6 a* `6 q; [  \
             Date  Fruit  Sale
    " s$ T0 R6 A* A0 ]0  2019-04-18  Peach    15
    " D) i6 z* b- M4 z5 w. R; i% h1  2019-12-29  Peach    15
    - s0 R! f+ O; [2  2019-06-05  Peach    19: Z) T+ y7 ?0 k* q" t+ l
    15 _$ h/ \7 s* _
    2
    ; H1 `5 p0 K$ R5 i* {( y: e- ^9 q% v3
    0 c6 T- ~3 P2 Z1 L4/ T1 N$ s* |+ V, N& e' _
    5! E3 O( R" G7 l! ?2 h6 n- a
    61 X5 e- x, l. B% l5 q* M
    7
    0 |2 F3 E, F6 a  b8) l7 B9 V3 B; C) Z! w9 A
    统计如下指标:4 I" Y) O- O$ T& k
    每月上半月(15号及之前)与下半月葡萄销量的比值" g0 ~9 H% y5 f! _
    每月最后一天的生梨销量总和
    ) E) o+ V& q7 g; H每月最后一天工作日的生梨销量总和7 ~# G- c$ j- S5 O& B2 s* ]+ `
    每月最后五天的苹果销量均值
    # }4 i1 `* l8 T+ M2 @按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    / g4 ^: n9 u( K2 p5 L按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    ( l9 v" p, a" \1 O) x) k. `import numpy as np4 X* C+ Q! [# v7 c- S. T
    import pandas as pd- N; K: d# S( ~: K
    1
    $ ]! a8 c9 \& k( C( @2, d( n6 Z& _) R# u- l* Z
    统计如下指标:
    - `, X: {1 m, s5 V6 N' l每月上半月(15号及之前)与下半月葡萄销量的比值9 O3 R2 n4 {  t+ [. r  R% H
    每月最后一天的生梨销量总和% E, q! X2 C- J9 }) y' |( r
    每月最后一天工作日的生梨销量总和
    , p4 A4 g' b8 F. ?每月最后五天的苹果销量均值
    ( L) L7 D: W" u# 每月上半月(15号及之前)与下半月葡萄销量的比值' a6 K: Z7 k* \; V; W
    df.Date=pd.to_datetime(df.Date)
    : Y$ Q7 i: P1 O. d$ k) `$ osale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    ( ^9 y) Y" D7 i8 gsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊+ Z: ?/ C0 k5 l' U5 g4 k' ?
    sale=pd.DataFrame(sale)
    2 h6 C; _' I- C* b2 D2 y% }sale=sale.unstack(1).rename_axis(index={'Date':'Month'},, }  p" @! r0 F; _" m7 ?- [8 v
                     columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名9 h& P3 S% d! h/ J; d
    sale.head() # 每个月上下半月的销量
    9 G3 `1 g3 g/ c7 q
    * G9 @% E' y1 ]2 M2 Q  Month        15Days        Sale
    * l" o. n4 ^. L0        1        False        10503+ b% z/ p* n6 k
    1        1        True        123419 c3 U  P$ [( r" p  h4 k1 V
    2        2        False        10001
    % q0 X( E; l7 ^9 v% P3        2        True        101068 U5 k: k, c/ u1 `6 K$ N
    4        3        False        12814; M0 q3 d6 j. c; a4 u. H4 {

    0 Q, [8 G' v2 Q; \# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序& [5 j0 M! ]) j
    sale.groupby(sale['Month'])['Sale'].agg(
    - ~; C& N: S2 n1 q  B9 p# K" I  I                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    ! y5 m0 P( O" g! w4 V8 F; B( k$ S, h
    Month
    " p* r" f: |" J1     1.174998
    ; d/ F9 H/ @; e  n2     1.010499& R( [% V5 n1 r; I1 h
    3     0.776338
    5 [# D1 A4 \) a3 ?4     1.0263458 i5 `' e( a. U, J0 I
    5     0.9005342 I. W' S& z3 l% c
    6     0.980136
    ! u5 U2 E2 s0 |( F# t' s7     1.350960% X! y& W3 |4 V
    8     1.091584
    4 }% \8 O& I% V6 l9     1.116508
    & S2 W% ]; k: C10    1.020784  G0 L8 Y" [+ i! f' S* A
    11    1.275911
    / I0 ^+ O4 I  q0 T3 B) R12    0.989662
    5 ~0 D4 O7 k3 ~8 j# XName: Sale, dtype: float64
    9 l2 q/ h9 ?; m+ r! c4 B
    . q0 E! i" J9 n( d& C$ U1- o( O5 h  D% m3 z' x. u
    2  Q' E5 L# I: K, O& C. [) v" M
    3
    ( j& c9 p& Z( y2 ~40 S' q7 m6 u7 ]) s8 n
    5
    & W) i: [6 ?1 j: t+ g6
    ( B% l( _7 q4 N+ w1 g4 \9 M7* s$ f+ s2 W3 [4 @8 ~* d
    8
    / P2 @& U$ x' N! y2 R& o9
    - C! P% p; ?2 u3 _  q$ m103 D& Q5 m3 v7 Z% b0 s4 C
    11) p) p! b$ I' t4 B' Z4 ^1 h$ S" V
    12
    7 `( j! w9 r+ b# t0 e) D3 \2 ~13
    5 D0 C$ H4 v' C+ M14, l- z  f3 H4 n
    155 a) Z0 ]  f+ s! J
    16
    ) `0 p" u8 j9 E% w17
    ! r* s% u# b# y" |) Z18+ N  ^+ t, P; i0 ?" C% ^
    195 u. v1 ~: a3 |1 |
    20" [& N. ]5 h) C% [. x0 `9 }; g
    21
    7 _. ]9 P* b6 [4 L7 z; X, ?22* e( V- ]3 b. a; e
    23( Z7 G# L8 G/ H: Z2 n) L+ ]- ]
    24; f0 i8 l) H$ \
    256 _6 ?  u" h$ f' S7 B7 @5 o) b
    26
      t0 D8 r% l  {  p27
    4 d6 }% q# n" B1 Y6 g28
    3 Z0 }( s3 [& @0 m; R# t295 k# ~8 y* r" Y8 @$ _1 ^0 |, }
    301 s" Z/ P$ j# \5 V8 y8 K# l
    31
    ( `4 R6 X# l* ^2 D5 h32
    ' Z7 C$ t4 I0 Z, J336 C# ^* p; h* |7 `; l# z" x
    34' N, q% W4 A/ H. n7 \6 H& {
    # 每月最后一天的生梨销量总和5 e* @$ I/ g8 \) N
    df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()2 s( c. j$ v0 i9 j0 {

    % N# u9 X, y/ o/ R, b& H5 ^/ YDate5 P9 ]( k4 E6 |4 z, G- J( t0 N
    2019-01-31    8471 ]- c; b% M  V4 t
    2019-02-28    774
    7 q& w( A: v8 f3 h' q2019-03-31    761
    2 U1 ^' e; Q/ p4 A" ^2019-04-30    648* A4 \4 i( h; B$ T
    2019-05-31    616
    ' T( \' J) A" I- c9 _10 F6 ?) G/ G4 ]+ \: _
    2$ w6 U1 R2 `9 ^5 H/ C
    3# v) w  S5 O( F# {) m+ s
    42 y- D$ R- q8 G! I
    5& R/ v5 p- e; @7 S
    6+ G0 q: I, |9 f, B* G3 J8 H
    7$ _% L. ?+ }# q4 G) g5 ?
    8
    % o6 _7 P" u: m6 u" K96 C$ S* B/ q8 B0 w: a
    # 每月最后一天工作日的生梨销量总和% [; n$ O5 T% P% @% c7 R
    ls=df.Date+pd.offsets.BMonthEnd()
    ) [: [( j# |. b% Cmy_filter=pd.to_datetime(ls.unique()). R, ~0 C. v( B+ C
    df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()2 S/ M) f) v5 y

    0 s7 R+ Z( D) ^1 f* _+ KDate0 j& _! [2 X/ V  s( k
    2019-01-31     847
    1 r& ]( T9 _/ ?2019-02-28     774/ N* Z, f  Y9 a" R# A
    2019-03-29     510
    + Y6 v9 }$ h* J! R0 I6 n2019-04-30     648' c  K* W" i6 S* O6 `* P! g. k5 y! @
    2019-05-31     616: g: l" b7 {8 {  k- S/ p4 o, c
    1/ M# T3 q. L  c! ^
    2
    : c1 ?5 o9 Y- N( F33 O- h) A  N$ D; l4 T
    4( A' K' j( R) t6 Q2 h
    5$ O9 S8 w( B7 A7 i
    65 [( M7 G; h) o
    7
    - q5 W9 p, x) m8
    & D+ I+ h6 D4 \/ x) j9
    5 A% W) ^% O3 F10
    ' Q+ a) q) x1 b% s: K& t- m11
    5 y. S* _% {% U% e- J' a3 g# 每月最后五天的苹果销量均值4 o: E5 A& f3 E8 ?8 V( @' x
    start, end = '2019-01-01', '2019-12-31'* @8 ~$ c4 u: S4 [# h
    end = pd.date_range(start, end, freq='M')
    0 V8 o& \% k$ m; z- kend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差8 A/ r5 z' E6 s2 L6 j5 E2 e  k
    % U* w: [/ @1 ?5 W+ y0 H5 L
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    ) A4 K+ B  `7 ?0 n* y- X+ _td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
    0 `, h: |0 Q, }8 ]9 u4 eend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    7 d- }' i1 M& Y7 N" U# w) F# K3 e# @4 G1 N2 R7 _
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量; f: v2 t( q) ^1 E2 y8 J. D
    apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()0 y: z+ {9 x8 X/ `& t

    2 ^! j( W1 r* x" W3 L# c0 gDate) z8 U* \0 R) P" D4 S- q
    1     65.313725
    - w: B; K* N. z' P2 x. s5 w2     54.061538
    / k  E5 g/ U/ i& i0 S3     59.3255810 u& R+ ~" M8 H! ]& l: N& b, R9 }  e
    4     65.795455; Z  O/ Y( w8 d
    5     57.465116
    # `; a  f+ n; w: |
    0 Y3 A2 S( ?: S& V# a1" O  a. Q1 k; C  U. k5 F
    2
    ) Q: _5 H% ]  ?1 @& Q9 A3- s- z5 b; m& r* [8 h# m1 p6 A0 @8 i
    40 P5 L7 Y- x9 s( G: M$ w0 w' L; T( B# h
    5
    0 @. \- c+ B$ S0 o  Q1 F62 W+ D1 y) v* b; w$ s  b
    7  A% d- g5 A3 ]; m+ h, O
    8
    ( v1 T  l$ }# ?& @' e& M4 c  S9
    7 {, D, ]% P/ z$ d# [10+ F( b8 D% W, u
    11% b. b( i: K! K+ Q, v4 m& \. {
    12+ C- ^. b7 a7 Q8 j
    135 Q) o( y' o4 K+ n9 o
    14% z- @, \; U, j7 f5 v
    15
    ! Y# h0 H! k4 Z4 E165 t4 K6 ~4 O8 z1 L
    17
    ; O4 J8 o6 m, M; @! g188 t1 P/ ~1 Q! b. R( z- G  x
    # 参考答案:' R" l* _! Z) ?3 s% F
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
    * g. u( d- W. P. f7 R            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
    " N3 x  y, P, G1 s" C7 t
    5 m  y9 J8 q7 N. B' h! ires = df.set_index('Date').loc[target_dt].reset_index(% K* [& z; @  c8 F; m4 A  L
                ).query("Fruit == 'Apple'"). q' Q- i- N0 u+ D: f/ {. Z
    8 O/ ]4 J  o3 {% j2 X: Y
    res = res.groupby(res.Date.dt.month)['Sale'].mean(
    0 a& |6 [2 F& l* r. v3 |            ).rename_axis('Month')" X* q+ v, r5 y8 W( l9 [
    * m. h% n, Y, V9 D5 ^7 I, j/ t/ t

    - d; R. B, X; h! i) vres.head()3 F8 h! W8 p% X4 I# k7 X- f! R
    Out[236]: . W' e; v  q% c
    Month( _; i* Q- f% ~7 C
    1    65.313725
    # w: S/ H; `, \8 S1 H7 k; r$ y2    54.061538. w# H" ~( K4 H4 A& {& l+ ]. t
    3    59.325581  g+ F  P# R9 r3 }0 c
    4    65.795455
    5 S1 f  H, x6 U; Y7 l2 N5    57.465116/ {' m% r0 _' Z
    Name: Sale, dtype: float64
    % z& k4 X* n" B- d! q7 E% [: s8 O
    % x# p# U8 U* ?% A: t- u4 l1* }4 d: U: E8 z. n
    2, R% l8 e9 _+ m% z9 Z& d
    3+ |' m! ^. I+ Q( K1 B
    43 g) B2 e& j  ]) q) K& U
    5( }3 y9 w+ P& i: }
    6/ z" e3 g; w- y, t, c
    7
    2 Q/ J( A9 H( [$ q8
    7 t# O- Y- A0 H/ ?2 g( I99 u' S2 m! k/ v  C6 C: x
    10; b8 N  _2 L" J7 `5 |* {' V
    11% o4 b# J4 t) Z: U+ B4 v1 X5 w- M' x7 D
    125 {. w  V' s" \( G* i8 `7 e
    13
    : E1 S$ Y) o1 l2 L1 v( {* c14' y9 Z& E) o* M
    15
    6 M8 [0 b  p' J9 d  X0 l16; {/ M  ]7 |7 \2 H
    17
    ( M, F" D% ?6 l' v4 O6 O18- @' b: c; `* S
    199 a5 g4 K1 m. ]% L. i7 H; T
    20* m3 l/ k% ]) u5 x1 _' w+ j2 q- N
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。- k9 H6 u+ T1 \: Y0 b( o) n
    result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
    ( A" _7 @+ J" Y7 Y# K2 ]; w: `" t                                        dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 0 [5 W4 j# R$ K" {" x
                                            & K0 D- q/ |! }' ?* L
    result=result.unstack(1).rename_axis(index={'Date':'Month'},
    % O7 g0 h9 w0 X% r% o1 \                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.1 E! O: f5 F. G8 q1 e8 {7 D7 L
    result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)! _. U5 k2 B+ v, x# W' U
    result.head() # 索引名有空再改吧
    1 h: \  }5 S6 p5 X
    & k. e% X: n& C1 z" u9 J. |, Y          Week        0        1        2        3        4        5        6
    ) r- v) G6 d0 B, y- \1 J3 ]( pFruit Month                                                        0 k4 q" ]9 P8 z) u& _# [2 C; y& P
    Apple        1        46        50        50        45        32        42        23
    ' b. C* z+ b5 c# O2 ^Banana        1        27        29        24        42        36        24        357 m( M  ?1 n# U2 j" s# S4 T9 X% I: Q
    Grape        1        42        75        53        63        36        57        46
    : v5 K* ^1 J# Q  ?Peach        1        67        78        73        88        59        49        72
    + z! `% ?$ a5 C" i; `; p* ]- SPear        1        39        69        51        54        48        36        40
    % W2 q! @; Q$ Q. i+ n# E; q1$ c% }+ `1 o$ D* Q
    2
    ! F3 v7 U1 w) s3
    , F2 R: T0 o  Z4- v) M$ T8 u) T
    5
    3 J2 X, J* q9 }( Q7 _6& q: o2 @& E8 |: W
    79 V: H% z4 p! r! e" f
    8% e/ x$ T5 A2 Q' B# D3 T* v
    9
    3 R& F* p& ~, O% [1 @- n2 E$ r10" b9 s& P% K; E! P: _8 N# ~
    11
    , S% E: C4 x6 c" I( E12: P7 {5 t7 M7 {  M8 }
    13
    - m, M7 @' c' @. K' t14- p# j/ c8 g7 I0 p1 k  C3 Z4 s5 S
    15
    7 F' c/ g& I5 a; H. w按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。7 l. ^, L: a  M4 C0 |2 V
    # 工作日苹果销量按日期排序
    " @0 _4 ~  ~2 \, k9 V5 z1 a* s! Y" K; xselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index(); L5 U4 Z  e3 _3 _/ v$ H& h. z
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
    3 P& m+ _, h# b7 e" cselect_bday.head()
    $ L) l7 R# {3 n
    ! Z( K+ e6 j3 J5 P/ fDate5 y: {  P) C: c+ i' J/ b$ s1 e
    2019-01-01    189! N, l# J; ]. k) u4 V
    2019-01-02    482
    ' ]  e. R+ k; F9 c; x& t- A2019-01-03    890
    , W' e8 m  i/ z/ Z+ [* |* B8 {2019-01-04    550, |0 r" y' G# J0 U
    2019-01-07    494- f; H2 J1 N3 y5 U+ c8 j

    ' ~& ^5 m0 X+ E4 ]6 U# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。
    # C2 f% v) l$ ^4 @) A' Lselect_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
    $ j& i; o( q: N+ w6 _9 Q) O+ `* o; {$ y
    Date4 s* e# M# ^3 y* ^- K0 X
    2019-01-01    189.000000
    6 S! M+ w9 A* q8 W1 m2019-01-02    335.5000005 @. u5 }- z2 ?( ~0 Q; s8 ?
    2019-01-03    520.333333
    + i, k. `, M, {  }: T& [2019-01-04    527.750000' {1 I+ n. U  y, Z
    2019-01-05    527.750000
    ; U' q7 E5 L$ r; W/ Y( d1 N0 d- g' X2 m& H& l$ ^2 f$ z
    ————————————————& }+ G* E8 O0 U
    版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    % M3 |4 A- P! L9 \; }8 x原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    ) y. E: N  r# d% A/ f
    ( W8 |. b' V4 T& l. L0 \! _0 K" u3 U) X
    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-7-9 17:37 , Processed in 0.715549 second(s), 50 queries .

    回顶部