QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2899|回复: 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

    # R# _3 \7 j" S/ y  P! |6 L. \
    $ ?. L1 H* i1 Q' [* f1 O* h
    + [' C. I" t  S- C5 q) L( b1 l) J文章目录
    + d: Q7 v! ]- Y% d! q7 |& ^第八章 文本数据
    / P% f  V  F  O2 s8.1 str对象! y9 B' Q- J: A9 d/ j& R- m2 q
    8.1.1 str对象的设计意图; z: A# r# N" s8 U0 x9 r) F
    8.1.3 string类型
    / G3 v+ `! k& C+ g. [1 @8.2 正则表达式基础
    8 }9 m  `, H  \- L% v3 h- a, U5 s$ S8.2.1 . 一般字符的匹配
    ( i' H4 D7 P- ?% |3 e8.2.2 元字符基础; ]" b4 H7 s. y* r+ i% d8 G
    8.2.3 简写字符集
    9 c& A7 Z. j( {% s9 @. n8.3 文本处理的五类操作
    6 U( x7 Q; F- @' K' i$ E8.3.1 `str.split `拆分
    " X; A" C- L9 B8.3.2 `str.join` 或 `str.cat `合并* r+ a+ H  R0 D
    8.3.3 匹配9 D. ?8 y6 S3 S- i. c( ?! g, U" O
    8.3.5 提取: A7 ^4 o( T! @2 h
    8.4、常用字符串函数
    6 R1 t8 r7 c1 u# [8.4.1 字母型函数1 ?+ V% ]7 j; J
    8.4.2 数值型函数
    ) H% V8 U+ f  P. x2 O8.4.3 统计型函数5 V* Y/ Z% Q( ], z
    8.4.4 格式型函数6 {& T& h! v  ^) G& v* T
    8.5 练习
    % _+ H& R) L8 o2 e9 g8 z$ ]Ex1:房屋信息数据集
    0 R' Y# v1 P6 X/ MEx2:《权力的游戏》剧本数据集
    1 X& }( n; L+ C9 T第九章 分类数据
    9 |7 v" [; w" m! r$ j% h" B9.1 cat对象
    7 i; F/ v- G/ Y9.1.1 cat对象的属性
    % e0 b+ G0 f/ [9.1.2 类别的增加、删除和修改
    + U. J) j7 U3 y2 w5 N' ?0 [9.2 有序分类
    : u  |/ U! o2 \1 s9.2.1 序的建立" ]; |8 P$ r9 M# q
    9.2.2 排序和比较
    4 W# H( O3 {% Q1 ]" Z/ |, l5 B9.3 区间类别
    8 h. l' J& |. H. Z+ |# ^3 O" x  }9.3.1 利用cut和qcut进行区间构造: \5 o3 n. ?& A6 [4 [* Y1 A2 b
    9.3.2 一般区间的构造
    - h% Q: ~# Y6 q; g- Y3 A% ^; p9.3.3 区间的属性与方法: }9 d7 K2 g7 f1 H8 w3 M. B1 |
    9.4 练习
    , H6 k7 }) A$ A+ ]* G  I7 bEx1: 统计未出现的类别
    ' m& W$ _/ ~, E5 B6 Y/ SEx2: 钻石数据集# z; C# S* ]4 d- q$ c
    第十章 时序数据
    0 _3 S5 ]  k* {" t& R& w* z6 g) G10.1 时序中的基本对象4 t( m2 c" h) \* F! ^- H- w2 m
    10.2 时间戳4 a9 |8 U$ W3 ?0 W8 Z3 c, a
    10.2.1 Timestamp的构造与属性
    5 T  m8 ]" @5 W3 F' o; z% V10.2.2 Datetime序列的生成; `3 O! Z9 F% @8 A
    10.2.3 dt对象
    ) E  e0 E8 h" l) N/ n8 B! Z10.2.4 时间戳的切片与索引* }: f) h6 ]3 P$ }
    10.3 时间差# d( D2 i7 \7 b$ r) J
    10.3.1 Timedelta的生成0 P% x1 U" M  Y4 \
    10.2.2 Timedelta的运算" P: E6 J" i2 N- U2 K! a" S- w3 F
    10.4 日期偏置
    5 ^# i: T. a: G' y* l1 B10.4.1 Offset对象7 p" D3 A; u; z# y
    10.4.2 偏置字符串
    2 E( H0 h, O$ v( k10.5、时序中的滑窗与分组( f3 \" m) L5 r1 O$ i
    10.5.1 滑动窗口/ L) I; Q+ }, ], D7 ]
    10.5.2 重采样  D/ a" I, f+ ^5 G: ^
    10.6 练习
    2 H  T% T$ `6 |/ F1 i4 pEx1:太阳辐射数据集
    3 I; a8 H3 [0 o. W: S/ O2 F; `Ex2:水果销量数据集8 {% F, E0 o+ a& z8 F
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
    # ^4 ?1 s  z% X" q传送门:' q$ a, E2 S0 S: w6 a; n# p

    / B( I# B) c( c  k( zdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)  W, w. `: P' N% x8 D
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
    9 b4 k  Y3 H. F% F' V( o第八章 文本数据/ j4 Y$ \& e+ g; Z# Y
    8.1 str对象$ q( W  r7 X5 z6 M7 e$ B" K9 y
    8.1.1 str对象的设计意图
    ' s9 t: J3 F. T' b0 U/ g& A$ c  str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    . P. c# |5 h# `
    # u" T2 P: z9 R$ U% o1 a" D( Qvar = 'abcd'
    1 h' }5 _  g/ n: Ystr.upper(var) # Python内置str模块6 L9 u1 Z( G8 ~. p# Z
    Out[4]: 'ABCD'
    8 q$ W. b9 X+ l: X+ \9 c1 Q$ l+ N: Z+ h
    s = pd.Series(['abcd', 'efg', 'hi'])  t& G0 L/ U0 ^

    + ~" l: x" E, g5 y4 a% ws.str$ z! L/ ~# M" K6 I( ~( n3 e
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    . u5 i* n% M/ z9 ?5 ~3 d% p+ \4 O* z' e8 B, V# {
    s.str.upper() # pandas中str对象上的upper方法; r% Y# v( ^" [* i2 L: {
    Out[7]: ) K8 L( R1 ~- a5 T0 p  c, e% ?' M( W
    0    ABCD
    3 p) q) g8 b& g4 B8 `1     EFG1 L/ X% R! H( U
    2      HI8 S9 i1 b8 I0 U* a# B
    dtype: object
    ( M& [6 z9 @/ h# ]+ \4 c+ u( j1
    * M0 C9 j0 l1 v% j* F+ v6 ~2
    ( z  x* P0 M$ j; e' d3
    2 i3 y3 m6 i# z4
    ; x9 y3 x: n" h- w- x' Z" H6 X5
    ' T5 k0 u8 e) y5 }7 w$ R0 c6
    ; [9 {& j9 u- K& L3 y7
    / l/ A9 h& [. H" _8
    2 r: \1 o- |: Q9 n* R" ^, o9- J% L+ I( R! N
    10
    * X( j; k7 H+ R. E; z) o' p11" V5 A- e) Z7 W" m& i( K1 E
    12
    % @7 v( T& w# k13
    / m' N3 T- Z! |& ?# L4 h14
    7 B" s4 v7 T0 `2 f15: ]) A6 y4 N$ _
    8.1.2 []索引器7 P5 G3 T. X& U( G) `9 t* r
      对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
    8 M/ H) o+ n. t# h  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:  o6 c5 l* y' f: {

    5 _! u$ I" m' g6 Hs.str[0]
    + X4 l6 j+ |7 m, lOut[10]: % H: M! ^5 V0 J6 `1 U8 ^) A* q
    0    a; m( I; Y. F1 e( q1 E( U( T
    1    e  ~7 H. D$ U6 _# k
    2    h
    3 A* }1 H' l4 z, odtype: object
    $ y0 I; k8 M9 q" p, [/ ]' N. S* ~4 ~0 C# ?. i1 Y/ j6 }
    s.str[-1: 0: -2]
    ! m( X5 P$ K" l! h8 H& ^: H' nOut[11]: * c: Z. }5 f& J9 y
    0    db
    , v- \$ i( r: Z. G, r' |1     g
    # |$ ?; X. d. N  r4 `7 m2     i/ u& d3 G6 l7 W+ C2 L' N/ p
    dtype: object
    6 v4 U# R6 X( F8 \, E) ?
    & {6 Z: M; Y: cs.str[2]
    : u% r6 g8 R( zOut[12]: , I8 m/ l/ I& s" T4 V3 E
    0      c
    3 m9 r( O/ Q/ H1      g& H+ S% _( B$ `4 e" W3 ~  h
    2    NaN9 @2 Q+ }( a2 c, H, {, @
    dtype: object
    9 u- y/ _; Y9 t* [) n
    ) E2 x* o; F$ u4 Z14 |4 L- C5 {  h8 U
    22 y. l! S) p* {8 S' P6 x9 a+ ?
    36 F" g" _. y, L% ?* X: x! g( i0 P
    45 @: D! x9 I7 s) @
    58 @4 M; o$ k3 B; u- o- E# _' G; q
    6
    * M% R5 t8 u( }2 [* {! \) }6 u7
    $ Z' ^% ]( @" A$ P, {; N2 Y8
    6 I3 h6 t, O7 o" e9) r, E% b2 {, a, u& i2 x: s5 G
    10
    ' f- d9 m1 G4 L11' }* ~! ?! y1 h4 ^' \# Y7 e6 m
    128 G) D- J( ~( G8 f
    13
    " Y+ ]1 f8 r; U# U4 a. V2 \1 _, B; H14
    0 M+ W4 p2 c6 y1 M; |/ ?15
    2 [* C0 a, ^- Y5 j' U16. m! ~- h, z1 F6 g
    17# d) P) n" v: a; O4 e" q  o
    18
    1 t$ l. S8 H9 m! D19
    $ P, U3 Z. ^; _9 I- c20* g/ M" }8 t+ q  X: K( ], ]
    import numpy as np
    7 A- V' S9 N6 Y; V5 |8 Qimport pandas as pd
    5 E; F6 V+ w6 W2 R4 |: T; N
    9 D# G. u% a, Z0 J6 is = pd.Series(['abcd', 'efg', 'hi'])
    : h( L, o7 B  M1 }/ cs.str[0]
    # e6 c) K3 e1 i4 t( h% ~+ Z, ?1
    , c% o* M! C1 X- D2
    . H9 j- @3 f$ W7 A9 H( D3
    - A7 l; x& o4 i$ y3 C3 }: F8 v4
    0 a5 T7 L" ^& E9 g9 a0 t5/ ]; v6 u- x+ \! {1 u; {
    0    a
    4 D( k* Z% H3 i4 q1    e) T5 d6 `$ S+ M; H
    2    h7 }( F5 \" M& }- x9 l+ G4 {/ X. R
    dtype: object
      u0 b2 v1 ?2 }0 f# [1! X2 s7 G; t# E* N9 Z. |
    2: j0 J  h$ N5 k6 ?) H! S& V
    3
    : V" \# z0 x9 D4
    # E/ |$ s4 P, s- L8.1.3 string类型
    ( s2 P" t/ ~( J1 V/ ?( f. ]  在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
    ! j* T6 i4 Z8 `* w+ _4 s! a5 C" c  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:0 _# ~5 I1 f7 z5 G9 K2 v- s
    $ R9 [+ L2 p1 H1 _5 k$ H
    二者对于某些对象的 str 序列化方法不同。3 j5 V6 X8 J& t- Q8 j  b' J
    可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
    . g3 O+ U9 k7 o, d' |! T! ^' |s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    $ m  K$ `7 ?1 O1 S, g! @% I' ^+ }s' g7 ^% F7 B: V  [
    13 M- @- K0 r5 E( G( ~( G  w4 _
    23 R9 c* z' A3 p
    0    {1: 'temp_1', 2: 'temp_2'}! I5 `0 G- C( Y" v
    1                        [a, b]
    * j2 }' B7 B3 c4 J3 ]. m) T2                           0.56 h- \3 R) z: U" s
    3                     my_string
    / V4 e7 C6 C) I& Bdtype: object
    & A- H" o& F$ [- c1
    : Z$ N: _. O5 o+ f1 O4 G2
      [( p$ ]! R7 @+ K8 w- B! ?7 ?3) `5 d4 I$ g$ T3 T1 r, ?; w
    44 f% @! t) P7 u4 E
    5
    : D; [3 k% u/ ^: A3 p: s% T1 ?" C( vs.str[1] # 对每个元素取[1]的操作5 W5 \7 B0 Z' V, b
    1
    5 v, i) f5 Q0 ?0 `. F4 W  M0    temp_1! h% Q/ M) q9 `
    1         b) ~; h. m1 d2 ^( V9 U
    2       NaN
    3 C8 R3 Q' e. n% S3         y( E7 R  _. r' m- l- V/ g) A( {
    dtype: object( R( |( R$ @1 y! G, G0 E6 [8 a
    1* [! u6 C# l# y4 V  h- e* R7 s
    23 K, Y* P4 }: z$ B
    33 P$ W9 B6 c7 z( Z4 p1 Y
    4
    2 S6 A6 ~! ?4 M* _7 Y54 E) G! {! L' Y$ \0 d" a3 J
    s.astype('string').str[1]
    3 C0 Q& C- P0 F; n1
    * }( v$ P$ _1 O: q0    1
    ' t2 {$ s1 @" s1    '
    + x, Q. V: w( h. g2    .7 n. s3 s& ?# H2 _$ C( G8 o
    3    y2 x7 {. O& ~) A: I
    dtype: string3 ]* Q# U6 C5 M. z( y0 Y' ]  f% b0 K4 ?
    1
    7 @& z9 L9 _' I+ D) `9 V29 D  Z) f# E  Y/ i( K( [% S
    33 }5 N7 g$ A- W2 h5 d: e
    4
      _* _7 d  [. r5& ?. ^) i1 q8 e- S! n
    除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:3 S5 a3 s0 a$ r
    & [% q* ^- E6 ^& J
    当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。4 Y6 N7 w' t. I# z8 C- e
    string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
    # O0 M6 R' |+ P) b( ^7 t4 L8 xstring 类型是 Nullable 类型,但 object 不是" e+ N4 H8 W/ N( o0 w( e" ^
      这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
    8 b: c3 f- v: K2 M* X  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。  n' g# [5 Z$ ]- q, E' V! l
    s = pd.Series(['a'])2 Q6 _; e1 ]. x# |1 R9 U7 C
    * P; N# ~4 k! t1 g+ q8 x
    s.str.len()) a: h) R! G" ~* @
    Out[17]:
    : K, K+ C5 I- c: K' E0    1
    0 q! C- f: T6 G  odtype: int64
    & e: I9 i7 w- _) R
    0 e; j) v7 B. v/ L% Ls.astype('string').str.len()
    1 w& Y, E9 [4 FOut[18]:
    ' ~2 B- {& U7 I& |# R0    1
    : _7 ~5 W: K) _/ qdtype: Int64
    + c! x$ T5 v9 y, |* _. z' Q4 \
    s == 'a'
    : M$ {8 J' T+ aOut[19]:
    4 q& N/ g2 x2 ]' }0    True
    , m5 A9 H% T/ d3 Ddtype: bool; ]; i- V7 @" ^! U) P1 w
    # S# s+ o0 C  Y; y1 V1 z. y- l& }9 }+ O
    s.astype('string') == 'a'
    , P* j) u9 D7 i" pOut[20]:
    ; ^9 [; j8 M, ]8 o% H- C0    True# W$ B8 g+ v2 ~$ _! T6 u
    dtype: boolean
    & n: K) ]% I# \
    : j$ l) Z) J/ C/ a. P5 \4 as = pd.Series(['a', np.nan]) # 带有缺失值5 V2 N+ O2 h# Z$ \( @: W
    / z5 e6 K3 H% ]' ]4 }
    s.str.len()9 {! G1 _0 t3 ?4 l' p% }
    Out[22]: ! e* k4 b" g) K: N8 z" f
    0    1.0
    & K6 G4 g8 u( u6 J6 ~( e1    NaN" k# C0 r1 l  e1 w
    dtype: float64, X" z. N6 P# Z

    ( N' j3 y7 T. C+ @s.astype('string').str.len()
    ! T& D* x5 m; p5 {5 iOut[23]:
    : ~5 W( S3 ~1 @, i0       1
    : F1 {/ r% `% P, j* ?- U! z+ C1    <NA>; C5 M7 }4 s6 m* C- O
    dtype: Int64
    0 u; |* q% B' U# [
    7 E& ?3 X) V1 U. y6 Z% S3 Xs == 'a'% u" Z. m' M. z4 G* r) c% [2 z
    Out[24]:
    . b, ~& }: [7 w/ ^% I: O" n0     True1 f6 z5 b. ]( d  w/ q2 o; r
    1    False7 G+ L6 a, v4 J' k
    dtype: bool( b0 [, Y- }! Z9 J  ?1 |
    , J4 G* S7 p9 Z/ Q
    s.astype('string') == 'a'- O2 w, [  x/ j- j+ ~7 v
    Out[25]:
    $ u9 H: @, z9 F& P( ~5 i/ m0    True
    ' v9 H$ W# E6 o5 b6 \0 U' Z1    <NA>
    1 I7 p& ~# g& c5 }3 Ydtype: boolean
      ]2 l& m0 E6 q% b0 v: C! n% Z% j# A8 E" z- V# o
    1# ~7 f5 u, b/ Y% r
    2
    / S- X0 ?( {  Q7 u3
    6 M% F+ C! K6 l5 I4) s0 m+ ]: C. C6 M* q) {( Y
    5
    3 d* F, z+ z; A1 r- A6# O) T8 k3 b! i0 i9 j
    7! d& ?+ x+ a2 y5 b1 Q9 Z
    8
    # y( b* H" E* n- N9/ b4 E8 B9 N% ]3 p% Q- |
    10: D5 l0 l8 s3 d
    11' ~6 c3 p  \6 ~. `4 j
    12; E( F6 z6 C# A) A3 f0 N
    13# Y+ b4 j) a# X. ]# B" U
    141 p2 J7 @5 a6 C7 H
    15
    ' Q" `. e) p0 t! f8 }16+ S! d3 y9 a( T2 _  }
    17
    + }9 z. i0 V- u! R, ~; w7 v18
    " Z+ u  W* t0 {# |4 I* ?5 g19
    $ |6 U, T; }9 u3 D6 Z20
      S+ \6 A. Y4 ~- M: g, K21$ O# `  [5 E/ X1 w% v9 T" t! S$ d
    22' f5 p6 J/ ]) ^: n  A+ m" q
    23
    " E8 `. X8 U1 E: a  p6 h6 Y  T! m24
    1 Q2 B: V0 t4 z5 j! n2 J2 D25) G' P* N' ?: P& r, e# Z
    26
    ' u$ y6 Z) U6 ~27
    6 L. ^3 C  a  _& m% B: G' V. Z. b28+ F  ^6 _# R; [: c
    290 V9 y9 S# J3 T* E
    30
    ! q+ I7 W% ^6 A+ D31
    * F- c; s4 [7 d& x; {32$ w$ }7 j/ {& E  J7 ^
    33
    ) \  ]+ I; [! e' l1 ?7 ~0 n34
    + t; ?: C6 _! t# u35. J3 [( X) \# s& q! L
    36
    + x) [  z6 v4 ?6 \- o37) u% _. J3 c7 _* S, v7 N. j* [8 N
    38
    * [" p1 W% V" L/ [7 {7 V% F/ O39
    ! ^* \& x7 n# U% Q8 m40
    6 [% ]" P  [3 k41) @) A; X# R. E! k" Z( N2 r
    42
    ( b0 ?, p8 n& G' ?1 f9 Q/ T430 m8 e/ a7 O& ~8 j. U+ a! P# z
    44
    7 N4 T" a, X& U/ B* L& _! G, `' I45( w& s0 v) t; @) h: t4 N2 d! c0 u% ]
    46
    0 U! v) B, H& l) F& K# t" F478 w# T0 Y, C: L( r
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
    , S2 S& P- v, `; i; n- d6 a, O2 d; r2 ^/ \) h
    s = pd.Series([12, 345, 6789])$ m0 ]* ?4 N3 X

    . C: g$ P* x0 B* js.astype('string').str[1]
    , f' H. J/ L" t2 QOut[27]: , K& _' u5 q4 u* l
    0    2
    7 X8 Y' l3 ^" y1 I" O3 `1    4
    ' B5 p9 }6 P& C* M* a$ y2    7( p% m2 L' z, t+ P& ^1 K" A
    dtype: string
    % G2 v, H3 c9 S+ l- G$ X- h1( V* z/ v9 G. C) p$ E
    2
    , a( M9 n# f; b+ G# N- B4 t3( |' }2 v' j7 b& p8 @7 z
    4
    4 t( a) Z# g, u2 e! C2 b' P5 C5
    $ E( c: V. v0 v. l63 x8 {4 j1 F$ J9 v
    7
    ) W! y6 L, o6 R1 G$ q* ?8
    * Q1 p( K, N% l' ]8.2 正则表达式基础
    2 y2 s1 |; h% L) s+ O, U+ V这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
    . H, F! @( d- l  x  x% j7 x4 l# C
    8.2.1 . 一般字符的匹配
    6 L3 F' j! b3 K" b1 G& h: j. j9 K2 p正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
      @# ?3 i( W8 Y. ^8 I( U; B( O* j0 V! H- z9 w  h$ v
    import re
    : w1 t" P0 D" [) L( d; @
    8 C) y& \* N: I4 [; l5 n6 o) e' Are.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配+ e1 X# z$ T5 I2 Y/ r3 S
    Out[29]: ['Apple', 'Apple']
    & m1 t: ~1 x, C3 m% R* V5 k18 Y( @( D+ o* e/ O, n  M0 r' i
    2
    $ `5 u( A+ G! {% i# o0 @3
    . [; A. U5 S* b8 E/ I4
    & ?0 w1 ?" l4 B% ^# T' g; Q8.2.2 元字符基础! }3 Q; r3 \/ e5 H$ [
    元字符        描述* L# `- q( t; s  W. ^0 [1 w$ u
    .        匹配除换行符以外的任意字符; M7 x( c& C$ ^. U
    [ ]        字符类,匹配方括号中包含的任意字符, A' W$ [8 v  j8 Z
    [^ ]        否定字符类,匹配方括号中不包含的任意字符9 ?: d0 J5 ]- h# Z0 a. M
    *        匹配前面的子表达式零次或多次! [& [# |1 F( @
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
    + W: c1 d8 n# T6 ~) m* P# P' i, k?        匹配前面的子表达式零次或一次,非贪婪方式
    : I- ~0 f# N7 e& h: B7 J{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
    & `5 j8 d. S. `0 f3 N(xyz)        字符组,按照确切的顺序匹配字符xyz
    3 _! u* M* A# i  p|        分支结构,匹配符号之前的字符或后面的字符5 \& O# z7 K% d% Z
    \        转义符,它可以还原元字符原来的含义0 g, R! X. ~$ U; O# ~6 D
    ^        匹配行的开始, e9 ^4 D& Y* W; a  E& j' Z
    $        匹配行的结束
    3 a- h4 w& O, H8 c/ eimport re
    % e; U7 s2 X: ~- Z" V  ]" t1 H  n$ xre.findall(r'.', 'abc')
    9 v8 ]4 t9 I- q1 LOut[30]: ['a', 'b', 'c']7 k/ y4 b8 t: R

    1 R1 |2 a* C0 ~- ~8 nre.findall(r'[ac]', 'abc') # []中有的子串都匹配
    ) S4 n+ W+ L( D" x6 ?Out[31]: ['a', 'c']( k  r* U1 K/ q, _5 L
    ; O9 `" `' K3 f/ [' ?" f9 T) |2 U+ a
    re.findall(r'[^ac]', 'abc')
    & F/ M) a6 C8 A; a! Q: B! H: t# }Out[32]: ['b']
    7 Q) f. R: z, a  V" T& \( ?3 P3 ?# L: ]) ^0 Y+ p. c. Y. ?( ?
    re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    1 Q! f7 L9 p/ Q- g& D9 D) o, k6 ROut[33]: ['aa', 'aa', 'bb', 'bb']
    # X) h3 |6 W# L9 `" k
    5 a+ e, F% n! T$ Q/ g. ]; J  Z9 N7 jre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串5 T0 |1 z  r( a: s# l& d6 P
    Out[34]: ['ca', 'bbc', 'bbc']
    5 g/ N! l+ S* u& V5 x+ A# \4 G, R7 }5 R4 t
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。9 K  \1 Z! Y9 k
    """
    0 ^$ B1 [, z5 c( @1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。) G0 C/ D# f: q) Y7 W  P, R
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边& K  p. i$ S1 {# A# S, |* |% ]  P
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,8 c& e) L% Q6 d: F; S6 x( s
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a3 |3 H. I- }8 e
    """
    & [& U% Q& m7 g
    8 u+ r+ t) {' `4 a4 _3 p8 `% W7 ^re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。
    $ I$ I. K- B& t  @; C1 G4 n& QOut[35]: ['a', 'a', 'a', 'a']% A1 o/ p( G% [# E* L; U) z/ d+ I* |
    ! u9 A  `8 K$ C. d. ?
    # 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。- }* _4 Q& k9 r7 B0 k/ g# ~
    # 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。  H" `% s" ^4 _
    re.findall(r'\\', 'aa\a*a') 6 B+ k/ T$ k6 v. x5 i6 t
    []
    , ?- _9 K. n! X) c
    / m- V. T& H# z1 H$ H1 z9 Dre.findall(r'a?.', 'abaacadaae')
    ( w# _7 ?2 ^# I# V9 D. J4 z! N1 g1 ^Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    4 s0 v; @* m9 U! S( r7 P) u% X$ v6 h
    4 c1 S  }/ \/ [- u. G. O1 X9 [re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表4 ~& ]' E) }3 k% W. K
    [('width', '20'), ('height', '10')]0 _1 D: X& p* H+ j) v$ M
    8 A) ?/ Q& h6 T2 f' b
    1
    " l; p: Z: N# J# a+ O2
    + y' B6 ?6 ]! [' k7 k3
    * I2 Z8 d! N% G: I& {+ a! U4- [$ W) c" K4 H* J7 {; V7 A! m
    5
    ' T0 ]5 \  m* W# v; Q66 c8 m/ `( p5 G4 q, |
    7
    5 s0 g- I# e8 N$ q4 f9 P8
    / X9 |9 E( t7 ?9
    9 n. w8 E) [- ~. j10  J. O2 N% \- ^+ ?9 C& L. W
    116 u- w# x( s) i
    12
    5 ?3 K( [" ?4 {: N2 e136 B% E2 @9 d* x+ w' h
    14  j: s. }# A  E8 U9 G
    15
    5 A; }+ u# I6 n8 z( ]% r0 k16; b% O5 Z' ]8 s2 a3 |4 V
    17
    . B+ _) T* T4 {4 C/ U18# g' |8 f% {5 W9 q
    19. G6 S# o+ P' l! R% w2 @
    20
    * [5 f8 c8 D1 T0 ?21
    % f6 u5 l( _( C2 B% e5 W22- y- f+ y6 w- W$ K+ H, J& I
    231 F. Q2 [( q! {5 t) j8 W* z* H* U
    24! _0 [; y2 s: @+ ?9 k) e
    25
    ( |; A+ f( U, N: u26$ ]5 y9 T* X# f9 V
    27( {& t) C6 p" i- V* o2 L
    28
    ; N: a% d+ h3 \29
    7 t2 {9 W0 Z2 C8 v4 z" U30
    0 ?8 G- D! `& D' [% \/ X2 E31* o0 ?9 X3 _$ r% s
    32
    ' r) m3 v! N% H/ j" n  M338 I. Q+ m" H) g# b
    34" M! g3 ]9 m, D- v) t6 P* @$ z
    35
    , D* F  q# B8 T2 j36
    # Z! m# b. [- `% B: Z3 ]3 C37
    % n, L% Y$ J0 j3 ~  g$ d8.2.3 简写字符集, [; G& R8 T2 w9 A9 Q! o9 M+ E% j
    则表达式中还有一类简写字符集,其等价于一组字符的集合:
    7 ?; V( U; W: i5 j; w1 l6 v1 v* ?& ~5 F: U& \7 v) b
    简写        描述  m) d6 K8 ~$ i) [" @
    \w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]0 g' d8 Q6 x, u' i' M% u
    \W        匹配非字母和数字的字符: [^\w]/ o2 d/ i! ^7 k7 L% j! Y0 W$ l
    \d        匹配数字: [0-9]6 R0 n+ A" j9 y! @) q6 K
    \D        匹配非数字: [^\d]% F! c; L* k6 O8 p* t$ i
    \s        匹配空格符: [\t\n\f\r\p{Z}]8 c' W. i! j# W1 o5 ~% h
    \S        匹配非空格符: [^\s]
    ; o" G" b/ X0 N$ b( `0 i5 F3 o  M% t\B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    * J0 d; y9 C  y, ~7 m4 \- v! Pre.findall(r'.s', 'Apple! This Is an Apple!')
    . q  N- r* E& h9 q9 G" gOut[37]: ['is', 'Is']
    , Q2 G! X% d  e+ q: s  n* w( R# v8 L$ T$ ^" X% c/ y
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次1 A' A7 d& H/ M- i3 ?: a1 s$ R+ d
    Out[38]: ['09', '7w', 'c_', '9q']
    + `" n5 S% H# o" @0 L8 J* n9 Q4 E+ ?0 b( K. G
    re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
    & `1 G# I% p/ u! oOut[39]: ['8?', 'p@']- N% f6 {7 R1 ?% g3 W
    ( q/ P1 Z, g4 Y& u5 Z9 }
    re.findall(r'.\s.', 'Constant dropping wears the stone.')
    5 I- y( o9 F$ e1 b8 \Out[40]: ['t d', 'g w', 's t', 'e s']# r1 u3 d, m( e) r. A" ?

    5 n1 O) h# @: D: Z; E  ~) M) yre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',1 x; M+ S3 f+ e# e( k" q
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')5 H$ t/ i3 o( T2 c* D

    / R1 t/ {3 J/ POut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]; I# d/ y0 J* j$ p. a! i; |

    ) [# @3 {; X6 O/ Y0 m! G4 l; |14 }0 s( R2 c1 A9 v$ E
    2  r( Y2 J+ `. a- r, T7 B
    31 n! m% z8 k" ]
    49 t4 V8 t' |  ^- v& c
    5
    7 [8 n5 H, w* t9 A' d: {5 f6 X* C6% x+ G) m- M) n$ z* K* _0 q1 R
    77 m# w  }8 d4 X' _
    8& ]& ^: ]& K$ [: A. G
    9, m4 F/ b- H. |( r
    10
    7 w+ D+ c' w3 x5 q! w0 k+ S6 M% q9 B" Q11
    6 `/ \+ e. c  O6 u/ d% ^7 x12
    8 I9 {! n/ G; T& C+ U0 ]( P13* \: x" u5 ^% ?6 J% Z  N. G! \
    14% e' i7 D9 v) L: j/ N
    15
    6 d+ y9 s0 D: [. v* E* M# ], E16
    2 Y* l7 @* f" d4 Q) m( f8 P+ O8.3 文本处理的五类操作* c; H* C6 @5 g0 \9 m6 E7 w' k# @! \
    8.3.1 str.split 拆分
    * W/ s3 u* Y( x+ C  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
    4 Y  ?& P, Q0 R+ g9 ~, E
    . J, k& r0 V) S0 E2 j: ]s = pd.Series(['上海市黄浦区方浜中路249号',$ U8 l' y0 I2 s3 t" p2 R$ N
                '上海市宝山区密山路5号'])! F9 D7 f! `, p  E+ ?. \
    8 z' `; T, D1 q9 {& ]( f2 `

    , y$ j* u# p5 y8 D1 @7 n9 as.str.split('[市区路]') # 每条结果为一行,相当于Series) _2 l( ~+ N( ^
    Out[43]: . w4 A4 s9 A# G6 }, d4 v8 d8 G
    0    [上海, 黄浦, 方浜中, 249号]1 D5 C1 n$ ?1 {3 s
    1       [上海, 宝山, 密山, 5号]  e. A% S2 {! q, D6 I, @6 x4 ]. n
    dtype: object
    3 \+ t( p2 S7 \7 ~, I
    5 G% t! G3 a7 C2 W; _0 G* ?s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
    ( P+ I4 L6 q" V' Z" MOut[44]:
    + |2 I- Z( b* W- j! m% q/ f    0   1         26 a4 M0 k: B& o
    0  上海  黄浦  方浜中路249号7 q: `; C1 {. P) {- S: S
    1  上海  宝山     密山路5号
    ; A5 s, q, k" k& T& v5 N1
      {. }- }0 P0 R29 U" A2 N) m: v( W# r
    3
    & y6 J1 x$ l6 Q* y4; ]8 r6 l) t0 O+ q5 d: q
    5' |  T; @/ m; ?# d9 V
    6
    & U8 n" b$ m# d& {4 l) x( h7
    0 }5 U7 ]" Z# Q0 [) D0 o% [) ^81 S7 K" {' F2 {2 {/ Z$ H
    9
    : K& O" L. E, X, v  [10  o' S5 U' C7 X0 y
    11
    ( v$ `/ _2 Z; E& d3 f12# _4 @( g; T. ?; u" ?4 q, ?
    136 U) M  y  Q7 {! R) t
    14
    * J* w# @& T( t9 G. g  {5 z15
    % q3 [6 J! J( V  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:- d9 I% O7 `. u' N5 [8 ^
    ; i/ c: i3 I+ f
    s.str.rsplit('[市区路]', n=2, expand=True)
    0 [% b/ A) K( M0 o7 L' FOut[45]:
    ' A7 I6 y3 ?/ d( |) E% q                0
    7 j. [7 H( {4 ~& e0  上海市黄浦区方浜中路249号
    ! u9 a$ a3 u2 T- U3 B1     上海市宝山区密山路5号
    6 \- c7 }* a" \0 z& `; a1
    + V( C5 H- p7 F5 W2
    8 O8 c4 @2 L' Z; {5 {* w3' ~) \* y3 q3 x8 N
    4
    : m3 k8 q6 \% ]; m1 Y5
    . A/ n6 s3 z$ E4 D  ]) S% @7 P8.3.2 str.join 或 str.cat 合并& f) ~. @+ P' R
    str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    - ]( `  d2 T" v; N& Y! U( Zstr.cat 用于合并两个序列,主要参数为:
    ) v4 k4 C5 w/ ysep:连接符、
    0 h9 V) B4 U4 o7 tjoin:连接形式默认为以索引为键的左连接2 u' A0 ^# O  M, Q$ A* A: B
    na_rep:缺失值替代符号- R7 y: i3 c. g2 w
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    + U  O8 I- |4 Q9 |/ X5 E. Y( cs.str.join('-'); Q" F2 E8 v. [5 B% D" H: O
    Out[47]:
    6 Y$ N. X* h' e; P" W3 G4 m, Q, O0 |0    a-b1 w1 y0 u5 }9 ]) s: v/ B( G) T( x) v
    1    NaN
    : w6 g) x4 _3 C  O) E1 g7 i2    NaN4 B5 Y3 j; b! L& M, A
    dtype: object* n4 U5 V. H, m+ D) y+ r! k
    1
    3 o! r, N9 @5 ^4 ?) S" L8 P5 ?4 @2  J% H# {' Q$ W
    3
    9 P& ^& L4 W6 y0 r4
    & `, ^; [; B9 z/ d7 R5
    ; U* O; c, k  Y* ]( q$ S0 N6" _/ s6 }9 u& x; l
    72 ~) Y: j$ @  p( ]% q& |: I
    s1 = pd.Series(['a','b']): W9 Y) f( J8 x' C
    s2 = pd.Series(['cat','dog'])1 v2 O+ R) V7 q
    s1.str.cat(s2,sep='-')' o3 x% ~2 |6 S) y
    Out[50]:
    ( i! D! h; k1 @1 ^: }1 W0    a-cat6 |% ~' \6 E# i2 q
    1    b-dog
    & Y" m- K: K0 G; `; fdtype: object
    ' ~+ H+ E1 |/ E. w# `4 W6 c- b' U
      W* y8 K3 E( P1 ts2.index = [1, 2]
    9 f' P" |1 O5 o: h  js1.str.cat(s2, sep='-', na_rep='?', join='outer')6 }0 m9 I3 K1 v: b3 @! Z- A0 k
    Out[52]:
    3 O1 P+ a2 [' L: K$ {  Y$ L0      a-?
    ) v& \! Q$ B5 f) K  x1    b-cat. z2 _- x2 G1 v, C
    2    ?-dog1 F0 w5 u& i; [
    dtype: object
    * a' u. f0 V0 y1
    8 T+ Q4 }1 N; H" M/ O1 ^2
    % E" n. X% d* P" {3
    ! c! b6 N; |) c; n6 G2 O* U+ a9 P4
    + B( j4 N7 _8 D6 \5
    & {* Y7 I6 a% `2 J* {6 U& o$ h" E2 H1 P6
    : {  V# s. c8 B! W" S2 |9 _5 L7" L8 W: k4 ~1 `2 q
    8
    - g7 T/ Z/ m% z5 y9
    - y5 h& ^: t7 m4 \5 W: ~' _# ]10
    " i& \+ [  w2 U1 {: |9 p11& P) v1 U: y5 o; d* a5 E7 d4 ]
    124 P( w* b& X+ [$ r3 m
    13
    . B# ?: Y! e% S14
    8 M' ?  q) ]7 N- k+ f, i15/ z# H2 q6 R4 k2 o
    8.3.3 匹配
    1 y: v  k1 ^: D9 C) W4 I6 Zstr.contains返回了每个字符串是否包含正则模式的布尔序列:
    0 J7 L2 x( J5 k' s3 hs = pd.Series(['my cat', 'he is fat', 'railway station'])
    8 ]9 z# w0 M/ s/ L* hs.str.contains('\s\wat')& ?0 x* G$ @1 I& d0 Y& h' @

    $ u3 r3 ?/ D7 s7 ~1 P! r4 n0     True2 u& X- V4 ?9 z  I- G' {
    1     True
    0 _& b; b4 w7 y! c2 W/ T# {  a. B2    False
    & X4 d, K- u0 n; t; g! adtype: bool
    + q9 J, m- _  A9 Z: W- O1& L/ j3 G2 T) Y( g5 i" b
    22 p+ Q$ K! L1 T& i4 z; @
    3
    * E, T+ ?$ k: E7 o* K' T& {% q7 h4) n" x& s; ?0 @. b$ K! K* U- @# y
    5: R6 c8 O) R! W- f
    6
    ; x) N  k4 v; ]3 N9 X  g7( m; ]' t% x* R4 i* W" e
    str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    - y& S6 h; B- x  M. T' x6 |9 z; w/ Ds.str.startswith('my')/ j' a3 |  F/ l1 i7 F) P

    * J6 I7 H3 {/ S2 K, z) J# }0     True0 }0 ^+ \/ {. w- P8 G3 t
    1    False
    ; |. I6 H, t4 h; y' T2    False
    $ ^  R) ]/ ?- z/ _+ h3 Q8 f# G$ ?dtype: bool4 {$ l" Y, [! ^& A  G
    1
    $ n8 j# ]9 p- [# x7 V9 N1 y- ?( v2
    / A; B) e- ^, A5 ?3 c/ `3' u* h7 o5 G& J
    4
    4 _- A# A1 C8 h7 J$ D5
    / K2 ^# C# H7 l# ]4 |$ f6
    * C9 s" z4 o0 Zs.str.endswith('t')
    6 Y& {, W# ~8 `8 w8 H7 L
    ) `0 h2 z. x) E" W4 \. i: u0     True% t& B5 A" ^) |/ k+ I/ |7 z( K' a3 d
    1     True* `- A* h) T3 `) [5 O# P
    2    False, w' o8 a8 F0 e" U; W
    dtype: bool  k( q) R" s' J
    1
    " I" f/ J5 p+ l+ X3 f, W5 c4 F2
    1 E, Y" h# `! ~6 t* E3& Z# g, o; S6 A2 F" a
    4
    ; U1 j) q: V4 |' `5, g3 n# [: k3 d6 B9 R
    6
    5 u5 i, x! b+ Z2 P1 E$ J5 V+ G& I9 bstr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    ! c1 P2 A8 c" U) }. X5 e  l4 y) Js.str.match('m|h')
    . b! t% n8 C: X8 ps.str.contains('^[m|h]') # 二者等价% C# b% I& s5 g3 x; @( Z; H

    / K; \& a7 B) `# H$ X0     True' O& ?! J; u. ], }
    1     True
    * \+ n+ n) H/ C% b3 Y3 K6 ^6 _2    False) e. A4 J; d8 ?8 F; q
    dtype: bool( n, z. c3 {9 ]8 B
    1
    - o1 U! @6 ^# x9 p* x' q2% h% `# _* l- R* e1 i! @9 H1 D
    3
    : @/ K& X/ }- I& E* D" @4 Z' _4- o8 w& Y% U$ A2 U! K4 P; }- B
    5
      w* w# M$ R$ o6 s6
    ( D6 V8 B5 }4 [+ m* ^- h6 _% G- T7
    6 u% I4 O1 e3 [3 }! }; W7 c; [s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配; e7 \( p; I- R- Y1 X. u- D# v9 e
    s.str.contains('[f|g]at|n$')       # 二者等价
    # V! E! [% a+ K/ e% C& q1 O8 A5 o1 i1 w+ c* c
    0    False
    3 w9 z7 X/ w8 d0 N+ H* W0 m1     True
    - G  ?+ e0 P3 p  K: E2     True6 {0 o1 f5 B9 l) o$ T
    dtype: bool- ?% \" Z, \; ]# x" O, w
    1
    4 {0 @. k  J% F2
    / @6 I7 k9 X. w  C, J3
    1 r7 \, N/ ~' r7 {1 k. C6 {4% x9 s& N% G2 Y2 z- |: }% u
    5( r) y! R& m. J" z
    6# o  f  G6 {7 @7 @  U8 p
    7
    / \/ R! g9 _: ?. ystr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:" g1 a9 r( {; \; K, h; V* _
    s = pd.Series(['This is an apple. That is not an apple.'])+ S% Z- _  f+ s1 w% `! a8 c) f
    1 }) R" ]$ E7 a4 a" v; m+ G
    s.str.find('apple'); ~* ^2 J8 v* d+ n" S3 g" I
    Out[62]: ) i+ B* w" }/ e4 D1 ~4 F
    0    11/ s3 y2 H* w2 y6 f! A
    dtype: int64
    + }9 s" {4 ?+ ~& l% N
    1 Y2 }$ W5 ]  u( @s.str.rfind('apple'). [6 T4 C7 z. \( i" J: G! U
    Out[63]: + h. X! D. b4 v, D# w% M
    0    339 H6 c& ?' U8 ^( O1 o
    dtype: int64
    3 b4 V1 s3 W! f* E1
    ! k$ J9 g; `5 ]: L9 x2" _1 R! O1 {4 ]( Y' B
    32 g! S. t, w) v0 p* K
    4, j; ^$ a4 f4 A/ R" q+ l1 p3 @( I8 M
    5
    0 z  b2 T( r& E& E! C- d/ @62 M. u* f9 s) ~/ o& x
    7. c; X* o/ @+ n: J7 I+ Y  q
    8* N" M& Y# X5 T7 g
    99 z7 \* o4 b! u# L% A4 a
    10. `. r8 Y: B1 I. E
    11
    * L! J2 `' R6 `, k3 g替换
    8 E9 @6 K# |& H- g* {& A* t* Pstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。  |2 y3 H) e0 t0 h* i  w" n$ ^
    s = pd.Series(['a_1_b','c_?'])
    0 ~' ?2 ]% o8 Z9 Q+ T/ q# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?; C" o, E7 ~3 v4 f
    s.str.replace('\d|\?', 'new', regex=True) 1 g) S" C) V8 l9 D

    / n9 |3 o) o  P2 J1 y" p0    a_new_b: u# n0 E  c) a
    1      c_new
    & A" q1 _9 t# E' ddtype: object
    2 \2 I6 K2 `. c" T4 W7 I/ E. Z, G* v1
    8 U% P" Q' A- {; ?9 h9 b3 u/ |2
    6 H+ e- J6 N$ N  q3 I/ \9 `3/ u' C7 o' Q$ `. Z8 N
    4+ {! f8 e9 c6 l/ `( S; }
    5
    9 A) \! U4 S% a! }66 ^, p' N' u# k
    75 D% h; Y0 T4 P; F0 A  [% b
      当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    1 N4 s( K: W7 s) J
    ' v: g5 `: B' J- o7 u, H1 Cs = pd.Series(['上海市黄浦区方浜中路249号',
    & B- p- T7 V, X# R                '上海市宝山区密山路5号',
    3 y3 v5 g1 U: m3 F) i                '北京市昌平区北农路2号'])9 i: o+ X* }9 `' |
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'* y) V/ G7 R, e: V( b6 i- f
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}3 G6 t- k' `3 q8 A
    district = {'昌平区': 'CP District',
    2 z8 b1 O" r$ J8 n( T            '黄浦区': 'HP District',- z" u. U# a; a
                '宝山区': 'BS District'}) [3 T5 [8 k( J# j4 X
    road = {'方浜中路': 'Mid Fangbin Road',* W1 d- g1 z1 k0 h& P
            '密山路': 'Mishan Road',
    , B) N3 a9 E+ f        '北农路': 'Beinong Road'}
    * T1 q5 j( y; H; z) a, i& M: adef my_func(m):& H$ n8 f( V' \# K
        str_city = city[m.group(1)]! g3 A8 K) x* Y6 B1 h
        str_district = district[m.group(2)]
    5 g- h, ~0 Q0 I* Y  ]) n    str_road = road[m.group(3)]$ M! \  y8 s. g
        str_no = 'No. ' + m.group(4)[:-1]+ N' t9 N- c, B3 F" I& i
        return ' '.join([str_city,
    , _7 r4 g, {) ?                     str_district,7 K/ i0 A/ `% }8 _
                         str_road,
    ) o/ z1 W& T$ g+ _! l) d                     str_no])# I. Y7 q6 E. z! R, v0 k# _+ G5 f$ R
    s.str.replace(pat, my_func, regex=True)
      K/ j8 M; H) e# p! |- F4 I$ v9 z, I, q5 b! l& Z1 S
    1
    2 z: A7 P: V8 r# a0 k8 y* Q6 W$ K2& b5 Z4 v, \- Z+ D
    3: z" p# \, l0 b0 x
    46 ]- p  e) m9 ^% O4 @5 n7 T/ [( @
    5
    9 ?8 R2 P1 A+ }$ V/ r( z  b. |6& M7 x8 q: G1 \8 }$ h5 r' f: N
    7
    / y0 N2 f. z1 _8
    / Q7 S- h  r- B5 S" p  d9
    7 x! q  T, ^7 P* \2 d6 H10+ v& ~6 [* I4 I0 Q" _/ q5 x8 m5 [
    11! Q. E# Z& P5 b! M7 k8 p
    122 K* K( Z$ T; h# w) \& u% a
    13* t7 R: |" S2 }# A0 p' f1 K
    14
    & ^- d  t4 q3 r' y" M15
      A. J0 A2 |; ?, @0 l; g16. n/ Q+ c+ p% c/ f3 a
    17
    ; x# T5 `" t$ s( ?# ]  x18
    $ |* M# |% t0 P- E2 x19
    3 j# O- z$ ^7 r# Q$ ~& Y20) K  ^1 A, j: p! m. C* S& L. z
    21
    . h! y/ p2 v* t/ g0    Shanghai HP District Mid Fangbin Road No. 2497 R/ a% D. w  O& ]% e7 F
    1           Shanghai BS District Mishan Road No. 5
    1 j% i  ?, q' J( x8 n0 N/ N2           Beijing CP District Beinong Road No. 20 p' E5 b+ T& r  k
    dtype: object" Q! o9 i4 u& G/ r
    1' h2 L* [$ B- U. U, }
    2
    " v5 r' o" j0 W5 t. [% R31 o* Q3 o1 ^! ]! s% F% F* K
    4
    & L% S& T+ A9 [/ O8 V& z这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:
    6 o6 v' l; w" L4 I$ B2 Q  e/ `9 i+ R! S
    # 将各个子组进行命名
    ) o; K/ h+ @, n7 c9 Cpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    ' W3 q, p2 }6 U( J4 ~def my_func(m):
    . I- u& H$ ?3 H0 n6 g    str_city = city[m.group('市名')]
    ' k/ V# p9 o" e5 X0 E" {. k1 |    str_district = district[m.group('区名')]
    9 U: |9 ~$ w2 H    str_road = road[m.group('路名')]
    3 b6 j7 n" Q# O0 @    str_no = 'No. ' + m.group('编号')[:-1]
    / y+ g( x% {& E    return ' '.join([str_city,
    , C3 z* p# s- s9 V. R5 i0 l) \                     str_district,* S. U9 P: N+ Y* K1 H9 E! M2 Z
                         str_road,% J  h6 u" N% L7 J
                         str_no])
      g6 K9 y1 `: |s.str.replace(pat, my_func, regex=True)
    0 f9 l5 g  `- }. T+ U1# D% ~9 B$ h1 D# m1 R1 [
    2# m( O0 _% [: S/ z. P/ W
    3* e' O: I. P; L6 a' b
    4
    % v7 \+ J: x# q# a# E' |5
    , w2 F# a1 c8 u: N4 A, W. W2 v6
    9 L1 m7 \9 l4 z- b- r: d0 M7
    % t7 N# K" ~+ b$ b2 V4 {1 B8  v+ k4 q* r$ F$ n& i* h/ l
    9
    % C) Z" j; P$ @5 B10
    8 N: u' g6 d" V5 I11
    5 `6 X% N& e. u, c12
    , Z7 T" w7 U3 d2 a0 t6 q7 ]9 w) ^0    Shanghai HP District Mid Fangbin Road No. 249  l$ k2 m. G$ g5 Q) h2 v& w8 g
    1           Shanghai BS District Mishan Road No. 5
    & W" }2 @: k$ K: Z3 j; L7 a2           Beijing CP District Beinong Road No. 2
    ( u. W8 o7 m( ~dtype: object
    : G  J  F) A5 Y! u' l14 M% D/ Y+ a7 @$ C6 ~7 c3 L/ R5 r3 J" v
    2( h8 F" l' ?' p& _
    3  k$ P) V" A- W5 l" M9 F% S
    4) R2 U: N4 T% ]' l
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。" Q1 M; g0 Y. ?$ F

    $ k5 O8 b* y7 r: {  H8 C, u# q4 S4 Z8.3.5 提取' J3 z4 x* B  d- j
    str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:/ X0 n& P- ?/ s/ {
    s.str.split('[市区路]')' Y% `/ p9 f7 ]; I) w
    Out[43]:
    : `8 u! i+ @1 G+ T6 Y" j0    [上海, 黄浦, 方浜中, 249号]4 |; H  y$ o1 z8 m" t' v$ o) i
    1       [上海, 宝山, 密山, 5号], Z3 f; x$ |& U2 G: ^: y0 b0 J
    dtype: object+ o& e) |) K0 w% v: z7 P
    % T( H7 V2 o$ W7 b& K! t
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    & b& f0 |3 D) y, Z3 }8 Us.str.extract(pat)
    & |6 o8 O" i4 K5 y4 cOut[78]:! X, v+ ^9 L! ]
        0    1     2     38 C  f9 m/ s5 H6 b& f2 @/ B) G7 t
    0  上海市  黄浦区  方浜中路  249号3 R* ?+ t( X3 W- H
    1  上海市  宝山区   密山路    5号
    ! z0 `" ^7 B: e0 \4 r3 E2  北京市  昌平区   北农路    2号, Z9 S% j& u: P  y; ], j4 s3 b
    1. h: I0 a6 G" w' x. M
    2
    ! X5 ]. ^5 W& Y" X! i31 q' k8 [( X# t1 B- u
    4" r  g/ F) l; |4 M- M
    56 T! j1 c: h! ]. T( D: w- J% B
    64 v# E/ r! \( k. m; A' X: T$ P
    76 f; ]: ^4 k' v0 G6 h
    8( L4 r0 F( J! R, l- C) f
    9
    5 l) l: N) s" G109 s0 _7 L* I& }/ o7 u1 x& i
    11
    / R: a7 @6 i# [. [. d0 p12
    2 Q3 n( y4 U& n5 k13: x6 G) }( Y2 I) f6 }
    通过子组的命名,可以直接对新生成DataFrame的列命名:% ?7 n5 ^( I! v4 {3 N: x! w+ S
    2 o) N( {" h. S5 [6 B
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'% X# Y: Q7 m/ G8 P+ h1 _
    s.str.extract(pat)7 |+ g8 _) J" o3 {3 Y1 @; X$ ]
    Out[79]: + q- H% s# A$ a
        市名   区名    路名    编号; i# ]* j9 f. I1 [5 Z/ d9 w& T
    0  上海市  黄浦区  方浜中路  249号6 c* P7 ~3 w( V/ M$ Q3 C3 V, Z$ T
    1  上海市  宝山区   密山路    5号, v7 }) G$ j8 p2 M; q
    2  北京市  昌平区   北农路    2号  r0 `. t6 ?: {9 i. g
    1
    9 Y* J! y6 o0 P2 n* o+ I4 F; ~# {, u4 ^2+ y$ X) ?2 S2 G
    3
    : o7 R  p3 o6 Q& b" E4
    # \# E( j2 f0 n! D8 \/ L- U' z+ O5( ?3 B9 z$ M7 V! b+ D
    6$ ]9 t( Q3 I% s  W+ b) `
    79 A" b0 u' _, Z7 V& g' ~0 c
    str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
    1 |9 h3 r, d$ f" S& \: s" N- ~s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B']): a3 p& j# ]5 `
    pat = '[A|B](\d+)[T|S](\d+)'
    7 Y0 u2 `& g( U! W0 m. Bs.str.extractall(pat)  S" C  p  J. m5 x$ u
    Out[83]:* Y2 N! c5 n  h/ [6 ], B* G( p" ?
           0   14 n( ]0 K8 y  k  y+ [* K" F
         match         
    . v) b' B: d) R+ V% m/ Imy_A 0      135  15+ A* ^; X: P& u. b8 Z
         1       26   51 C+ D1 X2 w# A9 V
    my_B 0      674   2
    $ u  f- I  P9 d  R7 t     1       25   64 {5 Y7 e3 A2 S; j6 S4 t
    17 w, V8 e+ u  Z- a' [
    2
    3 N4 h! _% O" ~5 M0 X3/ q: G+ T. q2 q% G: |3 A1 Q5 `
    4
    1 v" m: P7 g- D3 S* A( \5
      d5 V; i$ u" i% U) s6- o" K) P/ {# }* F
    7
    - z' x+ I- e' y82 b6 j  Y2 ^5 J3 A( |$ M. o. w
    9
    * H  Y- C4 j8 E4 e# A0 }10+ O4 N& D- t. h* i
    pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    + ?! I* I" b/ ]$ Ds.str.extractall(pat_with_name)/ g8 S; k6 X* B5 ], D
    Out[84]:
    / @& M8 h, d0 c/ G           name1 name2
    : r; M1 b6 Z6 `* W     match            8 V  b, n- e7 v' s( q+ _
    my_A 0       135    15
    1 n, E" P6 J# O     1        26     5
    0 k) D1 Y1 L" U8 L4 X( tmy_B 0       674     2
    7 ?( T4 @" j1 \, w     1        25     6
    6 M" I! [0 f9 u9 L! h- B. C! V) y+ P1  a* D4 a1 ^+ }
    2
    ; `4 {# p) j, R8 Z4 m3
    ' I$ i$ Q0 f5 a; x8 O5 H7 P3 T5 b: x/ {4- Q7 c# l% R$ t. n
    5
    5 Y4 t  `  j* j# ]7 x' M( \6# B* j* _% `3 a% ^
    7: |% ~6 K4 [! {
    8" y  B+ F$ C  C# U2 o
    9
    ! g8 U& d  o: Q$ o% U- W2 U* o4 `str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
    2 m/ K6 E8 G( \/ P" v" n! Fs.str.findall(pat): {  ?8 I# Y4 |6 X0 h
    16 @  ^/ a5 t. E& D
    my_A    [(135, 15), (26, 5)]& B- e' j0 b0 s# P% {  Z
    my_B     [(674, 2), (25, 6)]
    . Q- u; b! b" |* q+ odtype: object0 y) C. u+ E) T$ c: l) W
    1
    7 p4 V# |: X: K$ }. J2
    , I% X" `  ]+ o( d# U# @; E  x5 j31 Z% N7 [, y+ L$ a
    8.4、常用字符串函数
    + o) q1 R. L# Y7 X9 O3 n  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。5 K7 j, C! v* s

    " U3 T+ ?! k3 \# k0 S6 N  y8.4.1 字母型函数! R4 Q. y9 x* t7 z& d
      upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:0 z# V" C( F2 H9 }. u
    9 ~* {8 p. I. A. w% P; L5 d( [1 g
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
      R$ D/ I4 a+ q( e
    6 ]3 {- E0 [# y' b+ Y) ]0 {s.str.upper()1 f* ]  b7 ~3 Q, v/ v( T$ F3 F" M
    Out[87]: % x/ Z! I1 c  \* K2 W) w3 n
    0                 LOWER1 p. V8 z5 o6 k$ I
    1              CAPITALS5 ~2 b4 W3 B) O- _2 M* O
    2    THIS IS A SENTENCE
    , B" p7 f' ~- ?% I. b9 C7 A8 w3              SWAPCASE
    0 \4 `- v5 t) _! L$ I" I. v# z2 Xdtype: object
    7 b$ O8 S8 ?1 z% e" r' O
    & o# v( B+ z- }' P4 x, I4 x% es.str.lower()
    2 |: n) B) O! K( s& d  q1 L/ W( }Out[88]: & W. H4 |/ Y$ @" K7 \+ ~
    0                 lower
    3 G. G# n# }- J! L% d) {* Q1              capitals
    3 p. G$ [) w  q# h) u- @2    this is a sentence1 \% s* j4 k* a; I8 V  F. u  p
    3              swapcase) h# N9 C- B' v5 B
    dtype: object" P* @* M2 N3 J0 A, V
    + p1 ~/ I4 V( [, V$ l
    s.str.title()  # 首字母大写
    % G' U9 O3 X5 g6 J, C7 aOut[89]: ! O1 v& L  u  N7 C9 J. x" `  A1 J
    0                 Lower0 ^7 C8 J' r+ m- s* g; r; i) u5 F3 p
    1              Capitals
    & q+ t" X* h# ~% G: u2    This Is A Sentence# v! j* N" i  ~0 @5 E$ z
    3              Swapcase
    2 A5 |0 M7 I% k& m& x( j" J2 V$ K- zdtype: object
    2 ~$ l7 ]7 N2 f# g5 J
    2 M1 q/ h6 ~# C3 A9 i) F" [( T7 Rs.str.capitalize()  # 句首大写: b  i; }) q; P
    Out[90]:
    9 |9 i# ^# H# w1 p. V& j/ Z0                 Lower% ~+ _: n6 }# A
    1              Capitals
    " ]; P! `5 u7 u+ n3 l# |2    This is a sentence6 ^; D# Y1 l7 ~' H! d
    3              Swapcase
      L' H5 q' O, w# _dtype: object
    " N$ Q# ~  d7 d' W2 O: l/ i7 h; N& M' Q9 }' v) r
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。2 @5 P0 o: {  |9 J) D; |) D
    Out[91]: 2 _: ^0 o. X7 R* u
    0                 LOWER# A% M2 [3 M! n1 {+ c. q2 l
    1              capitals
      v4 G5 k5 Q' j7 w- T2    THIS IS A SENTENCE- T. \4 f! V1 o5 m9 r
    3              sWaPcAsE
    0 S3 v/ k4 f: f1 Qdtype: object
    ! ^) L& R/ e1 @& f4 F" A. h0 A+ p4 N- Y" S; q2 f
    s.str.casefold()  # 去除字符串中所有大小写区别/ @- p2 _, v$ x0 f2 R' \( E5 k

    - I: R& N! N/ R% v% Y+ ~0                 lower
    - ?7 _% P1 A: b, G9 c% v' H1              capitals& i; h; p4 F  i( l! f4 I. j2 U
    2    this is a sentence
    1 o/ q* q  s0 r3              swapcase, {* T& s- l3 Z4 ^' Y; t/ g
    ' y& P( I# P6 X8 H! c
    1! i1 B# z" r$ f6 A
    27 H4 s) ?2 k! O9 d( v4 [
    3
    : z+ H& [, b, h  b, \& W4. ~2 O  w( W" B' o
    53 W$ u& l$ \! M& ^
    6+ ^2 O9 o/ Y4 b: C
    7% H( S0 ~' o9 d' W) x
    8
    5 {' E9 i1 x: K: u' Z1 V  E8 [( U% N97 {+ l! w2 @" z; K/ j1 p
    10
    ! l2 \1 Q& ~8 ?4 h11! U* _  V, a. ^5 t: J
    12# L. d, G+ `  g6 }6 J
    13& n3 N, X( @# e( b
    14
    ) R# @% n6 Q1 \15
    - {5 H1 X0 `3 R- C0 W16/ \! V  m! d; g
    17
    % P8 ^. J1 v3 k2 m5 h18
    - O+ K7 U3 m" o  X2 Z19* p% q( A% ]4 l$ z4 @
    20
    . H1 \7 Y" t  q4 T9 }& N( Z21
    . R: ~& J  k8 M6 C22
    % z4 n' n/ y+ I9 `: \! e& f23. a4 L6 I5 a6 W! W3 C
    24( h! ?. m- U  Y/ E" y* K
    25: A2 Q% I' M5 H' \
    268 m8 {5 Q1 T" f2 w/ b
    27- m, w' d/ E, ]. s. K; Z* p
    28
    5 V+ A; W' G, ?) h" q+ V1 Q29+ H1 g  J( H( U3 C* ^0 U
    30  ~0 }& @0 R6 q( P! _! A
    31
    : b$ |2 }" H! C. n! O32- p& a5 M1 ]  ~# a3 m+ {$ O2 T
    33
    9 n; W! G- w( h3 `. Y. n  l34# h+ Y9 E4 t) l# z" P! n+ ]
    35
    : e2 A; t! b7 `; V) P' Y2 G0 M361 g  `5 ^! S# M7 \. F8 M
    37/ V" H3 z1 ^9 |0 h
    38# Z' B  a; L* C3 d. Y
    39
    ) K) i0 ~* K  t$ j# f1 U% j& o40
    - `4 Y9 V) m! }! W+ w7 [41
    . `5 h: _9 w4 b/ s" @7 ~42
    4 C# f% p7 g. ~8 W; u6 |43  @. H. P% r* H1 q
    444 `! Z" m9 K% U! H' O0 s
    45* W/ `7 @# {+ U! x. U8 ]6 z% A
    46. v2 k$ v) U: s& W, T
    47
    ; w1 J) Y; G8 D/ z) z48
    ( P9 [1 l  e( S( i, b0 L# t+ |8.4.2 数值型函数
    , h. A9 B1 q6 `! ~) a: P& J  这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:5 B) t( }  S+ Z) u

    : G' c" p) S2 g/ K! R0 P! T, yerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:+ z, B& h: F8 C$ O) e$ \
    raise:直接报错,默认选项! c5 H, a7 Z% L2 ^4 i5 y+ a5 `5 N) k
    coerce:设为缺失值
    9 d' B: u$ m5 r+ h# A. Hignore:保持原来的字符串。3 P$ s' [6 M# \; X8 z& O
    downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
    & Q& r; c2 @" b" Y& Q9 O2 ws = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    8 v: z7 _. T, ]$ k
    : b3 a6 Z4 L/ \! Xpd.to_numeric(s, errors='ignore')
    , e, W' J5 T" s2 YOut[93]:
    ( T4 k3 D1 S$ ?0 G0       1: o4 X6 R$ z, _! r8 z3 j. `0 F3 N
    1     2.2
    0 d* d" a$ U0 M& o+ D7 B; @2      2e% T  k/ R  d/ b5 |, q" W( U% p
    3      ??
    4 {2 q- R1 t/ z7 g4 q  d4    -2.1
    / _* \* i0 Y) Q' N5       0' H( [: H+ I1 R9 f# {
    dtype: object
    $ R5 H+ n( S4 l  W+ d9 H
    * p: n7 r& @7 ~- s$ p7 D: k; Hpd.to_numeric(s, errors='coerce')5 z4 x/ q/ P) n0 w2 Z) w
    Out[94]: $ i, H8 g5 Z( _% U$ b
    0    1.0
    3 M2 P* p! r% t  g# |( u. x( p1    2.2
    4 I. j) A: {9 `2    NaN" z5 D$ `2 \& Z) L; @: y
    3    NaN+ _" i: b0 y  g. F, W& ?2 [# C
    4   -2.1' i$ @" k2 g3 g
    5    0.0( [8 E* O) I. k! \
    dtype: float64
    9 A$ S; ?! b- ^/ u# m
    7 a* _3 U3 O# o# P: F% I11 T, }& g" [. c# K: M
    2& R9 G' B5 M* J, l+ q3 ]
    3
    $ Q% _# B" x+ c9 c; z2 ~. z& G7 H4
    5 [$ O/ s( S1 t5 ?( j2 m5 s5" L( R, ?0 T; x: e
    6
    / f% ^; w+ k$ {0 n74 S/ X& s& Z. D
    8
    3 B: f7 M2 K  ?6 ~& V9- M+ R% l8 I( }& M
    101 f# i6 t/ V0 I( H& w) \
    11
    3 L9 b+ N8 q! l1 Y& p1 _! ^% I12" s, c: E% e/ _5 B+ \3 r7 [
    13' g3 }0 M1 Z* R" i+ d
    14: L6 t+ X# p8 M; h; H4 m- g
    15
    7 ~' k4 Q% b( _3 W16
      {, C: Q( b/ _8 |3 E" E17
    / \* x7 e2 f. g& c18+ j+ f  l( \$ B
    19; I0 O- h% f* {
    20
    : @' {5 j$ M* r4 p: G2 n21- Y7 P% _% e8 D& }" R8 H
      在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
      z- t* g1 N+ V3 g. x) s# j7 I0 ?
    ! t8 g! x* Y! ?s[pd.to_numeric(s, errors='coerce').isna()]. l* D( c" Y! ^! n  c
    Out[95]: ; `- \! I4 d8 N% a/ c9 b
    2    2e
    2 r5 l( ?" ]( T! N9 g1 R" k3    ??$ s2 R6 A- q  x& Y) o6 [* P5 q9 {
    dtype: object; G  B! l5 P, G, E) @' a
    1( d& |7 L5 Z6 N0 ?3 ~
    2$ c3 T8 \( G. W# z' e
    3
    + h9 D' K- |" y. a4
      d3 `! m- H! f5
    + \6 h/ a7 u( e6 F5 G8.4.3 统计型函数
    - r9 a- X! d( [9 v$ X) O6 ~  ?  count和len的作用分别是返回出现正则模式的次数和字符串的长度:
    ( D/ C0 n3 Z3 Q7 g# X
    4 l# I8 V5 \4 w7 Q) {) `s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
    - w# B) v2 [+ o0 S; ]1 g+ s6 ]7 h" M) ~
    s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次& S8 t, e$ b6 o+ Z2 F
    Out[97]: 0 \) S1 ]2 O: {4 p3 E/ V6 b+ R
    0    2% D  g, G5 F! J0 E0 _1 w0 C
    1    2$ ~$ x6 A) R6 q" r; J
    dtype: int64
    + \. c$ R" Y3 ?; \1 I7 P5 k3 _' L' c# c
    s.str.len()
    5 e8 N8 y6 J) _4 Y) Z' F! O, }Out[98]:
    4 ]: R9 z, E' m) L0    14
    1 z, T9 C5 o( j( ^7 B' e+ y( t1    19! f4 b( Z, I& ~' S
    dtype: int64
    5 O0 }; f- d( r4 e1( {) c3 H4 k. I' Y
    25 t5 Z4 W1 ?2 u6 d$ y% x! [
    3# E" z7 o% ^( ^% D8 y7 U
    4
    6 y" M' j' ^  f0 C. R4 R5
    % h) X+ S+ h, c2 G67 v$ ?9 }+ r2 V9 F0 g$ u
    7+ C8 w" J3 D4 J
    87 }3 E! L+ y7 ^% Y' e5 J6 `
    9
    % u* X" [. @" q9 c1 Z% W( L10+ W7 k7 ~5 n+ v6 T( O3 A0 [6 d
    112 h! m2 |4 H  `( O% m% w) L
    12, ]" p- p5 d3 d. @8 ^, H0 f
    13
    9 H8 j0 X: R8 n% ^3 ^8.4.4 格式型函数
    9 O* K* c! ^- A, h3 V3 j0 z  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
    $ p4 K2 W( W/ o8 P
    3 A: |: l4 f! ?my_index = pd.Index([' col1', 'col2 ', ' col3 '])
    2 M- |# k( Z1 y& v4 M* p: L' j5 h& h& k* p" @4 x5 r, S$ B
    my_index.str.strip().str.len()
    ! f5 [+ e3 i! Y2 j& r; jOut[100]: Int64Index([4, 4, 4], dtype='int64')6 m# {0 I. }+ i; s; N% L

    $ ?) p) @1 E8 [7 \my_index.str.rstrip().str.len()# U5 m1 H1 V% h- p
    Out[101]: Int64Index([5, 4, 5], dtype='int64')6 H: b' y9 b( I. x6 |9 [

    ! ^9 ]$ M& K5 L3 _. b4 G3 ymy_index.str.lstrip().str.len()0 m/ l. z2 P' f4 r8 E" W
    Out[102]: Int64Index([4, 5, 5], dtype='int64')! P6 Q" f: S3 a, S  H
    1
    : R8 V7 Q. Y9 P' }% ^2+ h: y0 x8 y4 n
    3; _( l% m1 Z( R" [) u
    4
    0 ^8 Y9 B4 T* F1 h1 ?( w) ~: w/ T# b0 k55 d& E; f$ p" Z3 Z, ^
    6
    ; i9 i! k# R' W* j% C7+ C5 ?7 ~; t8 ~! @) Q6 P
    87 w4 x' w) h  r$ T
    9( k7 `: [" P. r: j  B5 F7 X3 T1 r
    10
    6 X" _6 Y& r$ \  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
    6 ?) }# P1 U& L" F. X& \: a; g. z: J( w$ h% W; V6 ^) f2 w/ s! s
    s = pd.Series(['a','b','c'])5 ]  J* p! u  ^1 X; h4 G- |

    4 Y( B5 H2 o$ \4 Y: b2 Us.str.pad(5,'left','*')& e0 @1 v5 E% v, a8 B* B
    Out[104]:
    - h' o" N: \$ z& p; R" O0    ****a
    9 x' d" ^) z+ z- X1    ****b; G: k0 s& y" q( W, ^# N; a
    2    ****c
    % C  R" o" T3 \8 Vdtype: object
    8 [( j9 D) ?. J% e8 \) }8 f! Y7 ?: R
    s.str.pad(5,'right','*')' j% G+ E7 w& V  x8 X# ^+ @3 E# q
    Out[105]: : d, }4 `; B- |
    0    a****
    * P( V& ]- k9 h' d# M- z1    b****
    ' V) C* J- Q2 S" S, l! ?2    c****
      Q+ ]0 c7 U0 V/ t% _7 L# b  vdtype: object
    % A' t: g7 y' h" h! \, C9 B. \
    9 I3 k- F; N$ k2 }. `s.str.pad(5,'both','*')  f' _/ N9 u" w- g/ r* K. l3 L
    Out[106]: 4 p+ ]: m! j, d" X
    0    **a**
    6 D( f+ x0 u/ y1 j( w1    **b**9 t1 z' c# u! ]; B
    2    **c**) d; g* H# q# y1 o! T" N; W
    dtype: object. m) I2 R6 e% t$ L% t

    : R9 r/ r, _, T3 i/ b/ l17 o- u2 e) e! H  I* }
    2" o" }" {% {1 x. g
    3( I# f/ o+ |( c- ]1 F/ l
    4- \* v. u) W& r% n& D; O/ E+ ?
    5
    2 k, Q& T$ j0 s6
    0 z' G' K% N% K% q2 z7
    $ D! Z/ A% D/ [- ~- J8) x. P& o$ E; @) u
    9
    ! [" P2 A7 z9 {/ t! k10
    * Z8 `/ d7 k4 P, B11
    # r2 }; O9 T/ x# G12
    ) _# g  L- @+ h" u$ ~" r7 |13/ t4 j* {$ W1 ?! K9 `
    14$ g/ K7 I4 b6 C2 e% ?
    15
    - H4 Y: a! n: _/ @9 @, d6 p16: I/ T7 E/ v' C/ b: P
    174 H& |) W$ Z0 q; d4 c0 k/ g
    18
    0 e. d( \1 g+ ^0 b/ |" R; D$ [19
    # t: k' n4 S" s- V$ s. R+ p) B20( x5 o7 {9 W, Q3 }- B
    21; ?+ K6 v+ g1 v, }, U/ D8 a, X3 T3 {
    22
    ' g" {4 A  c) D  B0 X' {  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
    . U# m; t( r( M% ]' ^; f/ r# j- l  {! D: \
    s.str.rjust(5, '*')
    # K! j& K; X6 R% t3 K1 S& nOut[107]: % E) J5 |6 p3 r% U2 b* B8 z$ x
    0    ****a! e& g! ?0 t+ `6 m/ B4 H
    1    ****b
    8 \- S5 H) k6 {1 o; h! x2    ****c* k, O( Y2 i% }. X* ]2 j- a+ r
    dtype: object; t0 R. F8 k# g) D, K0 p
    " W/ D% g: K" c  q( O5 I3 Z" l
    s.str.ljust(5, '*')
    & B% Y# ^6 r! j4 q; ]; bOut[108]: " `8 B$ Z/ ]9 u8 k
    0    a****
      ], }4 B4 [% ^+ W  R( @$ y1    b****
    % k5 f9 d4 a. U! U& n  Q2    c****3 L  v+ ?; R% ^4 ]4 y- z
    dtype: object
    6 Q: R  g9 G) i# m; R$ x# |+ g' }0 w: Z9 i; j# Z% d
    s.str.center(5, '*')1 y/ N% m" e1 }
    Out[109]: ! `) o; I  z  p( }4 a/ p
    0    **a**8 W0 S  J4 @- e9 @& z8 p) b
    1    **b**6 L5 Q, E  F1 V) Y0 \( \0 l+ a
    2    **c**
    4 e  _  {1 b! l8 P) b7 wdtype: object0 d/ I. C/ X4 ?

    ; q" W  @& f4 W& T, f1  Z* G* e3 U; |8 W- v3 E- s
    28 k, O  l( D- k+ X  A& {0 O. I
    3
    8 P% K: \, ?, a0 P+ J7 u4& ^0 [! k. N6 l  Y9 n# p
    5
    & O3 [; {  U; ?1 u3 i6
    7 D9 f: z" o/ A; X: h6 G70 g& \  ?4 D7 W' ^! e/ D/ \. H
    8) @6 z. H& a8 G  V& w+ \
    9
    % a9 L$ V& h1 n% F10
    " j5 W1 O6 p; P# j! N11# h+ H- y: S% t2 |, t6 i
    12
    - \1 Y9 @3 R, K/ e0 V! u2 _  w- I. [130 m3 }$ X% {6 H# K9 \
    14
    " r9 |$ n, n! I# R7 G- Q# j15* T; K7 W8 f9 z: M: N
    16
    . R' \5 P; J7 y. W170 B8 T7 J$ K7 h0 @
    18
    $ Y" h7 i5 C7 _1 K) z19. U# }, R' [3 t1 K- r& V
    20
    1 s  x& \- X$ m/ v% e  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。: [1 F% ]2 c* Q
    % a' k. n% q* O9 n
    s = pd.Series([7, 155, 303000]).astype('string')/ o. J& I, w- r5 Z8 H( G

    1 H: b2 u9 X1 |, o, M6 Es.str.pad(6,'left','0')& a+ O) T" N% q2 h5 ?  o1 P
    Out[111]: ' h4 f  B! K% e) W; p
    0    000007
    7 m' d' I& J. h6 Q' n1    0001550 Y( h) b& R8 a. j
    2    3030005 L7 w5 Y/ S  \* C( K
    dtype: string
    ! C# B8 ~( L( t8 f6 n9 B2 d' P9 A0 D6 U9 Z, m7 z: e% N
    s.str.rjust(6,'0')6 Z$ C  b$ P5 p: ?! h
    Out[112]: . `1 N8 m& f% b" l( }- Q8 I1 z
    0    000007
    ; y5 W2 ^$ l2 y) W0 W3 Y7 d: F1    000155
    ; D8 I" H  C) Q1 V' @3 ]) q" V7 Q2    303000
    3 m6 V( P$ }( Ddtype: string
    5 Y3 C* P8 P, G( H' x8 e" T
    , T8 B7 w; C, u" c: As.str.zfill(6)5 A+ s. X, g- u0 K) |( s
    Out[113]: ; V9 q% |- m& ^7 ?  }0 J$ Y6 ~9 {
    0    000007: D, v% S5 e& A7 u+ ]
    1    000155
    1 {1 D% R4 Q% W% R7 z8 v* c1 M2    303000- d, O) N+ w5 u, _5 _% s) ^
    dtype: string
    5 R8 S% Z6 ~; d2 v% {7 V6 m, l% ?1 F) }' }8 F( e
    1
    & X- Q. U# ^+ d) [. U* K/ r9 m. `2 ^! m28 x# v8 w% Z2 R: M# v
    3
    7 ]; b% M4 v8 Y4 [4$ |+ F' @# N  C5 M$ {
    5. e) L+ l6 Q- i! m6 U8 m3 {) t3 c
    6* @5 S9 k; v- w: r5 k* M
    7" F! E, O: Y  z+ Z. [/ c
    8# |4 b8 x4 d5 J- d3 }. o
    92 e/ j2 P/ T4 Z. \. z" i
    10
    . G& @/ g3 @% t; w0 ~4 t11! D2 [3 l) {2 P4 `$ T
    12
      m# e; |- T7 o$ ~5 N; J4 N% }- p13
    ' J  Z* p4 C* A" D/ w14
    5 C, \# w; m) @" F3 F+ y" s; W; z15: d" M" h; ^0 T- {
    16
    ! W# [* v: F6 E% N# E! c179 w# f) @9 [6 j. B  T& x% G" i7 g5 m
    18! p( j: |4 A  |, a( _
    19
    1 `9 j2 C$ p' e. E1 W4 W20. w2 Z* N/ J& O4 g% q/ v! `
    21
    % S! C9 O+ |# K" I( g1 Y4 W8 I229 ]+ h1 k8 o4 n% S4 O- j& Y
    8.5 练习
    & K5 l/ w* \' fEx1:房屋信息数据集: |& k# b- W/ }+ N+ }9 K! H
    现有一份房屋信息数据集如下:
    4 u& |# i3 P# l2 v, R( R/ S9 B
    6 e/ ^% C8 x$ y" M: Y5 F! {& ~% fdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])8 i/ A+ G$ v7 I1 {
    df.head(3)/ K1 r5 p  z. x" l* i4 L
    Out[115]: + Y0 m! Z, X2 E
          floor    year    area price& w6 p! j- |; `* d' T
    0   高层(共6层)  1986年建  58.23㎡  155万; N4 E  a: O" }0 m+ Y8 Z' K
    1  中层(共20层)  2020年建     88㎡  155万) }4 o6 {  i6 k# X
    2  低层(共28层)  2010年建  89.33㎡  365万0 T- o* V, z7 o; _, v
    16 s& v. A! A4 K
    2# ^( ?7 ~. K1 z  y
    3
    1 s5 N( B! h8 ]. j4. T8 w1 G) Q( Y+ ~/ R
    5. k0 I- F0 T. n1 Q1 k: k+ e2 @
    6
    9 W% G1 n$ B9 s5 ?7" e6 k+ H! P+ D" s7 k
    将year列改为整数年份存储。' n/ S$ F) H. o0 s2 V
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    5 Y1 a$ D8 T' ~2 |9 n计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
    / x! N8 G$ u' A+ T  F将year列改为整数年份存储。1 |- p/ c# A, ~2 i3 Z' [
    """, |- b" g# r$ ?; `: r/ c5 Q3 O
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
    / f! U* a/ c: _, P2 P+ C注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
    , I3 m+ x. K- s3 V& e$ ]+ I6 U转成int后,序列还有缺失值所以,还是变成了object。6 t% D4 ^# H3 o8 {  s
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。3 `. f6 v) ~( K5 |
    """
    + Q2 j0 J( w" @% z! Sdf = df.convert_dtypes()
    $ K2 g& W. }/ L$ Fdf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
    - q6 j+ I8 [: W0 c5 odf.loc[df.year.notna()]['year'].head()$ ]; a( i, F+ {4 t9 I( h6 ^% k' j
    1 ?* C( s4 K: B; l' i& F
    0        1986
    7 @+ ~! f4 v6 z& \1        2020
    1 l, W9 R5 S0 w% I3 k2        2010
    : Q2 \& I) \) o3        2014
    % P* ~4 O1 m% p) A! O9 E/ P' t4        2015
    / u. Z' r3 ?5 ?' e; z: ^9 qName: year, Length: 12850, dtype: Int64
    * d+ D4 E( j3 D7 _: C
    / p# M% S0 y2 t. @& p1
    * l' T& @) n6 N: F- F( K; |* _" E2 a2
    ; M7 O* a  f# H: D3
    + }- k, H5 D! u* O: Q" B) N$ n4& \$ |) D7 o6 J+ l4 ]& p
    5
    ) u+ F* }' r7 E. G( A6
    7 q# u6 A" Y7 o! R" C  d8 D, l7
    $ T( u+ N% U1 D. ]* r- _8
    ( C3 u5 h  _( _0 Y9
    . p' H7 b  |+ Y6 ?1 `104 s, ^; n- j: e6 ^; K
    11
    - Q8 K! x  o- T* |4 L! W; t  }0 v128 n0 n/ f. s, o; n# Z
    136 I6 G7 Q* B& j" M
    14  g8 o% ~' U/ s9 U
    15
    7 A( t# `6 q- ]6 D; ?( I6 ^& {16
    3 \8 w( M" `( z/ s; Q. }. c: b) ~9 ^参考答案:
    4 q  @- S3 r# y+ s6 H8 Y, g
    7 {2 r# [$ M2 N: D0 w( y0 c不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么. E9 |+ |' j1 R, h2 x! I% }2 \
    ! E# B! a  U8 A1 J* x, V3 f. s; Q
    df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    6 X% h1 b' }0 Odf.loc[df.year.notna()]['year'], v  k7 J2 A8 C/ K+ H* A0 w2 }3 ~  _( j
    1
    # I5 H9 u6 @& [4 b, I20 }+ Y+ z% X4 a3 M' b
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    / A, l; f( E% M1 t9 O+ [  J: zpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'3 c3 @$ h) {$ P4 l. Z
    df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
    ! K# r6 g; D5 _. o% H% odf=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型# X- T# E3 @% n  J6 [* l- ?
    df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
    , c2 w4 ~7 j) e& V( Vdf=df[['Level','Highest','year','area','price']]7 `+ a+ Y# W/ L8 d. @9 F
    df.head()
    " h8 @1 [5 C6 b! F4 o
    9 v8 R: Y8 @1 f4 W5 Y/ y- h4 N   Level  Highest        year        area        price
    , |" L7 c) J- D0        高层                6                1986        58.23㎡        155万5 v2 L+ Y; [# y9 X
    1        中层                20                2020        88㎡        155万
    ' y8 V7 Z7 t/ U. |) \' m% o2        低层                28                2010        89.33㎡        365万
    5 J' F5 w' f7 F8 B! k- A3 ]" [$ D3        低层                20                2014        82㎡        308万% k* W% H* Y. O4 H9 Y7 N) v( Q+ A4 L# ^
    4        高层                1                2015        98㎡        117万5 l/ y' Q& I/ m, o9 P$ X
    1
    & I, Y3 V! D  @2
    " ?2 X+ w2 O, B3 s% m3+ E9 g( t+ |5 l! v8 W2 P2 _' D1 U
    4
    ! n$ a: |3 c2 G2 s  a5
    : B8 Q" s0 {0 l8 d7 d1 n3 s6 L61 S8 g% {0 z# Q4 E# R
    7
    2 k% ^, C) L9 B8: j+ q) f) ~6 o. u( r
    9# T& C3 g6 t% T( s  x7 T
    10
    ' Z* ?1 t! t& |6 L* D1 T' H) M11
    & n' D# q0 @" A0 X: m12
    3 o6 g  w# C: }. o' a13& m8 e$ c5 B  k- a$ v" d6 G
    # 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了/ L* ]% W' A9 {. y
    pat = '(\w层)(共(\d+)层)'2 Z6 B1 y5 Y" ~1 D1 G4 g
    new_cols = df.floor.str.extract(pat).rename(
    ! R! v3 C) [3 y* Q                    columns={0:'Level', 1:'Highest'})
    * C7 p0 ?& }0 M9 C9 ^4 L0 E5 n1 n) r# E
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)' P& l$ @5 |+ `) e( p) t; n
    df.head(3)) v: e/ H& q0 d: e6 [9 `7 W/ N
    1 V. ?; N1 A4 q6 o$ y: G
    Out[163]:
    5 `8 |* e2 r9 x! x( r- d   year    area price    Level Highest2 t0 m' s9 ]( y# B+ D$ B
    0  1986  58.23㎡  155万    高层       6; |% H5 t2 C4 R/ E7 B% p3 B  Y
    1  2020     88㎡  155万    中层      20
    4 T( j, C: f8 v! k; h$ |8 z  |2  2010  89.33㎡  365万    低层      288 n$ `) t  X! R+ v$ l7 V- [
    1
    ; |( Y) g! i! W' b2
    1 c$ n4 |2 Y8 P7 o0 K3& q: _3 G6 s5 H# }5 K0 d. T
    4% h! E: f6 h5 Y# T7 W1 U
    5
    - ]+ h3 [; D) {0 R/ L* G6
    0 R6 L( t8 z. g4 P# f2 ^71 @, [  ~0 E. V3 z" T: Y: ?  z& ^
    8' w" @; V3 K# a) p6 }
    9
    4 t* g! ^* \0 p" r7 c- t- K10% g% ~; D: P0 u$ H  H6 P
    11
    ! ?/ H" f8 Y" z# W: l/ x! s; B126 s8 G& I, l$ k2 p9 o) W) k
    13
    ) Q# {! o* m7 j计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    & o& Q. A$ l  u"""
    8 Y0 K8 C5 f- [5 tstr.findall返回的结果都是列表,只能用apply取值去掉列表形式  ^0 b3 }1 g9 x' H! N) ^- N
    参考答案用pd.to_numeric(df.area.str[:-1])更简洁8 _1 Z9 s4 N% a7 g" S/ _4 _
    由于area和price都没有缺失值,所以可以直接转类型! x! }+ C) I1 S$ e% g
    """4 Y* r" Q; `; H  j  P6 P' v. s
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))/ m# r% ]; S# E+ L
    df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')% U# P3 F: f; a! z% i& ^
    df.eval('avg_price=10000*new_price/new_area',inplace=True)/ n8 Z* D# ?: N3 O6 t) x7 t7 r2 w  t' ^
    # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))$ X5 \  E  ~0 W' w, _
    # 最后数字+元/平米写法更简单* ^  d1 i4 |, j
    df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'4 U8 n! z  ~' O4 Y: a, g+ M( w6 `/ M
    del df['new_area'],df['new_price']
    * U; _/ C+ {: X! `3 K% l$ @df.head()
    ; F) V, m8 ^9 s6 N9 Y' g4 N
    ! O" T$ A. r0 b   Level        Highest        year        area        price        avg_price; E+ K: v# t+ \# z$ j9 l& |3 U
    0        高层                        6        1986        58.23㎡        155万        26618元/平米
    & v5 m# d9 ?% f! p$ a1        中层                        20        2020        88㎡        155万        17613元/平米
    + C, V9 |! C3 b3 T* ^2 Z4 P4 x- |2        低层                        28        2010        89.33㎡        365万        40859元/平米1 p; _. j  d) K3 d: i2 a' K  l( S
    3        低层                        20        2014        82㎡        308万        37560元/平米
    1 m) e5 }* c& u5 b: Z4        高层                        1        2015        98㎡        117万        11938元/平米
    + R' a/ W7 l9 o# q# P2 ~
    * F- ]4 b, w, r( [  x$ _1
    1 ^& c$ m( ^* J, M' x+ a22 c; r: D; T. k% {2 |# ~: M
    3, k2 ?) A1 u. l" p1 Y/ I3 L
    48 D# c" a4 o! l% S5 A
    5
    ( v+ m$ G; R# a; I! F2 E( l6 x6) h" V' I4 _$ P: z* J* f2 j5 ]
    7& F/ `% ^+ D2 m
    8* o- ^; [# o; H( E4 b$ ~3 S: e" ^
    9) u& m7 m6 F4 r; v5 Q0 D
    10
    ) b# P- B5 {! H8 k11
    - O5 U: ]  w% H& v* g  Z* ?* j12. }2 |3 K' i/ K9 e/ N( t
    13
    & g* H3 b( Q3 Z14( V% k9 o% o- w4 H
    15
    * ~6 ^9 ?9 l+ z% x8 r3 j; ~16' H& z' W& ^6 ?8 ?+ P
    17
    + T- I5 c/ \! f3 S+ m18
    ) S4 U' G. c/ G' w  ^191 w) r* Q: |- p. Q6 O. |6 O
    20
    % O% S8 l5 ]8 J1 g6 R5 Y! x" [8 {" s4 Z# 参考答案
    ( N! S' ]: a( c# @8 u# F9 Z+ Ns_area = pd.to_numeric(df.area.str[:-1])
    % B7 L* d5 \& A! \0 H3 L( t, t- M! Ts_price = pd.to_numeric(df.price.str[:-1])% Y" x9 _: y7 I
    df['avg_price'] = ((s_price/s_area)*10000).astype(
    0 V) i8 j! N. K; U+ B3 j                    'int').astype('string') + '元/平米'
    % Q0 D+ |8 w" E# Y2 X9 o5 H( S7 D9 R
    df.head(3): f* p8 h8 A* H  o) G& S' S
    Out[167]: ! L) q& ^1 v( O9 S
       year    area   price   Level Highest  avg_price
    0 G& v6 \' B2 \( w- T5 n0 n0  1986  58.23㎡  155万    高层     6          26618元/平米
    1 L+ ]! R5 ~$ C3 G% ]' V( A1  2020     88㎡  155万    中层     20          17613元/平米
    : _. e" {# D( W! Q) I% {# x2  2010  89.33㎡  365万    低层     28          40859元/平米
    6 s, |$ a* |8 u- x1& J. p4 E' A+ w% C1 A
    2
    $ @( x9 u& r1 g1 v3" D. G8 q' n8 C4 z
    4
    * f/ E2 g+ Y( W% W  m5
    , D1 ?- T: a. P7 B; k2 R) V6
    % G0 z& X2 C* A4 M7% E, q" @, T8 k: \& {# X  f
    8
    1 q. P2 N/ @1 z2 E, A& U  _9' L+ \! w1 y2 Q2 w. O9 `9 @8 t
    10
    ! |! T* `3 I8 f; o: v- O/ D/ ?115 v2 d: E; v$ |' ]
    12$ S* I* F5 I) N3 n, _) `
    Ex2:《权力的游戏》剧本数据集# i9 }& T* Y" u7 b6 @$ i2 b
    现有一份权力的游戏剧本数据集如下:
    " s& W( @# V- F) X1 x5 _# h! O  j4 v2 ]: U, M# w
    df = pd.read_csv('../data/script.csv')
    ' [5 |; f# j$ U- p# Jdf.head(3)
    5 O; M$ Z- d; G1 Y. z& Q
    ; v6 _: p" n9 u& w6 B. G0 C: i" {% L# ~Out[115]:
    4 r# `. K8 F5 ]# t% q+ h9 y; uOut[117]: 7 H/ O1 N5 d! K! [# I- {
      Release Date    Season   Episode      Episode Title          Name                                           Sentence
    1 f2 q0 N5 U0 A0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    & Q6 z  j$ _' Y  z2 d1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...
    2 ]# r- G1 w( u) A3 U+ c2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
    2 h, A! y8 o5 F6 p% O& f4 S! t1
    4 I9 \; R, t' M4 [  [( o) X27 b6 v' D* l9 H+ T
    3
    + |% O" i7 q2 P! y4  g3 S' w# o+ Z; C  ?7 V% Z
    5& t9 e1 y' x+ g3 @% ~
    6
    6 q3 S& t+ ^- Y) j9 w& Y7' T5 O+ O9 p& L/ w
    8' K$ q6 o8 W1 `9 y
    9) ^5 P! O5 b( @4 c
    计算每一个Episode的台词条数。/ M* n0 A' i. C# h, t
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。! }  @8 T& u8 F! W8 K( D- t' t
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    $ s, F2 _4 S: C4 X/ V计算每一个Episode的台词条数。
    , {8 F# |! R3 p- pdf.columns =df.columns.str.strip() #  列名中有空格
    2 t- H- C/ _4 N% o- c  R/ [. I% c7 udf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
      D! F; M; e& |. O6 N& L3 P$ {
    % w3 g: ^4 y$ G# z, Cseason    Episode  ( C: |3 u9 z9 a$ d# K
    Season 7  Episode 5    5056 w& Y; j2 X" h6 k- Z
    Season 3  Episode 2    4803 Y, j4 ~& a( b% j
    Season 4  Episode 1    475! C& }$ E: L0 L9 o8 V
    Season 3  Episode 5    440
    " A3 ^& b! `0 e! L/ o0 ]Season 2  Episode 2    432+ r! R* ^2 n& E/ h
    1
    9 L1 `# ?; C( H# G/ R, Z  c4 l29 h+ T, c$ G  `/ b7 w
    3
    2 e# Z, p) _0 o: w. G7 s4) G7 ~! g, n- w# t3 A! l* l5 J/ B
    5( J* D4 [( W" J) D2 |
    6
    : m  {, j# ?4 w- L" a, a71 p* k2 V7 F8 M( F& y
    8- t; ?1 |+ u0 p( a
    9
    ! T2 r3 d( _- a以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    4 }  ~( O$ ]5 l: _) @) ~* {+ E# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数/ Z6 b  m- P  Y1 }: d: Q$ Q, U
    df['len_words']=df['Sentence'].str.count(r' ')+1
    * ]: C% k; L+ w1 U8 b+ Cdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
    / [4 p8 q+ Y7 M% C  F% y
    4 _% @' k# F; V: sName
    , }) H) l0 q0 g6 [3 qmale singer          109.000000
    % Y/ ]( x! u( pslave owner           77.000000  G% `+ p2 \0 q, P
    manderly              62.000000
    / r7 P( n  o: U. jlollys stokeworth     62.000000- e# F2 U) K3 `
    dothraki matron       56.666667/ h2 v$ E6 f. f' I
    Name: len_words, dtype: float64$ f% [" q! E9 F. ^
    1
    1 r7 M0 M/ S: J# N7 p6 z2. Z4 x5 W# o0 a
    3, [. M4 ?8 |& P5 u( W
    4  u. z: h, h1 m; t8 }. Y0 ]
    5
      R0 ?; g' _" o/ J6
    , n4 e  C, @! {+ o, l7
    ! b0 z( E: B, w+ t80 W! K! U9 @0 h7 {. d$ F$ B
    9
    / K6 p. Q' n+ M10
    2 x4 P# y& \  `# u11; H$ J: k2 t) L+ Z3 K
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
    + V) p# d: k( ?$ l1 J3 h7 Udf['Sentence'].str.count(r'\?') #  计算每人提问数
    + c2 z7 [& K. B$ E2 zls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
    $ K8 A* g1 Y6 V) ?7 Z  vdel ls[23911] # 末行删去
    9 O- K: q6 R. H- U  P; h4 a9 r, Tdf['len_questions']=ls. c9 e6 k; P" h% T* _
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()6 S, D. T" r- P& [4 H' C1 N' t/ b

    7 {/ R9 f# ]4 d# p; n. l  FName
    + p; v( m. I4 w2 m3 a4 N+ ]tyrion lannister    527
    4 R% a6 H9 j: l9 j9 `# yjon snow            374
    ( O. a0 w; A7 m! M5 m4 ^jaime lannister     283, v* K& X+ \$ T& T
    arya stark          265
    % {) d9 z% j7 u) h3 F& m# ~cersei lannister    246
    # p7 A, Z4 o% o/ }9 i/ pName: len_questions, dtype: int64
    2 }" V: S1 F' Q4 Q* _1 i/ E
    2 `: o! e( P4 ?. J1 L2 d# |& O+ s' k# 参考答案' ?) M4 F$ k  P' F* A8 \8 `/ D
    s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
    $ N2 ~0 w$ {$ J0 Ms.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
    ( F5 y4 [. @( l  E, f) c( A* A. z
    ) s2 h/ c* G9 Y. Y. x19 }) Z+ d% v& X( z% o1 Q$ w
    2% B4 w1 E; a2 U" P4 V- O9 Y! `
    3
    ' U% E# ~3 E$ ~) C7 N4
    / o4 W  c5 Z( W* z5 m57 L2 r% E9 Q- ~; O
    6* Y$ N5 W$ \' Q. |' u
    7. Q3 u9 \6 U' M
    8
    0 n' R* A! h/ H8 n9
    ) T! {, ?) x- A, p6 v# U% D/ y10/ d- a8 N8 b3 [
    11
    : d5 X4 h1 x1 }. v4 p2 S12
    % j7 T# Z" X& b7 I& {9 b138 S( @9 ]) C8 k4 ?
    145 x" W8 S# I7 X* k
    15
    - Q3 P; ~- V0 O165 w2 W( g, T7 f2 H
    178 s* }' d) S) r2 j' v# l
    第九章 分类数据/ u& X5 K) P, a% ^9 A& G
    import numpy as np4 G, c6 l; A5 {2 ]
    import pandas as pd
    ; L3 Z4 h- j  v+ y8 Y4 ]$ W$ d1
    / s% K% }$ S1 v8 K7 H26 F5 \  P. C* N  q/ _# F0 B
    9.1 cat对象
    7 L6 e& G1 W! U9.1.1 cat对象的属性
    / w* X( l6 s! q& p: ^# W6 ~6 E  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。4 q( I% n+ X2 P7 f
    4 |, Y0 J7 o5 f& D
    df = pd.read_csv('data/learn_pandas.csv',
    - U& j/ n% K; P! V+ d- o     usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])3 L( ?4 a: |/ ^) ~! |5 P" i
    s = df.Grade.astype('category'). a% m3 F! A4 m8 m' f
    ( T7 l; @: {3 U9 X/ g% C
    s.head()
    ) ]2 b5 x4 e$ i: a& {# K- N  [. qOut[5]:
    3 ^" L9 z7 ~- s' T8 x  O4 \0 y! V. U0     Freshman
    # J; X) s* X3 E; j5 |1     Freshman
    1 t# ?5 D- G: @* U2       Senior
    0 x, n- c, ~5 Y& z+ Y8 ~8 r3    Sophomore
    + |" W+ q; D; ^4 F4    Sophomore. O" s6 k. w& Z1 I
    Name: Grade, dtype: category
    ( e% z$ |4 f5 g/ b3 k: Z- ]Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
    ) K' \6 V0 m: }' ^$ W1; H9 J1 @0 x. i- b1 _
    2/ `, U9 p8 W' A' Y
    3
    ; r4 y* q5 V$ U6 M5 y% V6 T' V4! z/ F8 ^4 k- m- E
    5
    ! w! k6 K6 p0 ~0 G% w/ I6) s) R/ @1 C) |# X8 s! L, ?2 |- ^
    76 r( J* Y* q% c8 y' @1 U( P
    8, N- T" Q+ |+ v
    9# f! C+ L1 A  w2 s: z! B' p1 G
    10$ C* l8 J1 M/ h( U
    11
    ) [$ C: j. n2 g+ y) t/ @% p12
    3 M) ]+ D! u, E6 m6 o( ~6 D5 U13
    # o9 C  J- r1 N/ I  d5 E# m  在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。' ^, `# M, ?7 d

    ' n$ V, a+ J# P5 V4 as.cat! O( x: z: ?1 N& c
    Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>; L1 R# E6 J: s+ U  d6 M8 H
    1
    * E8 X, N. v  t, p2" B+ {0 Q5 D9 s: s+ k1 {
    cat的属性:$ j! N8 h/ X" @! d# f  `

    - ~  @, J: M' |+ T( N0 `cat.categories:查看类别的本身,它以Index类型存储
      ~) A. w3 r6 I1 w1 {# Ccat.ordered:类别是否有序
    4 P# w3 C3 V) v+ {9 gcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序, {  g0 D+ B) R- [9 Z8 h) e, U
    s.cat.categories* l' x8 @2 ~/ E; L9 ~2 x) [
    Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
    ! h6 x6 z) q8 G& a
    / ]7 A& Z9 T; a  e$ s& P" E% Ks.cat.ordered+ C* J4 X2 V9 V. Q/ ?
    Out[8]: False
    # H6 T" v! m1 P( i, `3 K% n1 i7 [/ @; `9 j5 l9 z) E0 {/ D4 X
    s.cat.codes.head()
    ; D$ E) A" ^) _3 T+ S/ C  I: m& Q0 HOut[9]:
    $ I0 y! |% N* \0    0
    % @$ c1 h. a! C1 M% t; c. T1    0: `" @! _/ ?* i( k5 E6 w4 A% m
    2    2
    0 S1 ^3 q* E& l3 V0 J( l3    3
    9 V6 U9 b8 ~# s9 Z# H& C2 S4    30 w% @( ?9 _- Q0 _2 B1 v( b
    dtype: int8
    5 X5 k9 ^7 j# Z6 \! {1
    $ ?( c7 [; d  P0 {2 c  E: }* y. f+ M2% }9 B1 X. t, q" w% x, B4 f
    3
      t5 f/ f6 h2 i2 p  `4
    7 r$ p, |2 `/ K* b% C: j* h" {5( s( E( I1 T3 a5 X) m
    6
    + G% q& I  U' e7 F) S7
    7 _3 R% y' ~6 b8 W! T7 W82 b6 Z2 q, }( Q8 z: c1 \& Z
    9
    8 E5 r: [% a' u: r$ e102 ^1 @2 S; y# B& O
    11
    9 ^+ d( D3 F% B0 ?8 O+ T12
    / x* p# c. w7 [! q2 f13
    % f- k/ s  I8 i147 }2 \' Y6 o4 ~/ |: L
    9.1.2 类别的增加、删除和修改( M$ \  N! W* P& {
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
    7 m5 W7 m% g6 {7 k2 _4 [( H1 \4 B* p$ G( w4 W. C
    【NOTE】类别不得直接修改
    5 o, @# R  Z0 f, H  i2 k6 ?在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。9 z( M1 ^' l; a6 s; ]
    0 ^' l) U4 U( H: j" m. ]- [
    add_categories:增加类别
    : J( s, B$ M9 y5 |# y: c" ys = s.cat.add_categories('Graduate') # 增加一个毕业生类别" G7 y4 o! L( [, `+ P* u( g/ {
    s.cat.categories& ]! G/ t1 U  F; |( R

    ) P$ {% Q. e* r& V" EIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    & L* w4 }( q% [4 ~1  @' Q, a8 g& ]7 f8 @7 t: c# [
    2/ ?! _5 F# l) t9 O
    3; O, O8 _9 N- K5 d& G( Q: M
    4
    + D6 @) |; C  [- x! `' t% h5 J4 Yremove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。# f- T4 V/ z% I9 |# B+ w* D+ ^
    s = s.cat.remove_categories('Freshman')
    0 E3 w; g2 W- P4 ?$ v; x3 k  h' W8 t, G; N& m* L  m
    s.cat.categories) e( _/ }. Q# D9 x  C" O
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    , o6 F. D& P+ \. A3 p0 z+ f% j5 D/ s, a4 I: U6 B# L
    s.head()
    9 D2 d- E' N7 |! yOut[14]: 5 W. f  i1 P4 r0 R2 b
    0          NaN
    * j. N6 Q7 n# \' ?1          NaN+ I: Q& R' j( w6 K  a" i
    2       Senior
    & m- q+ \& H( ]0 U. ]1 @' Q3    Sophomore
    8 y( ?$ i( s* M- c) C* v4    Sophomore
    . F0 I0 ~) C& l" U  A; u+ IName: Grade, dtype: category
    7 V& d- j$ S; ?: k0 KCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    ; U% y& b: ~8 L5 W1
    , }3 ^/ c! U* a5 z5 F2' Q1 S9 {  N# W$ Y' n* \
    37 H+ o4 E: q% Q! p" p  E3 o  _
    4
    + l7 i  q9 J0 f0 m5 I( ~$ g* `+ }5% ~1 a- V# a) w1 j( g; _3 j, i
    6
    : _3 A1 e& x4 Y* e$ U8 I2 v79 j% `/ \4 e' c) [& P
    8
    & }- {. |/ R) T& Q9- G2 @6 O% x; [9 z# x
    10
    # L7 v4 ]4 {8 ~5 y112 {. b$ ^7 N7 ]6 H2 `6 ?
    12
    " j, }2 }- O$ `, u( R- \, g/ E13
    , i  C9 W; N: l7 K8 b& U' {14
    - H" A+ m; L; o5 U* aset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
    * R2 n; Y" ]* b' d5 a) s) a  Ts = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
    9 ?  W! Y4 r7 g+ D- v5 \s.cat.categories1 v5 H- W9 t$ x' }9 W" h, h
    Out[16]: Index(['Sophomore', 'PhD'], dtype='object')8 r6 G, a/ q: a/ n

    ) |8 y2 h+ f* h9 F5 Y4 is.head()8 e% P  h* U2 f$ i
    Out[17]: ; n7 Y+ i8 u. _+ L  m$ d; x
    0          NaN
    # G; d1 h, ]( e2 W, g1          NaN
    4 _, y* p) E  a/ \$ R2          NaN
    6 z( {6 p. n! A" ]  U3    Sophomore& x# k8 w0 F0 y
    4    Sophomore' ~+ {' M$ D, _8 Z& _- @# S' k' g
    Name: Grade, dtype: category) G, }4 k* [, d1 b: Y8 V
    Categories (2, object): ['Sophomore', 'PhD']% l* j0 J! u; ]: `5 e8 J' G" Q
    1! k/ v: n; ?) W1 b0 w. I" U4 K2 n
    2
    2 ]; w' L2 r( M  X% G* Y$ D3/ Y! l  N( W* ?. M# R/ H0 g, a
    4
    7 P, e) i% t2 r1 z' p% e5
    ; R3 J1 E' h3 w5 h( l. d6
    . L6 q+ ~' {% Q7 Z2 O) n7
    ; L& m- ]& {& C- a82 `/ V& f& f) Y2 q$ u
    9
    , A3 C7 q$ p8 J- \10
    4 A% o. @4 F4 A* Z11
    $ W  Y. `5 ~& W; M) |) v12& U8 s2 ]" y9 p$ X1 a+ Q/ v
    13
    2 d1 l$ K* ?2 @& Uremove_unused_categories:删除未出现在序列中的类别
    7 _' l, t8 Q0 m7 Us = s.cat.remove_unused_categories() # 移除了未出现的博士生类别1 y2 u$ z2 }8 V) S% Y: q$ Q4 B  V4 u
    s.cat.categories
    9 y0 B, p# K6 M: c: D
    " u0 A" J; e9 sIndex(['Sophomore'], dtype='object')5 D, G' O# Z& s; c# x; c. H$ w
    1
    1 ~4 w2 \' R3 E7 b* s9 u2 K1 u7 y2
    2 i/ p6 r* R" ~# X36 l( T4 y: t- r: g3 v4 s
    4  k9 [, C) t8 B. B8 O% h
    rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
    " v/ V/ o! x. N1 D/ a/ _3 y" ^s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})1 L2 n; F' P" s4 {
    s.head()
    & j% d7 h6 y0 d) s) M
    7 _: x" T# D* N' w9 q0 M0        NaN
    6 N, L, V; }( P1        NaN
      R3 D* }  \$ z6 W- ^2        NaN
    1 o/ b. {1 j$ _1 g3    本科二年级学生6 q! D- t! i5 @) L& v6 _) N
    4    本科二年级学生
    . ?! G3 m* X( }: N& ^Name: Grade, dtype: category: a5 D/ X( m$ m  o: h6 F
    Categories (1, object): ['本科二年级学生']
    - ~% D4 K4 l7 U. c' J; P9 Q1, f" V6 J* X% C! W$ E+ p
    2
    7 I# f& g9 e& }* V. k( E8 p3
    + R6 ?9 B4 m! F49 w8 D) c" o) ^3 a) g9 M
    5
    & O) {0 Q# v$ c69 L" l5 i8 N% I
    7  m5 O# T" N8 M8 G
    80 B2 ?8 a4 f* [$ z
    93 R( i! g6 ?( S. s! D/ i
    10
    . C3 B) q: U# D9.2 有序分类  `) C/ N  }4 u' h8 d  D
    9.2.1 序的建立. F8 p' B( N: `9 S- |
      有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:4 ?; g/ t1 g+ K. h$ x& t1 O2 b

    9 w4 u1 ^# e; M6 Q" ps = df.Grade.astype('category')+ y2 @! z* ?% \/ t5 b7 v, c
    s = s.cat.reorder_categories(['Freshman', 'Sophomore',
    . d0 V4 m3 _5 c  \8 L8 b                              'Junior', 'Senior'],ordered=True)
    1 ]' K) b9 w; n1 b: g7 L6 zs.head()/ R! u% A: `" t
    Out[24]: ) d" o" s- g% o; s
    0     Freshman7 \/ Q# Y) r6 b6 K# m
    1     Freshman$ k: ]- P$ s: L# R( b4 A$ ?! Y
    2       Senior1 H/ k7 y% c5 C% B5 \0 h8 i
    3    Sophomore3 y$ F1 z+ D. R3 o2 {
    4    Sophomore
    ; \1 Y9 @2 H1 y2 s% {Name: Grade, dtype: category. c: e, H& ^! Z7 i8 D
    Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']' g( v( B7 F( U# J2 {; i! r
    ' [; f  c! M5 L
    s.cat.as_unordered().head()
    ) Q/ Q  V: |# @6 n* M+ YOut[25]:
    - b- Q8 J/ L- Q5 |! z3 \' j# k0     Freshman
    - j- r; D, C4 e1     Freshman
    6 A: D7 g) x; T" x+ }/ A2       Senior  t; x! @5 V4 B4 o: |
    3    Sophomore
    5 o5 b2 v4 g* m9 h; \( |7 H4    Sophomore
    / R4 p( I- i. \Name: Grade, dtype: category  a- H8 C% m9 o! y, e
    Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']3 f  L5 b9 C  `) d8 [% n

    ) C$ G' Y1 h' q  c% q7 E1
    * p8 g0 S3 d9 D6 ]& Y2
    / O" y- h  f, |5 c) t4 H3
    + |( T- T; M# e' g" O! b4! n5 }6 }2 y/ f1 `/ q/ b
    5
    7 c, Y& O0 q9 @& x* W6! x' P, t$ |: {) n4 v. P: |- ^
    7
    9 X% H$ Q4 H7 m8
    ' o! X, U9 U& a7 O9: n2 V8 m. B* B8 t5 V* h
    105 ]  f0 J, X& U0 m* b
    11
    ) q* K3 [* D4 @9 Z127 {5 |  b, E3 `5 o, k) r6 a
    13. r! a9 n0 E; k( n
    14/ P' _. K4 l/ o2 A& ~# Q
    15
    8 A7 v+ T9 _% n# [: Z7 C* ~16: Z8 I( L- f: k2 [2 W9 p! C/ i
    177 J; t2 ~; L, _. U) n3 M
    18
    / z1 I6 I* u& E" {# o) Y19
    8 ]& m- ?; f& r, S% E* `2 G20
    0 s: w9 e$ Z9 Z6 e217 ~% i- h, j! N5 I( J
    220 I8 p, Z* ?' v! h. ?
      如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。5 G, G8 T  q; s% d4 D7 l
    2 o0 y  M4 w4 m( h" t# {( [. Y# g% C
    9.2.2 排序和比较  p3 ?* r3 `' Q6 \( l7 A! b
    在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    * ?4 v# h) L/ K9 f7 M
    , n: z4 [: }  V/ N+ v  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:4 t5 f& T/ W$ J: T2 J& e
    3 x) R1 ^6 ]% ?% F" Y
    df.Grade = df.Grade.astype('category')
    / q  M) j5 y% {: I% vdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    : _  w6 D$ N; e. \8 W3 T( gdf.sort_values('Grade').head() # 值排序
    8 r3 ^0 B! U9 B- |Out[28]: ; L; o& y8 u$ K) l* _' c9 W6 O, Z' Z
            Grade           Name  Gender  Height  Weight
    ; F- N5 V+ S" v; m9 P2 H1 ^! q0    Freshman   Gaopeng Yang  Female   158.9    46.0
    $ q- ?5 l, K: H* y7 }/ x. H105  Freshman      Qiang Shi  Female   164.5    52.0
    & J5 W5 X+ x1 v. }6 N; ], o/ i7 g96   Freshman  Changmei Feng  Female   163.8    56.0  |  _% _. N: {. t  \) ^4 f3 t' l
    88   Freshman   Xiaopeng Han  Female   164.1    53.05 r* L* e+ Q0 D2 m
    81   Freshman    Yanli Zhang  Female   165.1    52.0
    2 j' F- S+ F( d5 i9 j/ L; u
    ! r; Q* j) }/ s) k  a& wdf.set_index('Grade').sort_index().head() # 索引排序
    8 E. s! d1 ?4 o+ F7 U6 Q$ a1 XOut[29]:
      A& b" p0 l- D0 T                   Name  Gender  Height  Weight
    ' A. i" c. F0 K: i& P# HGrade                                          
    , u8 {( A9 D6 ?( C1 U; {Freshman   Gaopeng Yang  Female   158.9    46.0' U% J. Q$ X( A6 ^% f5 U: o0 D
    Freshman      Qiang Shi  Female   164.5    52.0
    1 m# B  t9 r) K) Q4 |( j* l% aFreshman  Changmei Feng  Female   163.8    56.0
    ' m  B% Y( o7 j5 T5 y. C4 OFreshman   Xiaopeng Han  Female   164.1    53.0
    3 w6 I$ b& ~/ b( H6 ^+ ]5 mFreshman    Yanli Zhang  Female   165.1    52.0
    - r, \' V, W) q3 G  A+ q5 C4 z5 U# U) V4 @( y# v
    1+ T$ ]  _; l1 l1 t8 i( t3 Z. y
    2
    : Z: |! F* _0 i$ p: t3$ M* X+ v2 Q/ `- p" T* Z" h' M  C
    41 i! h4 g: v- d
    5
    * p0 p  @$ V- m6 k3 c" s6
    3 R* F$ p8 @: H/ K1 N" ^7 x/ F2 H$ C8 P7, T+ w# ?  ?' e$ i4 r0 A3 M
    8: ^8 I1 W$ k! @) A# B6 A! Y2 \9 L/ P
    95 U: h3 i! A6 a. M, V; F) k3 a
    10
    9 b- d! F5 S* W118 s2 ~$ ^/ ?* r- W1 m+ u/ e
    12, O  N3 |4 r: ?6 U6 J+ i# m7 r2 }
    13
    : `& g. g8 k) n' s140 q6 D' {1 w8 x9 y- r1 C
    15
    4 s$ T0 N4 g* {  y16
    , S1 G+ n4 E( E1 k/ v17# I  P0 o( [2 r
    18" B3 N/ {3 ]6 u! ~4 I' m- J7 T
    19
    ( m, @6 c2 c+ P. ?. C20% N" c5 b* e+ N0 I/ f3 b( g6 t
      由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
    ' ^2 R3 O$ Y6 x) Y1 ^8 O5 I, b& a6 _3 o4 I2 k, G
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较). _+ D1 ^7 m9 I7 l
    >,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。# i2 L' A- i/ R- H/ t) }
    res1 = df.Grade == 'Sophomore'5 M2 t3 u6 U# C2 ^+ R/ D
    & I* I3 j: W. X) A  P% e4 r0 ]
    res1.head()3 S$ k6 F. l5 B% |' t; \; B+ R" @
    Out[31]: % H9 f/ V5 u* o& D
    0    False, U- r* v4 a8 \& b7 j9 \: G: d
    1    False
    * [$ g$ L$ {; G2    False
    8 G  t* T8 K# Y3     True# Y- g. o  a6 ~# c$ N* c% Z+ T
    4     True
    6 ]- x  L+ y. S* Z8 @! VName: Grade, dtype: bool
    7 ^; l( o% s, \" _) L& n" V2 l+ p9 {# l" w9 l
    res2 = df.Grade == ['PhD']*df.shape[0]" V5 T( s" J3 d3 w
    5 E; [4 y6 ~1 s+ Y3 {1 K' i: P
    res2.head()1 O6 c. |8 R# ^
    Out[33]:
    ) P+ b4 O9 d7 z- e& t5 }( F* y0    False( r( T- V8 J% X* h3 A; c3 W
    1    False
    * n6 N) F2 y) }8 H4 i' s9 N2    False. {1 ]& x( i6 x3 e1 n6 U3 h
    3    False, i, T+ ?, v$ J( J
    4    False7 j9 w- n  g0 H8 H+ K  g
    Name: Grade, dtype: bool
    ( a, O" R% q: e) ~' ^5 I% j$ ~4 u1 i( ~" R& l
    res3 = df.Grade <= 'Sophomore'/ L* Z  H: D* W; x! [, K8 i
    " }4 M/ c- B  K3 v, C: t. R
    res3.head()
    6 o- j& g* @  f$ @7 P, `Out[35]:
    $ {+ Y5 j( O( O& X3 g+ U0     True1 a: d4 c8 L4 D! C9 W3 C
    1     True
    " f/ Z* X% |; G( M2 }; l6 P2    False
    6 X  f0 \. Y) I  a) P3     True
    + ^  J/ g) L3 w0 M7 J/ l4 D4     True" f: |9 u; j8 H$ V  a. _  B
    Name: Grade, dtype: bool& e% z0 }: q8 S

    ; G4 N6 [7 D4 V1 c# S  B/ E# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。" _% W9 r3 N4 E- V+ E5 O
    res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
    & J  W  \5 j- I6 d* I4 g3 \
    7 M7 I( S7 R& b; U( W: lres4.head()
    & e& _: C3 y1 W- N+ t  Z! vOut[37]: - |! [3 P% D  l& ]" f. e' j
    0     True* A  j) Y4 |) i! T
    1     True
    ! z; F( E! g+ Z% T1 a2    False* V6 W0 ^6 E# p/ L- D6 H0 H
    3     True
    6 ]' x5 g( B6 o' e5 j4     True3 d! S: |% s1 q9 h
    Name: Grade, dtype: bool  s$ f. S* u0 @- m) a4 R% c- J

    3 ]5 R5 c) y4 f0 f# y. N0 C( k1
    0 d8 h& e0 m# v6 _  }- |26 z% _. J' v% i( \- W6 |5 q% }# c
    3, {2 o+ S( `, ~5 J
    4' _2 R" x; h0 R7 ^0 S7 Q! c
    5
    5 H  q3 T3 T# G  V; q3 I- E2 H6
    3 ~1 W+ n! c5 p) n4 I& O! s7
    5 V( ~6 h' T0 S! V7 c+ u8. c  E( G8 y' M7 ]
    9
    % O  w8 t3 c- g2 E10
    ) E+ C7 e/ f. Y$ j% H113 |; Y9 r0 v0 |+ \; ]
    12- h! Z& C; g7 v$ v: W, \1 T
    135 O+ ^' ?$ c" Z
    14; y+ u7 ?( h. r9 j/ ?
    155 O/ D5 h! C6 V0 W9 l
    16
    1 V6 K+ v2 x; E" f7 V2 K9 m17! F- [/ j. R: k/ H
    181 ]/ r! Q' E# O  M# I/ p+ l6 \$ J
    19
    4 F0 W  G0 b! h$ l- \. q1 y7 h6 t209 p" s1 n' @3 l& x6 O
    21
    6 f  F7 F$ T; D3 J. @4 e  R22
    4 ^1 |3 I. [! x% \6 {23
    4 i& e# }1 t4 m* p2 x+ ?24- _3 n7 M$ R# i4 u' J1 m
    25) c# U( G7 s+ L# ~+ Q0 z- I) `+ W: B
    262 z: u* f; ^4 l( j7 G) D
    27
    % [6 d) k) ^" |3 @. `. y28! c' g3 g- c/ V; K' I7 Z5 m
    29, j2 ^. y7 T1 W1 e% ]' g$ v+ C
    307 a* m' ?4 ^5 x& x7 z3 o
    31" g( y, w! g* L: R
    32& c4 Q2 |. l! E9 D5 L, V: I, i0 z9 G
    33; d3 h9 J; u2 @
    34
    0 C4 z5 c* v# Z' v) z1 u! p- O35
    + v" Y3 o  V  x5 m/ G" n36
    6 C9 q8 J; J7 v+ \37+ ]# o( I; d6 B2 Q
    38
    3 O/ [- |$ M, S  z9 j9 }7 g39; z& a( O  G8 I) b: l" U- ?; |
    40
    ! s, n) \3 B% B4 }8 F9 {2 }6 b9 z41
    5 i( [3 p* n  E0 A' M4 w" V42( U0 q  |3 A$ I8 u
    43
    8 h& |1 F  L. P; t% J0 {+ W44
    % \9 S0 y( W+ Z4 [6 a9.3 区间类别
    ; T1 f, I$ s# e, E3 g. f9.3.1 利用cut和qcut进行区间构造
      R+ v- n# F& G$ }) f( Y" V. _; @  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
    4 [. _+ L1 d" U7 A. ^5 B& [
    2 l# F9 j' H( K3 d) I! |: Q0 d  }cut函数常用参数有:
    : a5 N% F7 W+ p- fbins:最重要的参数。
    9 x) z+ V0 g7 E: |6 G' E# F如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    # w7 u/ r% d" ~; G也可以传入列表,表示按指定区间分割点分割。! `' _# H1 Z% E  F4 r0 O- v
      如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
    6 S% S% M$ ^3 U& E$ b1 t( V  W  如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。# w9 l# E- }) z! @

    3 r4 b. R6 S  P% h% H( ws = pd.Series([1,2])
    0 f3 b$ q0 r. H* ]( U2 ]# bin传入整数
    1 @+ j5 [# j  B4 t  n" S
    ; g5 f* a9 }( H; `; O% |  W) ]pd.cut(s, bins=2)
    4 S# |; o3 Y) K  E" V: bOut[39]: 9 q* t% \7 x! D+ l" V/ I( k% M
    0    (0.999, 1.5]7 x+ ?# |$ a, ?+ `2 |6 J
    1      (1.5, 2.0]
    3 w9 X+ f5 C, O+ Q9 Z7 j5 Fdtype: category
    ) F. i3 R1 s" E3 \0 HCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    ; b, `& y* ]6 \6 w( L! b9 _* N
    0 e7 n+ G/ H( K0 x& Y' b8 tpd.cut(s, bins=2, right=False)& k" b( w9 r, r+ d
    Out[40]:
    ) N. O4 l& l0 |; ^. n0      [1.0, 1.5)/ P- z3 j% J" [% h, N  F) [
    1    [1.5, 2.001)
    1 G: n8 N) I" \- w! o; g( Pdtype: category
    6 W, q  U$ Z$ n$ qCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]# t$ z( G# F% {. h! {

    ' M4 F9 |; Y: g$ Y1 z, ?$ ~, B- G2 j) S2 G9 _
    # bin传入分割点列表(使用`np.infty`可以表示无穷大):
    5 C$ A8 F1 i8 ?. Y+ T* }. `) xpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])4 R" G2 _! _0 b4 H/ h
    Out[41]:
      D' `& v3 W1 c. d+ i; Q, y0    (-inf, 1.2]" C# L5 b6 \9 T0 [- u) ^0 M; |+ \
    1     (1.8, 2.2]! C# E" w- [) S) D
    dtype: category9 f. C4 F& v1 c4 n9 y( V
    Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]" J! A( F: o- a1 J, A2 J
    % }4 g4 b9 z$ X
    1' e% @- f, N% s- N
    2
    2 v' H2 J. G* W3: I" V( w2 i' H$ g8 q+ t
    4/ C7 {( I  ~/ K) e4 h
    5
    1 V$ W9 v, ^4 O. R) K: Y6% q' k! Q8 e  d; t" Y& [3 ~
    7- K! S5 Q- G. u9 Q: _- d2 c
    8$ s) H, }" n  d* C2 t. X
    9) P7 p3 t: E8 ?
    10. b; p  O( K' _, T
    11# ^& r3 A" N% d" {: a1 U' u( T: f
    12) ^6 C; \4 r9 t" N
    13
    . S: d8 r5 w' E145 E, h% w( K4 u' A
    15
    6 j8 y* {* _4 W- k7 S  t16
    / u% a& u% o! \' `. ]178 g- h% m: m+ `6 I
    18
    & U5 F( M' a- H9 O; K) [19
    1 P% y; {2 n: Y3 e5 ^  g! I( a/ G20. i/ Z  C+ n( ]: {/ E1 I
    21$ L# W; p) ~3 j! d; B3 _( _! y
    22
    8 `: Q) L5 B  H  W$ \23
    - V! J% A/ D: g2 U; Y+ e24* J- m5 B  O2 J3 s! l
    25
    * j  _# f6 R0 @  H3 X3 q7 qlabels:区间的名字7 G, c% K9 \4 s# P+ D/ U1 L$ F6 F- g
    retbins:是否返回分割点(默认不返回)+ I. T! g- @: y. m- E% x/ n) e
    默认retbins=Flase时,返回每个元素所属区间的列表
    0 a* W# U& K9 ?( i! x7 gretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值$ E' b, f; T6 N

    ' a2 x5 |0 B& _  K& s  M  Ms = df.Weight
    9 R7 q; j! c& w  P: Ares = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
    . n: e, \  L6 Z& E  Mres[0][:2]$ k  ]7 v' B. t& x" S5 j
    , z1 O/ g+ p2 X' J+ q9 f# L  x
    Out[44]:
    : y, k+ |& b+ N( u0    small" U2 d  ~; w6 ^1 v% V- \' C
    1      big9 x; W) E3 z1 P5 l% F
    dtype: category& h0 ^: ~  R8 J. V
    Categories (2, object): ['small' < 'big']2 f6 h5 _9 |* B

    ! m$ r0 h& B. Qres[1] # 该元素为返回的分割点
    : E7 i; c) T/ f) dOut[45]: array([0.999, 1.5  , 2.   ])0 y+ n& R+ @: K3 V$ @6 p
    1
    % _$ \# p: Q) I. n0 H( U2
    8 [! v: }  F7 v: c3# G! \9 V6 [/ R# n- J
    4+ `! C1 y) _" k2 Y/ T1 p) ~
    5; E" o9 l+ q# F8 u0 `6 [1 H% ^
    6
    5 j; L) E* Q) Z! }) h7/ l# o- h. d, l; w) j5 h$ ]4 e# g
    8" |$ ^% g4 K* D% Y( t
    96 u2 ?0 L# N. f* b" v
    10
    % n' V. e+ @# S4 t+ N, i114 a. r3 a( e2 t3 b2 [
    12& |  o  V1 f) U; ]" A
    qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
    9 k: b- I; Y9 t: q9 h5 @q为整数n时,指按照n等分位数把数据分箱- g, i! L/ B# j/ n
    q为浮点列表时,表示相应的分位数分割点。
    8 [9 N& W) T9 F1 r( s8 as = df.Weight
    6 j6 O+ ?5 f4 ?% R: z" i) Z
      d( x& ]' ^/ _! z8 O/ M: o4 S! ipd.qcut(s, q=3).head()
    $ O1 d' S" g7 G# HOut[47]: 3 `8 p, I; }/ B
    0    (33.999, 48.0]
    ( H- Y- _$ m/ ?$ x/ }9 x1      (55.0, 89.0]
    ' H' M2 Q# d$ o2      (55.0, 89.0]
    6 n" `1 {4 G: [) i/ G0 [3    (33.999, 48.0]
    8 O5 x, Z  g: H* R! J/ ^& o# d4      (55.0, 89.0]
    . C7 h- i2 P/ y1 |Name: Weight, dtype: category5 M4 d" Q" y  h( {5 s! n
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]; ~! X1 v1 r# \8 \9 X0 Q/ }/ }, \- |& ^

    6 O" _) A2 P: f' w8 Kpd.qcut(s, q=[0,0.2,0.8,1]).head()3 v1 n- x2 e/ o. |
    Out[48]: ' n- i! m! ]! T: r3 @
    0      (44.0, 69.4]
    # P4 @! d0 F) {$ U; `; N' W1      (69.4, 89.0]
    # J8 m- N4 d  S$ Y& b/ s2      (69.4, 89.0]' |8 X8 q: E  M
    3    (33.999, 44.0]8 S' v; }0 g6 R0 Q' e# p
    4      (69.4, 89.0]
    & c$ u' g" @/ ~Name: Weight, dtype: category. @' {9 o* T3 d* n! d3 Y  l& C
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]$ ^/ p6 f/ e( L; m7 k6 j2 N& X

    . y3 G. `5 ~% P* H  j) ^1
    8 B3 s# p( c) J) A2
    ! G- u! k( o) U/ G2 Y! f; f  F3; Z1 f  e: x) u7 d! p* F) `5 N
    46 Q* h5 F% O5 c$ e+ T
    5
    $ i  D# Y0 U! }; U! R61 `; D3 E3 }+ J3 M8 Z
    71 T; I- v0 d9 f" Y" l
    82 C3 a- {! l7 X: ]) R  X
    9
    7 m7 O  l$ q& h1 j0 S10
    , E- r( |; y: D7 ]- }5 S/ q/ M11
    ! W: o9 k3 a; K, y( A3 y! g6 R12
    5 ?: J! k2 D2 Y13
    # H/ R: q: K* Y) o  f. _14; M; ]7 ?/ _0 [( J& y  L
    15
    3 |! x+ V; F9 }16& F4 H! {1 s0 q$ @# G+ n
    17
    . M; B  ^, [! x( U+ ]- d182 C" {! j& P* ]/ y, Q4 i
    192 y# G5 e8 o2 C8 E6 D
    20
    1 I. e1 K- B9 g" i: c1 T21
    $ J' w2 Q/ @3 n1 ]9.3.2 一般区间的构造
      @! M9 a0 @7 ?( [: i) p5 C4 K9 A  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。- G8 z# x6 H( y6 h' q

    1 O" z: o4 N4 W  f) J  U0 `7 [开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
    7 Y$ ], L6 v" a! s' n8 Y7 ?my_interval = pd.Interval(0, 1, 'right')+ a/ z% B2 c3 S' c2 O" T
    # m  ]7 k0 n$ B
    my_interval* r4 |& G- |: K+ d* h
    Out[50]: Interval(0, 1, closed='right'), e8 _0 J% J2 `# V1 b
    1  |: P6 E, @7 H$ ^  k2 y
    2# o6 `) h  S2 c1 @
    3% O7 `9 {4 a- H- N9 e, o! ]
    4+ ?+ D. H: `8 y5 n# |
    区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。3 {# a0 E! E2 n, ~! L) c. S/ }' v& t
    使用in可以判断元素是否属于区间! `: c' `* D* h# N
    用overlaps可以判断两个区间是否有交集:
    ( V+ R. I* B! F2 M$ k, d( C. ~7 ]8 h0.5 in my_interval
    % o# E8 e1 u2 m- L; b/ t' i# \! s  j, I) X" u. _  `
    True
    # `; w$ Z7 u7 C! M' s; F1
      i+ J2 |: @. y2
    ' G: M( W% m* P! b36 w8 w  J9 V) M5 {, n$ ]( |
    my_interval_2 = pd.Interval(0.5, 1.5, 'left')/ i) f7 a, N  Z
    my_interval.overlaps(my_interval_2)
    ' i! O5 @( ]; v8 Q* @2 M! f( d- n$ n3 e% O
    True
    " @. w6 A5 {: z1" H2 S) A6 }- u; P6 C5 b, S
    29 ?6 q4 P- Q5 x5 b2 x' i) l
    36 h8 ]+ l0 q, V
    4
    * s) ~1 q9 c+ L1 e! H  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:1 {9 J) c; J/ _

    & l! `9 d9 U4 l$ q. X/ qfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:" O' {$ Z' R3 L$ K
    pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
    : ]& b+ x+ J0 c' U( n" |! p1 _) ~# L9 H- I% a3 A! R
    IntervalIndex([[1, 3], [3, 6], [6, 10]],
    7 M7 V0 f% ^/ a6 v1 ^) u2 M9 L$ H               closed='both',
    * J; I% A: I, \( j* i               dtype='interval[int64]')
    # V6 k3 \: X, ?5 a* q; Z1
    ' Y) X7 W  H% I2 {( }" R2
    8 f3 b3 b7 ^4 }+ U6 b. [1 P3
    7 C/ L$ O! r6 `$ r: @8 Q' ?; O5 M# @! u44 `, y0 ?) }- C* `! h1 N- ^% F- I
    5
    9 r) S8 h- Y0 W% ^from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:4 X( K% g# |4 i3 A; p: H$ c/ K
    pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
    # z1 A; a; @# f7 [# N, W/ ^9 P2 S6 e! H, d( R
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    : w* K' _  O$ P0 W* P  T                  closed='neither',2 N  D! J2 k# P1 V( O+ ^. A) V; b
                      dtype='interval[int64]')
    # C" s! q5 ~  S6 J1) ~0 h1 G2 Z- x
    2: f; ?: m0 p( o  i( g) g4 V
    3
    ; z% y# _5 J9 D! m0 g7 z4  e. p9 K. }; h) t
    5
    : O0 l, |* L8 t4 P2 Z2 ^8 }from_tuples:传入起点和终点元组构成的列表:& x, o& t7 ^1 Y, v4 d% ^/ Q
    pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    3 \; L5 I; q4 f- U! X: W8 ]( R& T6 J7 i$ E6 p0 Z
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],: o, a7 M' g4 t) @( t4 s- ^" C+ z' w
                  closed='neither',
    , K  q! X1 Y. L, N5 \; t) c              dtype='interval[int64]')( y! ~( a1 N$ }" ~# }5 ]+ N
    1
    : u3 s1 v& q& }% w0 i2& b4 ~1 w5 b! |8 Y5 z, a& g* O
    3; q/ _1 M. B3 ]" y  `" M
    4
    # B$ u5 P, x$ N& Q) ?5  C( l" g  J2 s) L5 d- C- E7 q' i
    interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:. v9 _2 j3 m! f: V
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
    - ^' G3 w) P% g' BOut[57]: - |. U6 y# h8 ?6 J% U$ n9 E$ B
    IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
    6 V$ d! }# t5 [+ b4 x3 A              closed='right',
    8 c$ A4 Y% {+ ~              dtype='interval[float64]')
    9 n. c/ ^; Y5 g! S( S' q
    6 P3 F: S/ P1 \pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度
    - q; n& P6 \# N0 l  pOut[58]: ; [, U( U6 c- C
    IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
    7 M5 H" q7 E5 S% L7 W# ?2 m              closed='right',
    ( ~& G- v% p$ Q  v" k              dtype='interval[float64]')0 @0 C" H% a: }: K+ R, s2 o2 J
    1/ |+ G: Q4 D4 N. ?0 E1 Z
    2/ k, Q4 Q' R  m
    3
    # Y" N6 s+ o7 i5 {, O- }3 d4
    ! \# U! N/ E8 U/ d/ y1 `5
    3 l* J, }) s0 w7 [5 L) p65 M( [8 p7 @6 y
    7
    & H* x$ U. g+ V; Z( E! t" ^2 x/ j8
    % F3 G0 K# w; I6 s; f5 n9
    ! e+ k8 m7 ^' P! L10' H6 ]6 d" e: T4 |8 a
    11" X% k+ Z& q% J
    【练一练】
      A; N/ u; j2 R/ _6 b5 g  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。$ A# b) ]" q( U1 v% [1 q
    , S- f9 e) }- o) s, V" W/ f2 M
      除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。2 J$ r" M5 j0 q- l
    + D/ \( j+ d1 H$ s3 J; k
    my_interval, ?: e& @% {$ f4 P- u3 i
    Out[59]: Interval(0, 1, closed='right')9 r  q& a; p  D# R
    5 `" Z$ N4 v9 T' b" a2 g
    my_interval_25 m& P- v4 d. c' i- H2 |5 J9 G* F
    Out[60]: Interval(0.5, 1.5, closed='left')
    ) U7 H- L# V' S, C& d4 P" R- ?1 f. S& b4 x
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')
    ( [3 K  f8 k/ u: W$ j; jOut[61]:
    " U) n5 Z7 k3 F4 O' G# G% kIntervalIndex([[0.0, 1.0), [0.5, 1.5)],! n+ s# j/ u1 q: b
                  closed='left',6 \& x% e8 ]( Q1 x1 J7 h
                  dtype='interval[float64]')
    0 ]% g4 f' [' l, y5 z8 m: `$ S1- ^+ r# H4 A) S- ^. A
    28 n$ `  W5 A9 p% Q' R
    3- s8 a- u& q8 [2 A# j3 Y
    4
    # L. E3 N8 ]+ B8 O! l5. z/ h' b" D, D% M6 }8 N
    6$ j3 k- N2 l. _+ c, E
    7% w- l3 Y4 I4 `" N6 }. a* Q
    8& z4 A0 z2 O" Z1 ^1 d
    94 I. E1 ~3 r6 y& K  n1 {# {
    102 t6 g5 _. o% `2 \8 p
    11
    & w" Y& O( Z) E7 w; b+ T9.3.3 区间的属性与方法
    4 A! K' {- F$ X2 k! _% |  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
    : K, G' |8 T" |1 w. P0 W( Y% L2 ]8 P2 R5 t
    s=df.Weight. \; I1 l9 M3 H4 U3 O
    id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
      p8 X( L: Y  g, c! e4 x$ Xid_interval[:3]
    1 g$ P) J1 i3 T& x7 `& r+ U0 t8 L' J- W0 I; ^
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],- D' V4 A5 r9 w* j
                     closed='right',
    ! W+ w% y4 d4 R3 g                 name='Weight',8 n+ q2 T* `0 X& q
                     dtype='interval[float64]')5 C6 M$ @6 {$ \+ ]+ z/ `
    1' {' a- |$ j8 Z5 ~# n" h- [
    28 T! ~/ C4 U5 J
    34 z& ]: ~$ N6 l% w2 {1 t
    4
      c- V# r3 y! F6 z* E" y4 e$ n# _5
    & K- y; K3 r; a/ b) x6
    + ]+ ]$ @4 M9 y: e8 l6 K7
    ' g, R# F8 X9 G/ i1 ]8
    ( [: l6 v/ _! Z- E- B5 C与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。  f8 o9 v! a) e9 F; c
    id_demo = id_interval[:5] # 选出前5个展示
    ' `$ o, J" C3 c% N7 v* ^- @; \, c" |. |
    id_demo3 @5 O+ q) N: {" L5 `) H* i
    Out[64]:
    . K, V* ^' u2 T% c1 r% v( eIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    ; v5 O# d* L" I; Y3 J2 P( W$ S              closed='right',$ H  {7 }$ {/ w+ m# i
                  name='Weight',
    ' \) b: Y3 k, p9 d              dtype='interval[float64]')/ t! M' [* l8 [" _- _! d9 e
    ( m7 @, _- f! Z5 j0 \7 R
    id_demo.left # 获取这五个区间的左端点6 y  d' e" [% B( T
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    ' e0 ^4 S0 _/ s  x0 P8 H% {/ y2 z& \1 j* N# T# O
    id_demo.right # 获取这五个区间的右端点
    * I3 V# b4 O) y$ u" ZOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64'): H$ [1 T1 Y0 s3 g6 r/ J$ F
    & o% K. |9 d; E  |1 w/ a" O  @7 L8 i
    id_demo.mid4 Y" y" T# j, C4 p- X, B
    Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
    6 W0 a* F$ O' t! F# L
    + F+ f. C$ k1 E0 a2 m- sid_demo.length
    : [8 f* {. Q2 @3 _+ rOut[68]: ; @# k6 B) J1 `1 K. |
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,. Z: I) q0 |. |+ I+ I- U
                  18.387999999999998, 18.333],
    " G' E/ @; t  T             dtype='float64'). C( f, l4 Q/ l- ^1 G2 }  S  O* }
    ' u5 K( h4 F" K: M1 W  b. C' {
    1- k3 O! f  {0 Z/ O2 j
    2+ E/ O0 `4 B5 L) `/ L
    32 G+ D5 S0 w( A% k. R3 P( i
    4
    6 c* j4 b/ |6 a) f; t; n: c* E* v5
    / E2 R6 U" s3 {+ H7 v4 f6
    / o5 l* e3 |$ h6 r9 t/ W' N5 v$ g7
    $ h) K) S9 E- @: R8
      e$ a8 K* Y, ?9# z" I# P' R7 o' F- P% Y( o
    10
    # o2 O, v0 d4 Y9 X' ]: K116 g& _1 O! W/ _0 T# B
    12
    * x1 R/ i, T! C+ R! U: c" O2 M. I13
    2 \. `7 k+ f, {- {14
    * |4 G( B  }& X$ x* n  Y5 p' m15
    & s5 h% X6 w1 p% A8 e16
    ! {" e8 M% }- l; G17
    * ^% a1 M% ]! o2 b+ c, e188 v5 a, I7 }# Z
    19
    % }) S0 v" C# \2 q20
    " x% a' C9 l# q6 J210 Z& a* x- l8 }! `9 N
    225 e6 n7 p+ T9 z- [
    23
    " V8 S& c* q& ?8 j/ s; aIntervalIndex还有两个常用方法:
    ! Y9 F8 e( C6 g& H7 _" kcontains:逐个判断每个区间是否包含某元素
    . S% @6 n8 L, n3 ?" g; y; Doverlaps:是否和一个pd.Interval对象有交集。
    ' l- d0 _* d) H! i! ~9 t" Xid_demo.contains(50)
    & c* q0 |0 Q1 W# c2 J# V: GOut[69]: array([ True, False, False,  True, False])
    ' \# |) K+ ?1 y$ Y& K* P! P  T0 {3 g. X' k2 |
    id_demo.overlaps(pd.Interval(40,60))0 n. z1 ~! u/ ?+ y- f7 m
    Out[70]: array([ True,  True, False,  True, False])5 w+ o* r3 o/ z# d0 V3 {% @" x% O
    1% \4 f; i1 L% ?4 ^
    2- Y8 d! ?/ t" w5 n
    3
    4 S0 p% _! Z; r0 d: n; E& @9 |2 t) y- D: k4, O' O$ e( d9 P) `7 F( B0 }' `' V
    55 w5 n: w- T3 @& k% b  T' n/ g
    9.4 练习8 e1 _, B) `' @8 R- K1 o
    Ex1: 统计未出现的类别
    - K5 a8 l1 Z% \) l) N  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
    3 i4 T2 U7 t, F- A6 _' q
    4 d4 V) E# P* R2 ?% tdf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})5 B" b: e7 n( B0 G9 X
    pd.crosstab(df.A, df.B)2 e/ s- u! \- b! c& t
    ) `: r6 `$ c: H: C0 m: [
    Out[72]: + z- z6 C6 }9 X1 O  h
    B  cat  dog% ^2 x: k2 P( h$ v' F+ [* K# H0 {
    A          3 s/ Y( `) j" H( D. E
    a    2    05 B* e( V( N: J$ r- H
    b    1    0- o3 i( w8 ]& K6 i
    c    0    1
    1 e+ G* y# a% A$ f! G1# \: R+ G! q0 Q& E( f! I: D; j
    2
    ; |( B2 R4 u1 Y" {8 j' T9 W7 Q+ F3
    2 T1 p7 n6 S; }' t4 C( y4
    ; K: Q: z: [0 M7 f5 k* M# Y5
    : v9 Y7 P$ c0 B6
    3 d, Z5 @) `8 r' T8 v/ D7
    ! ?5 N7 O8 ^5 A5 A8  D$ h# Z1 p$ X- K6 s% f
    9, M. t3 x1 C0 w7 r
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:5 G8 T; T3 F- y" S
    ' U  I$ u, m. D
    df.B = df.B.astype('category').cat.add_categories('sheep'): b& N/ O, L$ ~* B1 O/ m
    pd.crosstab(df.A, df.B, dropna=False)
    - y6 R0 Q6 L1 u7 L8 K7 h% n/ y$ z" D0 F; J4 C+ H
    Out[74]:
    ( c& y9 |7 T6 {: _- C/ [B  cat  dog  sheep3 g; h* i. A1 ]3 p( H- Y; e
    A                 
    5 ]2 b! Y, w- J! c) Fa    2    0      0
    7 K8 [6 _2 g1 Sb    1    0      0
    / m1 |; U- O1 f: }% T6 Jc    0    1      0
    % M& y" f) a6 b0 [/ l6 E5 V1& |. [; V1 x8 N
    2" f. L, f5 B2 Z
    3
    ; I( U" W" s, z- ~4
    + `  C, J0 U" I9 y: D5
    9 o4 z: m& _: l$ h% E6
    $ T4 Y: W, S* j5 P) ~: R# p7 ]7
    & q) h! u3 r) u. l8 l( m8& u. Z# j  B& |1 |
    91 D4 G8 }# u1 R# e; z' p
    请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
    % @, e; y, s. p6 b) D! ~. V
    4 L# o6 j8 I( T2 ~' vEx2: 钻石数据集
    6 G" g1 g: c; j2 F  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
    ! x+ q, t* f# L! Q$ O0 H& N) [  ]4 u# W
    df = pd.read_csv('../data/diamonds.csv') + Q& v: e& r) R8 T+ b+ x- C
    df.head(3)  n+ _& y0 }% t% i
    ! E6 A! i5 Z# C% B  c
    Out[76]:
    ) Y5 ?! D, m4 [3 {5 c   carat      cut    clarity  price
    # Y& U* J( m8 g8 ?  I0   0.23     Ideal     SI2     326& h! z6 m5 U4 |5 q5 C. ~
    1   0.21    Premium    SI1     3269 G) P/ v9 i/ t+ [9 n+ V
    2   0.23     Good      VS1     327
    ; P$ o2 u; @6 U$ {- f4 [10 D9 _  s3 \9 x
    2. j7 h/ O6 E1 g3 o
    3
    ( T+ t0 b5 ?9 ]3 f# ?; _/ c  R4# r* D0 J( p4 g2 m
    5' c) Y- u& f4 }; Z* _7 ?" w/ E
    6
    0 a0 n5 Z: ^$ J# y5 R71 v- z% Y% [8 y! x3 I) [5 E
    8" w' W$ E) r# `4 U  Y: s5 y! T
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。4 a( l; N+ L# |  Z
    钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    ; T1 E5 Z9 Z; g+ [0 Y, U8 Q. M- j+ R分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
      ~7 ]  @+ A: O) M* `对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。; y+ s" y: g. X7 E
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    4 l+ V# s" {/ z& z2 U8 K' L对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    ' {; D9 ], G0 i( q& {! @先看看数据结构:2 P! b$ q: D; J4 a& }: ?  O: w$ w1 u

    7 U3 R% x* H5 E" \% X/ g  {  Tdf.info()
    / }7 S$ ~5 o$ Y6 f) n. ]! z% u( ~* [Data columns (total 4 columns):. }) T! b' |1 `8 z. s- k4 A& \+ ~% R
    #   Column   Non-Null Count  Dtype  
    5 a( X- x' H; \+ u# \; {---  ------   --------------  -----  ; W9 k# C3 b" |* \2 \9 F
    0   carat    53940 non-null  float64% x( T1 C! O) Z: O  u. H
    1   cut      53940 non-null  object , w- \9 z. E+ H
    2   clarity  53940 non-null  object ! a+ K) u& E0 Q6 A/ d; O
    3   price    53940 non-null  int64  
    " U7 j1 Q9 V+ M% g$ p0 l! @dtypes: float64(1), int64(1), object(2)6 s7 T4 D0 \: Y$ ^: R) s) C
    1! h$ H( V/ ]; h! [9 R, _9 H6 P0 s
    24 C" r* v( t: `
    34 F4 L2 ]3 \; N  f
    4
    7 ~  E0 j& K. F- E8 r5% `. i0 K7 r" z4 C" z1 y5 g
    6
    ; N. `4 w4 B  q, h) S78 p* j1 O2 a5 H+ {6 H0 |& G- g
    8
    - y+ N( b* r  {# g0 Q( Y8 ^; W9
    / {2 l: _6 ^6 S( v& w  y比较两种操作的性能" V. D5 V. w3 B; `8 x9 i
    %time df.cut.unique()1 q8 v( Z7 Y$ S7 e4 @7 b
    . I; s9 G4 p( m
    Wall time: 5.98 ms
    - F# `, L( Z; zarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)1 k  z/ D# }. A1 X5 x
    1
    ' p. p# }% V5 L) o) e2
    . d% o" c2 b% ^" i! Z3
    - C  I' }% r  y( s4
    8 w; o6 k5 z* y) N3 L" ?; t%time df.cut.astype('category').unique()
    & K+ a7 ~4 A/ I0 ~3 K& S  ^! ]  k, f' }& C9 Q6 T# c
    Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms
    - H; k' X! p* t! R['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']& R% s0 [2 h0 n. X% p# H7 R
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']( H3 e) F5 N4 T! b% b. p6 o
    1
    1 Y. s& O  e6 W5 h" v2
    2 R0 ?% l7 ]+ x7 b; Y# U3% F% `) g8 a$ _; n( I
    4
    1 r, ]5 c' e+ X( e, L/ Z0 w7 _5
    + O6 Y9 A1 h* R$ Mdf.cut=df.cut.astype('category')
    3 j5 }; v) Y8 u- r- t# a1 p%time df.cut.unique() # 类别属性统计,2ms4 ]. j1 J$ G# u) C

    * A2 s$ @* s/ }  Q8 q% {% bWall time: 2 ms" k! n! I8 J0 Y3 m! {9 V
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    & L7 {2 N$ t0 r3 C) jCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    & {; H  u1 ?$ K0 Y1
    ' {7 J' F& H$ t+ R% d" O2
    ! ?) h4 f; Z6 X" D! P3
      J3 O- [9 ?8 h8 X4* `: m2 D: P# C8 b; I
    52 b. P& ^5 g7 _  [7 @9 b
    68 l4 v0 i5 @7 i* K  N# I7 b
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    . p6 T' e* o+ Q' f4 X( bls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']" @1 B/ k" p0 P/ q
    ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']( S" a7 t- J' H$ v7 C( \
    df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    2 O5 x! j' `1 y" l6 U* h9 p( Q* cdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)6 e7 R/ J, H% K) a$ y1 G1 h0 E

    2 J' `  g+ B0 [df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    / b" T* b6 _( w3 H/ v- g! k9 g5 j5 k0 |' f4 x% f0 g' T9 v9 e
            carat         cut        clarity        price
    7 O- V$ R2 K- R1 j3 X6 _315        0.96        Ideal          I1        2801
      s& _$ J. B1 l; h8 `$ j535        0.96        Ideal          I1        28264 }% D/ V  d* f
    551        0.97        Ideal          I1        2830
    / {$ Q% \& b8 O5 S; c" I7 v1+ m4 T" H+ |- K+ u$ h5 q
    2; y( J/ P3 m- m0 J( b) [
    3
      Z" h% @; ?- D6 X* ^  T4
    9 T% U' J. e+ O! {, b& |% G5 L8 |5
    7 _* {' ]+ N" h( E% U2 }2 m" l6 ~' b6- n$ Z6 F; T: {
    7
    . |2 e: H7 @0 P2 K& ^8
    * t" H9 z, _6 f3 w3 n6 E0 j9
    2 Z; v" C/ ^& }5 c8 |" ~10
    5 S% p/ m6 u) I& l% G' _% ~11
    8 i/ }! N+ B4 b+ _  H' U7 _6 q; [分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。0 E& j9 P; R. D  m" o9 }! R/ L
    # 第一种是将类别重命名为整数
    9 s* h- s% p! i8 _3 {% zdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
    ) W! P9 p: ^" v$ l  L, ]8 |9 jdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
    4 v7 I: N* B" O9 U+ n7 u+ `/ E" D1 O  `% o
    df.cut=df.cut.cat.rename_categories(dict1)
    3 D$ e" }  ~- t# G1 ~- tdf.clarity=df.clarity.cat.rename_categories(dict2)) R7 h4 W% u% r9 @8 ?
    df.head(3)
    % ]& E1 p5 U2 {- q9 d1 A
    ) B0 o. O" T0 C" e' H, ?* P        carat        cut        clarity        price$ ^5 T+ T* k5 R: [$ _0 {
    0        0.23        0          6                326
    9 M: t3 ~1 m+ w3 k1        0.21        1          5                326
    7 s# P7 t( [  G8 [, ~- X2        0.23        3          3                327
    5 P4 e7 W- u3 x" H1
    & W8 t. l, ?- |" S& E1 x/ F6 J2
    4 v! @' H1 Z# ]% H  l0 x- @3
    9 B0 p/ e3 ^; l: C: M4
    9 V& y1 L9 }  u3 c5
    2 D( n/ x+ r2 r! g( F6. [( O4 \% C( D) b2 ~* ?
    7
    0 Y2 m9 R8 [! a/ C8
    - T, L4 W$ ^; {  g90 I4 J0 F& Z/ v* q
    10
    5 Y. j2 y% |: Q1 a( e/ N115 \( e+ b5 B- O' _3 N
    12
    " ?: ]+ I8 X1 }' R& \4 E# j4 Y# 第二种应该是报错object属性,然后直接进行替换
    : c% m7 R8 }9 m5 d! k; n4 ldf = pd.read_csv('data/diamonds.csv')
    ; w. Z( ^* L# h5 [4 ?. l' |for i,j in enumerate(ls_cut[::-1]):; X" g8 X, C2 O' w
        df.loc[df.cut==j,'cut']=i $ \- i; A) {4 r% k' T, r  F( b, |

    . q# Z( Q' h' ]* R* s1 y% e# {' Ufor k,l in enumerate(ls_clarity[::-1]):
    . `7 O* J+ q8 j2 P9 w6 l) Y    df.loc[df.clarity==l,'clarity']=k9 `5 h; D' J  P. \4 A" p8 u/ {- v
    df.head(3)
    8 K+ ~6 S9 P: Y' N6 B5 T) V( m' Z) N. o
            carat        cut        clarity        price0 c% Q& C4 B; `+ I1 h+ Q& a
    0        0.23        0          6                326
    8 s6 z2 }" c6 t- D( |& D  k1        0.21        1          5                326
    6 R+ j2 E- P9 S/ A2        0.23        3          3                327
    * L7 N* Y* {1 z1 z* W6 q/ A, [16 N0 y, v5 u3 I4 D5 e8 y$ t
    2
    9 z0 Z7 u; Y7 ?5 g+ }3# ?) }: L! ^6 U: D+ f) w* ]- u/ g
    4
    1 x2 j$ A1 T, b4 ]5 d5. f* |- _7 f5 t3 C
    6
    : M( _2 N) p# ~6 M( Z8 H7
    ; {; }# W# z" Z3 T  i9 |8
    - k" X; h/ b8 T# u93 Z3 H( g/ V$ g) r! Y
    10' |  `7 O, [: W& u/ n; K7 C: |7 S1 v
    11
    ! h! G. Z" b" P) g, J% {12
    3 v5 I+ U$ N+ ^5 [$ Q6 |, C$ ~136 B% b( p! \2 m6 a* D2 V5 `
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。' m2 A; n, x! Q1 A$ I, k
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
    $ n0 Q- Q3 h% s4 ]/ ^/ s3 B& iavg=df.price/df.carat
    9 r; \9 [, [" b6 q* [0 l
    ! f$ ?* C4 F4 ldf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    ) v' L, P# f7 h# p) U6 i& s                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]4 S$ j8 M$ `  X
    : R2 B$ R) h: [. E4 {5 w
    df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty]," m0 O8 w0 o5 e6 B# \! p
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]9 I1 y" _5 [) N/ S2 N
    df.head(): A4 t3 Z1 n: t- N! {
    + G6 m1 N7 Q( m" E& D
            carat        cut         clarity        price        price_quantile        price_list; m+ R1 a( F$ n4 j
    0        0.23        0                6                326                        Very Low                Low0 S: l- d: ?: c, O
    1        0.21        1                5                326                        Very Low                Low4 k- y' L" N1 E7 L0 p- j$ M
    2        0.23        3                3                327                        Very Low                Low
    3 G4 w, ~5 O9 ]3        0.29        1                4                334                        Very Low                Low
    / i& Z& Q* I# c) \/ {6 j1 i6 y) f! @4        0.31        3                6                335                        Very Low                Low                                       
    2 E* t7 ]# K3 j" M' M9 I5 W2 L: o' z3 b+ U
    1' _; h$ w/ r& z9 j% X
    2; a* o% w" R" _" n. V( p' d; W
    3
    6 Y+ o: `( z4 G* y4* K, |' `* i3 |
    5( P3 N; C, z( r4 N) v" N
    6' z5 ?/ B" O) A, L! v
    73 t! l7 h1 f: x$ o- m; j
    8% `" y. z+ |$ T% W- ^  i+ n
    9
    / U8 N9 z6 i$ N- k101 Q) S6 I, p0 ]6 r  [! O$ G
    112 _" \0 b5 W6 i% V& ~
    12
      A5 [. _' N$ B# p  ]13
    8 G  B: }% j' n! b8 ]" y141 B6 h( Z. |8 _
    15
    , U4 h4 m0 \2 l9 T6 a16/ f" `3 c  F7 _- {7 M
    分割点分别是:
    , c; A8 E2 r* ?* a! a
    , i# v* x1 @% Y/ }9 Uarray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])
    ) _) C0 I5 T' P4 b" t0 Z1 f5 Harray([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    , y1 ^/ `4 m4 C; J' L: `# b  R17 S& m# t- Z- a! H
    2
      l( e- v# G7 z2 I7 y第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。9 H: z& f0 {* h1 X  k
    df['price_list'].cat.categories # 原先设定的类别数  K0 \+ T5 I, d/ [3 T2 F
    Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
    8 M, n  n: g: w2 G! y9 `
    3 q  a5 i. p, d# qdf['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别! ^/ o+ H& Z5 {; E
    Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    # z- G! N; M0 U1
    / K. Q. v$ a, w) `$ M+ ]5 B" ^21 ^- X7 y) T3 Q+ F8 R
    3
    / y% F) R" N8 V6 P( ^8 D. P; m/ G4
    ! J, k+ s8 w/ o; |$ j5
    " ~! S8 g) p& R- l+ Uavg.sort_values() # 可见首尾区间确实是没有的3 h9 H, g9 C( w: \% n
    31962     1051.162791
    : H$ G  D7 r/ z" a15        1078.1250009 L- g1 n/ m3 g! n! n% {# s
    4         1080.645161$ x1 ]2 r% _/ b" O
    28285     1109.0909098 P& I  _5 n/ T% ?5 w; h
    13        1109.677419; z6 D* H" j6 J1 r; |+ g  {$ _% D
                 ...     * H& t9 g5 j; X  h3 W
    26998    16764.705882# L9 \# P8 l+ {" a: j. _( W
    27457    16928.971963
    9 ~7 \1 M5 \+ {, T27226    17077.669903; u3 Y4 v- g  w& j0 H
    27530    17083.177570( q( [$ H* w0 D9 E" Z
    27635    17828.846154" S/ o( U" c+ f+ o4 |, m- W! P
    1) `# h, h8 Y- O  s( \. {* c
    2" N% b  o4 T$ u
    38 {& ]# e6 m8 q
    4& _  \* C1 h5 N
    57 m' o" q' c  W1 [
    6
    / n  M  O8 l3 c: J2 f$ p7$ H& {3 u- }: b4 l
    8
    1 z7 g7 Q6 M: g% m# l# Z( S9
    3 Q9 r2 O# r2 N- r10
    ) K0 Y' h. Y- s  w* y% T  D- x2 P2 q11% w6 N- J5 C2 v0 _9 s
    12
    7 L# V6 d& h4 D, Q0 h6 g对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    3 J+ K# A/ E, r, f; ~# 分割时区间不能有命名,否则字符串传入错误。
    0 n) G6 h8 i% g. qid_interval=pd.IntervalIndex(7 r4 j, H. l% H; M+ @
        pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]& q  D% l$ o. ~' [
                                )
    3 r9 w$ A& K& ]# g, u9 Sid_interval.left
    - z8 Z% |  I: w  U& t) y9 |! Nid_interval.right
    0 @+ i: V4 x9 n% W$ Pid_interval.length                            1 [/ j, R  k7 k4 I$ ^
    1- T# J* u: j2 R! W( m0 o
    2, j# b# N3 U3 O6 a
    3: W9 G6 i( l: O; N0 K$ i
    4; p4 F. Z. A; ~$ X4 a! [- {
    5
    5 x1 d+ C+ l# D) f6
    ( u1 t& E+ y4 T& b1 K# I+ {77 B4 N7 b. C' n/ I; }
    第十章 时序数据. y* Z3 w4 Q8 n
    import numpy as np8 k. h# u  Q. Z9 P
    import pandas as pd! W3 a+ a3 S/ P0 n
    12 e# e0 n- m2 L$ c/ I
    2/ `' N  k$ S. W5 V7 V( ~
    ) k& o" T1 ~: ?

    & P9 f# a3 Q! a' S' a10.1 时序中的基本对象- q6 y! ~1 ?3 n
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?! r2 d/ U8 G# j* l1 Y& W
    * w% S6 [& z0 a5 h
    会出现时间戳(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的简写。
    $ P# a  k  C( G, Q# r+ G
    # t8 u, i  Z$ C# c/ d会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    ! B9 q) M. _' D, c9 `% w& ]) G; U, n
    * H( p& k6 a9 J5 T4 u7 k会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。/ Y4 ?. S% R! s5 {2 g, g) G9 r9 a
      s  q+ d0 v  B, o; _& K) ^
    会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    . ?: @3 d' h! F8 b# ~
    % u; y( Y0 m, [: y3 \  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
    4 {! q( `5 @- j. K6 j& W" p3 R$ x$ J
    概念        单元素类型        数组类型        pandas数据类型/ t) m6 c' o5 X+ ?
    Date times        Timestamp        DatetimeIndex        datetime64[ns]- h; ^; K) Q/ w/ C+ `: f! B! ^
    Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
      K! C0 d. x# g! LTime spans        Period        PeriodIndex        period[freq]
    $ l3 q/ v; q1 i; V! y! YDate offsets        DateOffset        None        None0 ~& C+ O' @6 U5 M
      由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    " H! z5 V2 Q7 B0 y9 s1 d! L( Q: j' f# k5 N; |
    10.2 时间戳
    1 y) K% l8 E/ r$ j  f  z10.2.1 Timestamp的构造与属性1 ^3 D5 N; l# p  {
    单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:. m  Z  r" y' e2 m
    + s9 u+ ~3 y& x: w! p* P
    ts = pd.Timestamp('2020/1/1')0 k: F) i; a" K# X& F
    7 S( m# F4 L7 x" K
    ts
    2 N2 |. t( a% y  U- W& oOut[4]: Timestamp('2020-01-01 00:00:00')6 S% J/ L/ @! R

    ' L0 I6 b* X  \1 Wts = pd.Timestamp('2020-1-1 08:10:30')
    ! T+ B) O2 N# _- W
    1 k6 I! ^# v0 V- Yts- i, r  m) L- x  C+ \, _
    Out[6]: Timestamp('2020-01-01 08:10:30')
    ; p" w5 d5 y! F% A6 E1
    / O4 L3 {5 D+ |. U2
    % X+ c- N8 W, y4 C7 T0 _34 ]1 n# Z) B1 A+ v$ M$ ~; S, B
    4
    . y- e: G+ y* g, z# D7 j5* Z+ I# l$ \( a: `( u
    64 B' l7 D* a  F- a; k& ~
    7
      x- V6 ?" @& b8, G" z9 G, P" ?, d6 J7 r9 D* u$ e
    92 D0 y; m9 o: g, P6 V
    通过year, month, day, hour, min, second可以获取具体的数值:- Z3 e3 @) \* J/ D

    3 x2 v) X' f) A( ?- k4 ~ts.year
    + L2 x9 d9 O' @* [Out[7]: 2020
    9 E4 U$ ~; V, l/ Y6 @# c0 F3 |9 B( x% O! M5 V
    ts.month) o( p5 x6 y# l- V" N. Y+ k9 d, M
    Out[8]: 1
    0 ~$ y/ u1 ~9 j$ {0 T8 t) l# W5 z! \0 t  W2 k7 x1 F* d2 I/ L
    ts.day
    3 [, O  }' q  g; t& i. u; z1 ZOut[9]: 1  Z: p/ i. c. ~
    . f- l/ z9 _1 c6 W
    ts.hour
    $ `  T$ M4 b/ |Out[10]: 82 n/ L  n/ }* ]
    . h- t/ F. {; e6 I' K( ?% z
    ts.minute
    ! A4 }& I" y2 K$ k2 \5 IOut[11]: 10, U& `! C% v: L/ p1 [
    " @8 p' l- N4 u; m
    ts.second" Q6 c) F/ u$ N5 }2 h7 j1 {8 W
    Out[12]: 30# E5 G4 N0 a# @

    3 l, e$ e; ?' q3 O' l12 `( `, N/ S* h4 g" ^
    2
    8 o4 m; H* G7 l32 x# E9 X7 e8 f
    4
    - g9 }* Q, i6 s3 W- K5: }, u% b! P' n* B  X
    6
    " ]( v! Q1 \* R' ^7
    5 l) q# \! o9 n8# R5 A' _3 f- p9 t: w
    96 `/ ]# ^/ Y$ z9 R& D
    10( y+ Z1 g9 E1 h, m
    11
    " e6 G3 B9 K4 c# K- e12" D$ N( x7 P+ v  c8 T$ w! ]  B
    13- j) Z! X3 S# E, I  h3 e
    14
    3 r6 }5 t& N: [- K15
    1 _# G5 h: M+ Z9 O! }: O16
    $ }# M  O' z, X5 i! F17! x5 B8 M6 R  ]+ C  p
    # 获取当前时间
    $ u# I4 j! A' M" ]now=pd.Timestamp.now()) A/ e9 b  z7 @$ @  D; u" W, U! S
    1* ~2 d" i" i  X; v6 m4 m" N4 U/ T: K6 Q
    2
    + _0 Z+ r6 D. {: s+ X7 s$ i在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    0 v8 \: Y) Q$ j1 \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)
    + ^  p8 X* L7 ]8 Q9 o& vTimeRange=
    - l6 h8 M) {0 E# Y! L! R10
    % l; P- T5 e$ X8 H9
    4 p8 e0 O! p- a ×60×60×24×365
    6 s9 s/ ^8 f# W  }- [2
      d0 E2 w0 w- ~$ o64
      A2 H. i8 }  e' ?4 F5 x+ m/ I, \% m, m7 M1 q+ F7 d( U; \
    ​1 G7 q) ~8 n% M  R- q) w& ~
    ≈585(Years)6 f2 c0 E/ l: v2 l
    : Y/ Z3 f/ F+ X& ]: t! P
    通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    % @- r* m, R7 a; w3 r" `
    * z& T# j6 z- S' Q6 T/ Mpd.Timestamp.max, e0 ]* |' N& x% u+ a0 z
    Out[13]: Timestamp('2262-04-11 23:47:16.854775807')  n: l9 y# @# n0 z( e5 ?$ k, Y: Z
    $ D  r& W, v1 [, K& ?6 n
    pd.Timestamp.min
    1 W- L' U0 {1 D% r  O# [Out[14]: Timestamp('1677-09-21 00:12:43.145225')
    8 z5 G2 S+ X4 M9 Z
    $ y7 ~1 |6 i+ z/ d5 Q( d7 E2 Kpd.Timestamp.max.year - pd.Timestamp.min.year+ Z$ @0 \; W4 ?" C" G1 v) c& {
    Out[15]: 585
      J: O) \0 Z) k, W$ T5 V5 D1
    - ]$ [( C  q% Q! u- k27 B! A/ |* @( ?, `7 x. h6 C( L( ~
    38 G7 m. B2 `) {
    4
    - A' H9 @( Q* v. l/ Y( o53 K) Z6 Z% ~/ p# e2 F9 `
    6& _* C, k! f0 h7 L
    7( u9 k/ J3 a) b) l" T
    8
    ; n; V9 k9 t( z* j8 {$ ?) V9 ^10.2.2 Datetime序列的生成" H" U6 ?7 T) o0 L+ ~
    pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
    ; M; a1 |9 K. {                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)4 f5 Y- D. d* B, Y
    1
      t1 A- P/ b0 z6 a) B/ {5 `2
    / v+ J; i4 p( W) S! @9 p% U0 F5 Y* ^pandas.to_datetime将arg转换为日期时间。
    % ~3 ^! T. ?) N2 Y
    ; G4 j' ^" I# Zarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。  H5 b( ]& o* M' e9 b
    errors:
    ; U# S% G) z* d/ f- ‘raise’:默认值,无效解析将引发异常
    : E1 `& {/ Z4 p% A1 u1 l; R6 K2 X- ‘raise’:无效解析将返回输入  v9 [+ |4 l! b) k; G
    - ‘coerce’:无效解析将被设置为NaT
    ) Q5 @& L8 n& }9 A. pdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。: c% x' T0 d; p6 v% M5 Y! G. {4 p
    yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
    4 G9 K1 z2 |1 O' Cutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
      Z! |5 F7 `; yformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
    0 `$ U' d2 b% e4 W# {9 B+ Ounitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
    , {5 ~" l8 F5 l  O( ~3 i* zto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:/ n' v" V+ X7 w- m% Y
    pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
    . s6 J9 R8 w: t9 d( z  o/ D( m& Y9 V* L6 S. h1 z
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
    & h  U3 Y8 T1 K17 E, B/ P2 x! u2 x' J' J
    2
    % }! y: w  n1 H/ y% g3, L" y7 H2 n) e- r& N2 y
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    $ R+ z5 Z& _4 h+ i( y! ~5 s" x/ g4 m* o
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
    $ `1 Y  \$ A+ ]% ?temp7 o) L  ~3 D" m) z$ ]' M3 Z, ^
    ' j, }4 f; n; ]
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)& t( ?- S; z0 P3 S
    1
    ) y3 z! ?7 j: I4 w29 S$ B) o9 I9 ~  ~
    3
    , w( p" ]: m: v4
    * r4 L" b( v3 E% J  W  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
    1 O3 X1 ^. ?6 i5 \. ~1 d& x/ g
    9 b! j5 V+ I0 Tpd.Series(temp).head()
      B, q% m9 S6 j( I1 G7 I4 c, D/ _9 M  S% n) W- a5 {0 k" B( `
    0   2020-01-01- N3 r# F0 Y- Z% `$ _
    1   2020-01-03; m4 a; k9 @6 D$ P4 T: P
    dtype: datetime64[ns]
    2 o! h6 }1 l2 U. F  |1
    3 E, [/ y3 N! G* d5 n8 `9 g2
    ! U, [* G: d) t# I+ T/ y+ v3
    ! j8 Q: E3 P# A  U, J1 g) y42 X0 C  S( I( f8 w% C! a2 o0 ?
    5  b, Q) W! b6 v( P
    下面的序列本身就是Series,所以不需要再转化。: U" O5 Z' L/ Z5 w, u& s
    ; _. s' h# @1 u1 A- a( t+ ~! A
    df = pd.read_csv('../data/learn_pandas.csv')9 o; f* a! _; H
    s = pd.to_datetime(df.Test_Date)
    ( a: V& m5 X  }/ ys.head()
    ! B, W8 P" H4 t- ]* p, }
    : W0 k4 R& q3 F7 N9 x% `+ q0   2019-10-05
    4 K& }6 T  T3 d, Y1   2019-09-04
    6 D3 C6 `4 X+ t$ m5 t3 P8 W2   2019-09-12
    6 v% N# |6 u, N$ K- r1 v3   2020-01-03. u6 I8 n0 R' I9 r- l5 h
    4   2019-11-06
    + w) O% e5 }2 V& jName: Test_Date, dtype: datetime64[ns]
    1 h+ m1 E4 E: @5 O, k# V1
      o" j" m5 X2 f) ?29 B! Y/ o  K: e$ u4 x0 |
    3" F0 S! e1 H& J" n4 k9 J- ^8 ?* k! g) l
    4
    + S( d6 n0 t* o1 _1 i6 {- V55 q# A4 `1 L/ {; M9 E
    6
    4 k: V/ ]2 }/ p9 r7# d$ m" S- @7 h! W% ?8 q3 \
    8
    - S5 I7 z# H7 k2 j0 }; p9& S, ]9 Y  |" N0 b1 k5 [" q* R
    101 k0 t/ Y: t8 k5 X/ r8 \1 a5 G
    把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
    $ D& q( C9 n* A1 Kdf_date_cols = pd.DataFrame({'year': [2020, 2020],' m) Z; l% B! c5 s
                                 'month': [1, 1],  N" Q) i8 g/ X; e1 I' y
                                 'day': [1, 2],: [2 @2 o* c4 J: F
                                 'hour': [10, 20],
    ; e7 x& o+ f) z. F                             'minute': [30, 50],/ |  h! n% b. m5 x
                                 'second': [20, 40]})+ V# `# I) d" F* y
    pd.to_datetime(df_date_cols)
    & s( Z' `; t* S5 U6 E# z9 [2 |6 p  w8 j% Y  e+ f
    0   2020-01-01 10:30:20) W+ n& i4 _% r8 y$ b& y
    1   2020-01-02 20:50:403 n- s2 x+ q% I: Y$ ]: J6 m
    dtype: datetime64[ns]
    - ^( U, x8 H+ `18 d* v! e; e+ z( v2 m" s
    26 A! I, j$ i. W5 h* i' F. I8 ?
    3
    - X: V' M$ J: I4" |# f* t$ l  _/ ?; j
    5* {, }7 N4 S. C  z
    6
    % _8 K) M4 q- x7
    ' H7 l7 r0 A' P) m% |8
    ! ~3 M8 y- T' _" Y* d90 d$ ~, {% I1 {
    10( o6 S& M9 ?& l2 [* J
    11
    . Z" U  y  C+ ]. i( F# y! S) L$ ^; Jdate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:) f7 k3 A0 ]! H  C+ g, `' N
    pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含) ]9 v5 I! o5 k4 G9 ?
    Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')' q; \* b9 Y7 _% ]

    # O( B5 x- |6 S3 r2 ~' R  ~pd.date_range('2020-1-1','2020-2-28', freq='10D')' v# E. o" z2 p/ C+ d5 G9 T
    Out[26]: 6 K( T/ k. q8 D/ v  h6 i
    DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
    ' _0 N: F; @; Q* n               '2020-02-10', '2020-02-20'],$ `- M* m& M' J: S. D  E% z. c
                  dtype='datetime64[ns]', freq='10D')  F6 N$ }$ }) K) B0 ~) v
    7 p; W% |* r- J' d0 |4 T5 r' {. q
    pd.date_range('2020-1-1',
    3 k& l. U7 ~3 g; b; [$ F! t              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天& F7 f, K' a# @% A: V' _& G- S' p/ x

    + j& u7 |8 O8 g( y3 p$ xOut[27]:
    5 e: h9 v4 P  f; a$ B8 x9 qDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',. S3 l% _8 ?2 R* x: F
                   '2020-01-24 04:48:00', '2020-02-04 19:12:00',! R4 \. Q3 O" C8 D2 B
                   '2020-02-16 09:36:00', '2020-02-28 00:00:00'],
    & W; w+ M& `, j0 V; b7 a              dtype='datetime64[ns]', freq=None)
    ( r* Y% w! U7 `1 t5 A
    2 Q! M8 _! |9 g/ t$ B1
    7 w/ C4 l6 r  ?% n# C& s' A2
    % o+ u1 z! i. j) v  J30 F' K; T' A% O: F  S0 s
    4
    . Y4 x- Z$ c2 R# R& o' Y. ~) N+ B- W5, c+ ?& r5 f5 E8 A7 Y) _# ~0 _
    6" D/ W' [$ N: b# K2 H
    74 b/ ^1 u2 X7 f/ _
    8! O# N4 b# F2 N0 c; u- J
    9+ A9 L& _' r# ]! u3 ~- a0 V9 t
    101 C- ]$ c& p" r. E" g
    11. I6 F. o; q% M9 D, V$ ]! w$ u* ~
    12/ s" o0 G% ^. O
    132 e* G6 d, j6 \8 q4 E8 {* n
    14
    7 O% b/ ^) E* p3 j0 ]15- f/ K5 z) o( {
    16
    $ k: A0 r2 n& i7 ?3 S! B174 k1 n+ @6 J, C
    这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。0 d* j! N; X: u
    , C3 V! [, Y2 F' }
    【练一练】
      L! C" g0 V) M9 f' {% ~) }6 [5 ]Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
    / B% _. ^' g- R$ O
    $ g/ _- Q* q( M% `. Z% Xls=['2020-01-01','2020-02-20']
    5 V" R7 S" K" X+ P& s( @5 Gdef dates(ls,n):
    2 i. z* P  a0 |9 {    min=pd.Timestamp(ls[0]).value/10**9# b9 @" s2 h" r' ^4 h
        max=pd.Timestamp(ls[1]).value/10**9+ [; r! p7 g& d$ }' K5 ^% }
        times=np.random.randint(min,max+1,n)
    3 _& g7 \" N7 V# O    return  pd.to_datetime(times,unit='s'), H' ?: T' ~- M- j
    dates(ls,10)
    " ~/ d$ t7 ]% K; j, q8 Z* o3 J* n- c- }4 j
    DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',$ U4 s) {  h. v1 U. F# T7 W
                   '2020-01-21 12:26:02', '2020-02-08 20:34:08',; p  |/ F1 V" v6 b
                   '2020-02-15 00:18:33', '2020-02-11 02:18:07',
    8 `6 N# k, X& T' i               '2020-01-12 21:48:59', '2020-01-12 00:39:24',
    + z6 S% z) m) w4 I. S               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
    " c) Y0 Q: n* W0 l$ h5 u6 c              dtype='datetime64[ns]', freq=None)
    ; A6 ~: b1 o% j" |( ~* o/ [' {1
    " S# `7 N; v3 h- O- _' I2
    1 s  {8 ?1 ^6 \; a$ U3
    4 g) G0 Q9 \, C4
    0 n  O) D2 K9 w# D& m3 v4 J5! O+ s% C: E) t  d
    62 x- l. V3 ^/ E
    7" ?% c) H$ A" c3 D% ^
    8
    7 \: G% s/ K" v* \9
    ) q% r- N) W( Z  j* t- |! N3 C10
    3 Z( m4 h4 W! f5 [! n# ~: t11
    % p- a! t" e, T8 g12
    + s# k# e5 V( M0 @8 N13
    : C# M9 X' L9 V- I, o; H149 {) F8 O5 ~) |# L% O( u
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
    3 f1 T7 ^" \8 J, t7 v- d* Js = pd.Series(np.random.rand(5),% {" A0 M& ^: c$ B; u0 O
                index=pd.to_datetime([' R9 J0 V% E7 `- a" D- c7 y6 M
                    '2020-1-%d'%i for i in range(1,10,2)]))
    8 b) m$ @) T* k! E- F  q2 p, K
    ! a9 _7 U" ?9 u9 T; N
    s.head()# v- Q' P; N1 e; s; h; G/ D1 b
    Out[29]: : L' u* |3 {5 v; B
    2020-01-01    0.836578
    / @  D9 h( w: x3 M! m0 S6 a5 o2020-01-03    0.678419
    ! x7 H0 B1 }. O# S- f7 z2020-01-05    0.711897/ M' U' E2 T$ W3 o! V
    2020-01-07    0.487429
    ) X" M) e  y# q, p2020-01-09    0.604705# v( c% z5 a  B2 W# B$ p6 U
    dtype: float643 J+ E& P/ H7 v2 v5 D

    0 S- v% B+ `% ks.asfreq('D').head()
    8 f& Y. P1 ^/ ]5 uOut[30]:
    / \) u/ w6 D8 W9 U% q2020-01-01    0.836578
    ( U' h/ O7 A/ H2 d2020-01-02         NaN8 {9 }) b8 N) k. y) @
    2020-01-03    0.678419
    1 j6 M' V5 x5 D! y$ Y1 e7 j2020-01-04         NaN# D, y0 i5 L" C9 e! F
    2020-01-05    0.711897
    6 q( \# E' z1 D- ^. @- B3 GFreq: D, dtype: float641 G$ _7 \9 H  D& ~
    2 F! ^% [2 O+ T- i- `" t
    s.asfreq('12H').head()
    * P7 i2 a, I! B4 n/ `$ Y# g) sOut[31]: 8 p" x9 Q! q2 z, ]
    2020-01-01 00:00:00    0.836578/ V. P, j7 p3 P7 f
    2020-01-01 12:00:00         NaN4 M1 g4 C2 A. ]9 d1 I
    2020-01-02 00:00:00         NaN
      G6 V! F$ q* F2020-01-02 12:00:00         NaN) w5 f3 i' ~: L! i, g/ o/ [: d
    2020-01-03 00:00:00    0.678419/ a$ A& W2 m2 U8 [
    Freq: 12H, dtype: float64
    - Y# I7 y3 ]+ G5 l8 A2 P3 [* R2 x5 B- j' `8 j* _5 d9 l2 A# O
    1$ m! I2 {9 q6 t% m0 y
    2
    " n9 S' s0 D$ l% J% ^- w1 U/ M3
    . d! M, J% X) R  z2 v: g4
    9 D& g- o4 g! f0 y- N4 v59 I  w6 O5 b) T; C9 A
    6
    ' A* N' X# ~) f) d. q3 T: z7! @( C3 J) x$ C- s. @! W
    8
    6 u1 y3 G  V: L- m) G+ O9
    . K; c& U) ~  T  @. u1 _; n, H10
    6 p! Y% ]) p4 O  ^9 J+ W11, B/ a0 n0 F7 V- v
    12# W( ^/ ]0 W/ W& V0 A3 H( M5 H
    13
    - Q; X5 A9 J3 t' s: K1 f14
    . H: ]: a+ ^5 t, a" a8 n9 X15
    9 l+ E9 m! }, Q  |3 M2 T6 G+ u) E16
    ! L; u* Z0 _* z6 a& t17
    ) [0 z4 W, Y( d$ g, N  r4 ~18: L' g! g- O* I, G
    19
    . q+ \  E. j2 b" i4 b# _6 B$ b/ V20
    # D8 I, b' t" J21& K1 @  S8 G  N: r
    22
    . m0 u7 K- H' ?& R" S23# G9 H: A. g/ q/ Y7 C
    24: g% g4 C1 N6 C
    25
    # @/ U+ ?" C7 |/ f3 z26
    8 Z9 D2 z/ J$ `0 O27
    / e) j) }- p( B- {  u$ D  H* I282 R/ U* P  V7 [6 ^
    298 n9 f& H! @, Y9 v# z
    30% O+ `- r9 \1 `# ~; X" o5 {; J+ J
    310 ]5 A/ B& _2 T) ]2 H5 q# m9 v
    【NOTE】datetime64[ns] 序列的极值与均值& z1 A' c, n! H4 J  ?
      前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。) L8 s( i2 f! z) Q
    7 J: f# z8 L: r, ~& I
    10.2.3 dt对象
    % D0 F7 q$ w# o) n! @% ^  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
    - r7 m- O7 M% f* e; K4 l' @, T! P8 }+ y9 r3 k1 W# V
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。. W7 x( `! z# q
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))5 Q/ N  w6 R1 D& z" h; ?
    ) ]& ]. x  ?  G% ~7 q# t3 f
    s.dt.date" d2 R& r+ Q* W, P. d" ]% C
    Out[33]: : w& V% d$ v* a
    0    2020-01-01
    9 d* I2 `% ~  e: A1    2020-01-02' C0 H7 o  ~( X6 b" W5 s
    2    2020-01-03/ b8 y5 w6 |( P3 m" b3 u: p! [; S
    dtype: object
    & [: W  e# m3 z) Y+ W7 C2 j' g& M2 t& d7 U  a" S5 A' s
    s.dt.time, i5 f) a  h( F" }
    Out[34]: . v* |6 ?- t8 y% V6 u  Y
    0    00:00:00
    ! i  s6 I% q; J, M- g1    00:00:00
    # E$ g4 s! e, q& z2    00:00:00
    0 b5 K3 t- w; T( f+ m0 ^dtype: object
    . u/ |/ e: X8 i. q2 e( O/ h& e1 \; q4 S
    s.dt.day
    8 n0 S. @7 A- @( U1 i& C% _. cOut[35]: ) B3 h$ ^. |1 E! C2 R
    0    18 c. ~9 p$ V! C/ u2 b
    1    2
    ) S* u# H" Z! r% A/ T( h. `2    3
    , y/ I. {! f  J" Jdtype: int64& o1 q/ P6 z  L6 R% ?
    5 O' @& E0 _! [
    s.dt.daysinmonth& Y3 n# X2 l: _' Q3 l. z
    Out[36]:   w6 w, a- ?- T) B! J( Z& v$ U9 [
    0    31# }$ D/ D3 n1 c
    1    31: W4 ^* ]% Y% W. H1 |5 B) y, f: t
    2    31' I, m+ P/ m! r' T
    dtype: int64
    2 B: W3 `: ?) \' y- n% J5 k5 M9 Q/ J9 _
    1
    3 ^: P# u& n9 `& {% k1 {2
    % ?# E" S* ~9 k" W; O# o3+ S0 @9 A9 ]+ w9 f1 G8 k# r  z
    4- X6 X5 F6 B6 X2 M7 Y3 O- \
    5: L3 v2 y; D0 U
    69 U/ H3 A: D2 f8 ?7 o/ ^
    72 q3 R* v$ t, ]0 P+ t) U$ U) D& n6 ~& R
    8+ P! _8 g2 _/ H1 J: ]
    9
    , I; N1 D; x6 G( `9 m0 [6 L10
    : W) H! X4 [( z1 s11; S' U# \( H: Y8 M
    12
    2 p, r* A) C* s6 f( M2 O8 v; v; ?13$ m. l, _# q, L5 _% I2 R
    14# N) O- C; V7 o" _' g; L
    15
    9 D& I, P! V* N7 O16
    1 P) l6 w* R, t* g# ^: ^17
    . n, }) E$ Q! a% W( }- t2 q+ C189 G+ c$ p' M- }: P, d
    19
    3 K3 a3 q' g! [6 {20: `1 j: }& |% {6 A5 Y  S1 O. Z- E
    21
    $ |5 ]6 p, y4 n1 [7 D6 V22% c& o* c( `; |" c5 {
    23
    ! p5 B% e# f& f24
    % M: I/ u& b5 d; T4 C! K" @) ?" W25$ y$ U* q; m+ Z. M$ C
    26' V2 B  L, k& x, O& ?7 R5 S
    27# ^; `  i  O5 ?' D: F
    28: V8 q( j4 d( |4 H
    29. X. S1 B( Z2 P. U& \
      在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:( r# A. J: F* Z
    7 Q& Q8 u8 E8 @6 q% M8 _1 Q! `
    s.dt.dayofweek
    . ?% c# Q9 N' t) e7 D" B4 ~Out[37]:
    ) t  b' X% Z1 I0    2
    & T" V+ t  @; R9 v; N1    3. q# ^2 v* L1 E' f+ f/ C- x
    2    4
    # ~( n5 l6 o) w. [8 t8 S2 a) F* ~- Ndtype: int64
    : d! \; h. h9 z( y8 ?& M' S9 N! G- F: L5 {( B7 |! }3 T
    s.dt.month_name()6 e. x0 }; T* w3 T
    Out[38]:
    9 l" |* V) J# a* b: M# ?0    January: i: T$ C$ v+ k
    1    January
    / j: |6 ]* v5 h. }5 ^+ H2    January5 x5 b. G! ?. k
    dtype: object3 u& O, i2 D( H0 m0 p8 m9 U
    2 d) t  Z" s2 x5 d* e  c% H4 @. L
    s.dt.day_name()# V! j4 s2 O, ?0 ~; X' z- ^
    Out[39]: ' G4 x, N5 n% H5 P' {# Z' G
    0    Wednesday
    1 y: k9 L0 W+ j2 M! }# h9 m- W1     Thursday7 I, g2 U! x! c& k/ C
    2       Friday
    % X9 ~+ {: a) Jdtype: object. g+ F$ f3 k8 Z3 T
    6 f7 U1 S& X! v. S
    1
    - G/ A+ ~- R# E* p+ ?9 E5 ^2 h6 x/ v2
    9 ~2 ]6 O0 c9 v3
    ) K6 \, d, R/ ^, y/ P; Z$ _/ e4
    1 a) {% Z0 A8 D: a2 T& a. U5& X) @( [( t/ P, k$ K' `
    6
    % p" f; E5 P7 C, L7 b+ V) P7
    # F( f6 @9 J3 ]' G7 Z3 d0 C3 b8: \9 ~# y7 Q/ j7 p
    9
    ; v5 _* U! l. \5 z10
    - Z6 `1 {8 c9 m! D1 W" n  d$ q% L11- X/ a( z" a5 Q3 a7 @. g
    125 _' T. s1 j# }. i3 }
    13, F! K* L, E$ ~$ @+ x% c% X  a# D
    141 e- O: C! A4 F) K
    15& Q0 W7 F, {! T- R
    16
    : [' |2 Y8 [( G5 }, I171 Q* W0 F6 n/ a. v9 t2 @# \/ u  P
    18) `( k3 M9 [9 l" D$ ?4 z
    19% H; X, v  `. `7 \* J
    20; l7 s4 B  j+ \: c& _4 ~
    第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
    & c* q/ O3 t' ]0 P8 e' y: i1 @s.dt.is_year_start # 还可选 is_quarter/month_start: M) {0 P+ e+ p4 d! Q! |
    Out[40]: 6 e2 q$ U. H1 s
    0     True
    ! B* T/ Z2 e5 Z1 _- |3 I! u1    False6 r! U; C2 h# U% b1 u2 M) i
    2    False! B% Y4 Z4 A9 `. s
    dtype: bool
    / ]( j! h3 `; g( w: V
    / ]1 `( k9 N) v9 Ls.dt.is_year_end # 还可选 is_quarter/month_end! L3 a( |4 k5 b$ e( H0 s- ~
    Out[41]: 8 d3 r9 k1 K, s  z* |6 g3 a
    0    False% [! M5 K8 q8 s/ `9 C; m
    1    False/ s% g+ o! Y- ^
    2    False
    / c3 ]/ u& ^" r, ~! l- ?dtype: bool
    6 Y8 I8 C& c  D5 U( q" t2 T8 z7 [- a1
    # t9 ?  E; |4 J" y: @2
    $ }" y. W( z, I' w6 H9 L3- c& V5 A- U+ ]5 _/ u/ }
    4. f( m9 O$ b7 j4 L
    5+ e0 [+ u( s3 G: f
    6& I) P0 |3 K! I
    7. ]9 f. g  {4 h7 ]1 I: K9 J
    8
    : i: p; W+ A& U, U5 \) t2 e9; e. ]' ]3 N* i2 _4 w9 @& @
    10
    9 W3 w2 I3 k2 D% n' j5 @. w8 b) p11+ z. Z8 O& b* _' E% W2 X" s
    12
    1 ~! C5 X3 u1 X2 X0 e2 V13
    - ^, c7 `/ r" Z2 J) X% O6 T# }第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。0 u; q) A& m2 n6 Y+ c
    s = pd.Series(pd.date_range('2020-1-1 20:35:00',, X% s% t7 u: R" `, v$ p9 m6 C
                                '2020-1-1 22:35:00',
    2 C% |4 k$ T0 S* N* L( Q* D1 D                            freq='45min'))
    - m( Z; V' Q% U
      p4 o2 a6 F; g+ e1 C3 A- R: g# y/ X: T3 d+ |$ K
    s
      F4 M# e0 i6 g  D0 f9 L) ~. LOut[43]:
    2 G9 v4 F' s; F* n0   2020-01-01 20:35:00' k9 I  r4 _8 G- |- x7 E
    1   2020-01-01 21:20:003 d0 M4 a% G8 T  ^5 f3 m, \
    2   2020-01-01 22:05:00
    / Q* Y+ h. v/ l4 ndtype: datetime64[ns]( ?1 T2 L8 K6 |( C

    1 b& E$ |2 f& N1 a6 S" T( Ms.dt.round('1H')6 Q, e+ }, O3 S* X. n( ~
    Out[44]:
    : ]5 y3 w) H! z# U9 ^0   2020-01-01 21:00:00
    ) l+ c* f; }% U  E# `! R) B. s1   2020-01-01 21:00:00$ _# r5 p6 b* B7 C9 {* F+ m
    2   2020-01-01 22:00:00
    2 q2 L$ L, G. ^4 k) U( g5 ]/ p- cdtype: datetime64[ns]4 @( I) ], ?+ `. g! H9 g

    , M# ~! e5 z+ A( _3 ms.dt.ceil('1H')! k* Z) M) Q$ O* g6 F/ E, v
    Out[45]:
    # M# _" w  C/ q. N3 T! y0   2020-01-01 21:00:00
    - K. }3 `5 u" z) ?; f3 T" T1   2020-01-01 22:00:00* l- @6 A) i( A4 D5 F. r) ?1 E! n: t
    2   2020-01-01 23:00:00( ^& ^4 ~9 R, F& R
    dtype: datetime64[ns]) b! s, e; ]# s+ Z& Z

    ' u! X2 A& v! Z! j1 A+ k5 I. H2 Bs.dt.floor('1H')1 _6 H$ v7 w3 b; U5 U
    Out[46]: ( Y  [% o: J- U6 w. {
    0   2020-01-01 20:00:00
    + L: y* j4 j3 f3 u/ w9 X1   2020-01-01 21:00:00% e& w0 @) p* w6 \! `+ Z
    2   2020-01-01 22:00:00
    9 a: k( o8 l- H1 @# s- mdtype: datetime64[ns]! D/ m. p& f% H  B) `0 E! T3 z

    ! c6 f4 @6 o. }. E16 U9 Y, P/ c& S/ a  [2 k
    2- A3 i& N8 z$ e; Q$ B- Z. L
    3
    6 k; U4 K7 E: Y1 M2 q8 r4, y3 z! P5 X" d6 ~
    5
    9 g& @7 l! V' q# G- G6 _: Q6! L: {% [' H1 n
    7% s1 q7 K! M, {0 @' }$ V1 Q9 z
    8
    3 m" n$ b# h' M4 x& r3 ?9. Q, @" P; p8 a) s
    10) ^& L9 ~8 e2 ?( w- Y( c, W3 G
    11
    * t  [, Y8 d1 b4 i1 t12
    ) c! ^- D' m) H6 B13  D- l5 R& Q" `" ?4 v% F
    14
    4 Q5 [/ [' u3 h# N6 ?% q4 }; ]2 D15
    $ _3 A" w0 K7 a! ?1 r' s16
    / b  u; K! J; A; s0 q173 k! R, W3 i) r/ P6 }, y& N
    187 N( f" X) L+ ^) _
    191 G5 Q: T; }: q5 h
    20
    : ]5 F" ]6 q4 i/ K+ ]8 n2 B21
    ; o6 w* I- |+ [, Y22
    8 h& H* m' X8 e0 J: E. z8 u% N, }23
    1 n/ y( L* |' ^# X240 x3 Y6 H% @0 @  C
    255 x  y+ P% N$ O. t* M- s
    26
    : S/ P7 G( }0 S+ H+ C9 _27
    * k% m: j, O2 H3 q& Y6 t& e28( x% ~" k4 G) i* ]
    29
    8 z( f6 L2 z; Y0 ^; R30
      s' X; t6 e+ ?2 R31
    / V* v+ x7 V( }6 h32
    8 W3 O0 W" @* k0 u10.2.4 时间戳的切片与索引/ y) O/ s; J, A$ g
      一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:$ n8 D4 A3 U+ G5 c  O2 \

    ' @% h! V0 Y$ Z& r" m利用dt对象和布尔条件联合使用7 v+ j- ~: f" X2 {1 F
    利用切片,后者常用于连续时间戳。( T% c+ M3 |3 H% u/ I
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
    / f( s: G+ s% y0 M  zidx = pd.Series(s.index).dt8 T! \; f. p: v) V* B/ \
    s.head(). B5 \2 g8 P# }1 k6 L! w: m6 i

    $ R5 Y! Z& c# s  g) c) a( q" t4 m2020-01-01    0+ J( l5 ^6 D0 W  s
    2020-01-02    19 f6 o1 m7 T  `  H+ z( y3 Z2 d, ]+ [) Y4 Q5 h
    2020-01-03    1
    5 Y& b0 F; O& l/ T* z2020-01-04    09 H1 ^+ v# i  H! q: c
    2020-01-05    0
    ( r: d+ |( l9 C9 n9 \; _5 }; MFreq: D, dtype: int32
    ; {& e& a( H, {% Q& Y1
    . A" U3 p; B2 O+ V  \  n2/ j6 O$ ^9 g9 U
    3
    7 P# _2 y/ b5 d4& s! ^  z, p7 `4 ]% A3 ?
    5% Z' m5 x7 P# `, k- _4 U* z
    6
    ! H# r; A, l# g7 k/ P7' w' C# p5 C1 H. f% S$ Q
    8
    1 a( H# ~7 H8 N9. L/ M0 [; X, d. m
    10
    ( l; p7 v& R8 q' f& ]Example1:每月的第一天或者最后一天
    : B) s5 s) _; G: h4 v
    / W, n! `4 d1 q7 `s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values! W* I$ B' a. B' I( S  b
    Out[50]: 0 C% ], F9 m) Z! }- s% j: z/ ?. o
    2020-01-01    1: x% G9 j% F' ]3 k
    2020-01-31    0
    9 r/ @8 g4 t6 \# k) B) {9 g2 ~2020-02-01    1. b' B7 K6 ]8 `
    2020-02-29    1
    , h( g, O& C2 N2020-03-01    0" m7 |4 f! E. T
    dtype: int32! |9 @6 w4 o8 C! J% H: y
    10 F- V. R% e/ ^) H8 T" f1 g0 |& @6 O
    2
    : _0 C& X, I1 y6 M3 x/ f. P( x3
    # q$ h& `& |+ M8 Q% T8 v4
    4 |- }- q, z6 ~" ?: U5- z5 z/ ]/ m# h# k. q( x" V
    6
    8 ~" V( ]& S: b- f" ]70 C  ^3 T( A, j2 |
    8
    $ S) u3 Y5 i1 @" j% z3 v$ r( dExample2:双休日9 D: [  Z* |; T" x
    & ^5 R: _! l7 A* V) `) G
    s[idx.dayofweek.isin([5,6]).values].head()  F: t% ?+ s+ `* y6 M
    Out[51]:
    ' E6 H" f1 D6 s) `' e1 U2020-01-04    1
    1 e* {( Q( j5 l% ?5 Q2020-01-05    0
    2 }! H! L  X. i7 Z  X2020-01-11    0+ H' s$ X& q1 q. ?$ R9 D$ b
    2020-01-12    1
      z: \8 U0 [7 Q  m: w& D2 F2020-01-18    1- i. g% y2 Y( Z3 A
    dtype: int32( f5 L# W  \. `& t+ {
    18 U! d/ @) \% ?
    2- u* c8 ~0 l! t6 k, I
    3' ^  ?) w* P$ C/ \4 u7 y2 {& E
    4
    " B! y. |: X8 z1 e5
      h, [9 p" q) k/ {, [65 r2 S" d. P% F4 X6 F) {  R  w5 }
    7
    ; L6 o* D/ N& a0 U/ C$ e% f$ ]8
    3 @+ i" {. N& U; x+ K) t! m# PExample3:取出单日值
    7 F; W: x8 V) f  J. D" w4 _. b) _9 M6 ~- X% E/ o# e) v) t- _
    s['2020-01-01']! ^1 p8 |* u0 C
    Out[52]: 1
    7 y4 {" r5 r4 _- W
    ; G1 B  R9 ]0 p# k4 f3 ^) A- k& V3 As['20200101'] # 自动转换标准格式7 a; S2 _6 y) M* i9 U2 R% E
    Out[53]: 12 {5 m4 n* A' V% S7 F' J& ?0 H4 w' l# Y
    1
    0 x1 [  |9 O9 S. P$ o2/ u9 s. u" j8 I# x2 v$ H
    3
    / p5 H5 e+ U1 A* j% W( ~4
    8 ^) l, C; W2 R: j% z, n0 [5
    0 Y+ W6 k" y* r9 yExample4:取出七月
      ]% [6 q" t  e  |
    $ [5 l4 K, c; q: Hs['2020-07'].head()5 B5 T: G3 u" X* k: m
    Out[54]:
    9 V  e0 L! y* |- P2020-07-01    0. j8 y/ H/ c& F4 F3 v$ {2 N) X
    2020-07-02    10 v' \/ v" f7 Z) t/ U- Q
    2020-07-03    08 n/ J/ Z+ \4 t* Z- V$ [7 z
    2020-07-04    0% Q& F  c# u3 U7 n* }1 B
    2020-07-05    0
    4 E5 r1 C# Q/ D( K% HFreq: D, dtype: int32
    1 B1 K: c5 I7 i$ ^. ~* q' C& Z* I12 B4 _% ?% D6 o; E
    2, |( h$ N( S+ g; X( e- M! z5 D$ p& H
    3
    7 M6 c8 X+ b5 e& h. o: C44 Y) M- n( h( f8 [( F4 h  N
    5, ]* b( H4 X3 k* W. s: L  i
    6. ?5 c6 ]6 S- g1 T3 L: x
    70 h+ o! P6 u+ A1 {. i0 K1 h
    86 b) t! N, G5 W3 l& `# |
    Example5:取出5月初至7月15日
    4 g+ ^9 z7 }& H$ h' J- Z
    + D' j. }* j  r( M) j6 m# d$ fs['2020-05':'2020-7-15'].head()$ c6 h3 L* z- a+ a9 u0 C
    Out[55]:
    4 A! ?- ]& G7 g: [& X! e2020-05-01    0
    , z5 S. E0 i1 H8 e/ p2020-05-02    1- _! q& R1 T6 G* t6 H9 J
    2020-05-03    0
      O, p0 f- w8 {( \$ t9 U2020-05-04    11 \- n5 w+ q; J% P& N7 Q& S
    2020-05-05    1
    " F' M, ?( v" l; fFreq: D, dtype: int32) ?' z# J0 l) e9 j0 K1 W- Y7 a
    3 v8 e: x3 h: \7 `  X0 Y  k
    s['2020-05':'2020-7-15'].tail()9 F2 z) q9 l4 f& h4 R; `& e
    Out[56]: & A9 d) V1 _% J2 z' {# A0 y
    2020-07-11    0
    ) x3 O5 I$ E& n" }3 i' T- D+ F5 y. E2020-07-12    0
    % d$ j7 S% ~) r  x0 x2020-07-13    1/ F4 |5 U* p5 e* @4 v
    2020-07-14    07 ~$ y& x1 f- G8 Q- Q" W
    2020-07-15    15 y9 J( I4 m9 I: }: D
    Freq: D, dtype: int32% f8 C' c' N, s3 D
    9 {! a1 _' r) U. }% D0 J; ?7 }
    1: R1 o. s  _! [  y1 L& B# s% l9 d
    2, q% B% Z" \  c5 c+ `
    3
    6 P' l) n+ E* H6 _% n/ }' c4
    : p4 N8 s2 h/ Z% K/ o5. H' x6 T, O& W% w
    6
    , @% ^& a8 \! v% I7* A* h. a" z/ s( q6 t
    85 b% |0 N+ ~9 \' w4 Q6 r& `
    9
    8 v) S9 ?6 \4 R' |4 e1 c2 S" b10) k+ C' }' H* f5 d: Z
    112 f% u9 Z' h& x( g% R2 ]
    12! H% b* {, z2 U% B
    136 l0 X6 X* d6 D
    14
    5 J/ N# R. L# o# o/ f  h( C1 a3 H15
    ' J* }) e- F$ K4 b! k: G16
    / j! N6 z0 v/ S& g7 a17
    3 K+ k7 ~0 W$ J! y3 J  ~6 n10.3 时间差
    + g5 R. l5 p& ^$ c) x10.3.1 Timedelta的生成+ f- a, H! K8 A! E( E
    pandas.Timedelta(value=<object object>, unit=None, **kwargs)$ O& g) |: V$ s6 j/ x. ~7 l
      unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。. |& _7 v9 g# z
      可能的值有:
    4 \, a" q$ i+ J) @4 g8 X
    4 n6 H- t; H- _/ g' P! e% ~# p‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’: Q. f* E2 R6 e& t
    ‘days’ or ‘day’
    ; s: S; _* T6 {( }( H) i( o5 \2 [‘hours’, ‘hour’, ‘hr’, or ‘h’
    ! m9 T9 \, v7 |/ V2 r$ |% B4 W9 k‘minutes’, ‘minute’, ‘min’, or ‘m’
    # _7 W" y; c* q. j‘seconds’, ‘second’, or ‘sec’
    7 \4 Q1 E( U2 f, C' k3 B毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’( j4 P1 \$ Z+ m! A& I3 V
    微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’4 w- ~* V( H5 b0 w7 R' {  J
    纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
    8 m& a# K. x" }* h; ]4 W& U" X, Z时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    . s' H" p. u+ v, Q3 h2 d% `4 F' Xpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
    % ]  _2 V/ K( Y$ cOut[57]: Timedelta('1 days 00:25:00')$ u$ X- }# h' G% `5 p1 V
    * E0 V; z* y$ I2 G/ K7 e! q! n
    pd.Timedelta(days=1, minutes=25) # 需要注意加s+ C* `5 d; E5 J/ X3 J
    Out[58]: Timedelta('1 days 00:25:00')6 A4 C1 E/ G' s! @

    1 k" M8 w$ d# o  [- ^* J9 lpd.Timedelta('1 days 25 minutes') # 字符串生成
    ; W6 ~8 q/ {' f# f6 @% K7 ?8 JOut[59]: Timedelta('1 days 00:25:00')& t# D$ ?( V( i% G) ^
    ' {& u0 b5 E  B9 q$ X: e- L
    pd.Timedelta(1, "d")1 A2 D3 X. z, v5 u8 e- d
    Out[58]: Timedelta('1 days 00:00:00')
    : Q4 C- x$ z4 A/ J18 r. I7 i2 t7 Y; m
    2' k: d+ \7 q' e, e7 B4 G
    35 T9 |$ \% v1 q) k& t! f* B& U
    4
    8 [: |5 ^; [, K: n' `7 n50 f: `# v% T  N+ N! r, W, ]
    6
    ) l1 q/ d2 e$ m& |5 O7
    ( b% e" S3 A) R; h% E87 D, _0 c6 x$ ?' @  B, k
    9; a. N6 ?% ]/ I$ }5 ]" p2 ]) t
    10
    , Z2 i! g" c6 z7 V- z; @11; L" v' k. f5 M; Q- m9 N
    生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    # @+ M7 t9 v( R# Ss = pd.to_timedelta(df.Time_Record)
    $ H! h+ r6 q# S/ E' v+ z: {% z, e6 {4 g" o5 V" \
    s.head()
    + U, }; t) Y$ D. F9 ]$ j0 ~% hOut[61]: . s( Z6 E2 v- m+ W
    0   0 days 00:04:34
    6 `% O6 o2 `& `6 k& T' B1   0 days 00:04:207 \/ c+ T# _: U/ |* t
    2   0 days 00:05:22
    : P1 m5 P1 x! L7 E( H! f3 O3   0 days 00:04:08
    5 O% ^& ^/ F4 B6 P- o/ S4 y% r4   0 days 00:05:22) A: @) b0 u9 E) P* l; H
    Name: Time_Record, dtype: timedelta64[ns]
    # Z' D5 A1 U' X  w4 @. Y1& q6 Q4 H, y- l5 Z# C
    2
    7 z+ C& L, y% \# L$ ~+ g, k3
    ) Z! l3 h$ e, x4 w, }4
    7 W3 Y/ K7 m+ _& ]5
    9 @" p' e+ x8 v8 c& ]5 F6& {; M, x2 [; z) t, ?, Y% i
    7
    7 l; ~$ _8 t3 p$ _4 a8
    ( p1 C+ x& y. O  {+ k$ A) b96 d3 W! |, G$ V8 d* b6 S
    10$ E; q1 |( E2 V& m. M! x/ d
    与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
    % d$ t3 T, ]$ _- K' n1 npd.timedelta_range('0s', '1000s', freq='6min')
    " J% @9 v# Z9 l. r) zOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')/ g6 i7 f/ W; P# @

    . G. y0 }  {* E) I" K0 w( J& Bpd.timedelta_range('0s', '1000s', periods=3)8 W- u$ P0 ?% l' M
    Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    - Q6 @% Q8 h4 r9 j6 b1
    ' t( F. S. S( L9 b$ m2; ?3 A9 O$ D$ D/ ?4 N
    3
    * h& @+ r. S4 I3 G4 R4# o2 Z  ^0 K1 G0 a- f
    5+ Q* l+ F. v$ a2 d- p3 ]+ t
    对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    6 R- @8 t, ]3 k; x+ Hs.dt.seconds.head()
    & k# j9 j, y3 aOut[64]: : P4 w/ G' ~% F5 u% M1 C
    0    274$ O, P# S; L; E! ^4 W
    1    2605 t+ u. c' t8 m0 Y$ h) `7 ~; t9 \
    2    322
    ) Y  V# e, S0 D0 K- |3    2482 i/ j. M$ W" I2 l. [" k) S2 L
    4    322
    8 G  s, Y% {9 FName: Time_Record, dtype: int649 c2 c- L; Y+ k, L
    1
    . r. |  \7 Z: C% T( s/ x2
    4 W8 r- U" P$ z% N1 h( @  x35 ?% t* L+ U; ~% Z4 z1 E
    4
    : x% |% n' X' b! {7 g53 ^' g8 h$ l3 g* x9 Z5 L! ?
    6
    5 X" ?6 j, k5 l$ g. F. u7, p' G% n) |, T  U8 x
    8
    7 F3 p" z( j' e2 R1 Y如果不想对天数取余而直接对应秒数,可以使用total_seconds
    - L2 o0 S# r; P  K# W# U; }
    4 S* V3 B' e1 H  S$ Hs.dt.total_seconds().head(): O  I! t% o0 u5 Z2 s
    Out[65]: : l. `& B8 O! m0 ^8 j" |
    0    274.0
    & r5 F9 |7 k4 L. c/ C8 G" P: _9 Q# o1    260.0
    - o2 V' E+ r9 T# H! ]2    322.08 g- u/ k  J6 N
    3    248.0
    " N% ?: L4 b7 f5 m& u4    322.08 Q+ {2 N6 v& V
    Name: Time_Record, dtype: float640 X' l8 M; J& v* R; v
    1) C# P$ {5 I1 o' e5 A
    25 o( J3 X; A" M: F& S; n
    3& s) l7 f; E$ i" u* z
    4* `7 t! o4 M: O' s% \6 @" M8 m4 ?
    5" S6 ]6 L1 a# C5 z, V$ y: \3 R) x
    6
    1 @6 U! F1 V9 z1 p6 K- h7
    7 |) H3 W; j" }1 g5 \( S( l86 s! w( d. r$ u
    与时间戳序列类似,取整函数也是可以在dt对象上使用的:
    $ y2 ?* b' S$ Z/ f6 L3 @% A2 f. A
    6 l  ~6 `8 \% fpd.to_timedelta(df.Time_Record).dt.round('min').head()+ X/ e; O- H# ~0 m
    Out[66]: / Q  m  r+ g; n0 ?0 U, @3 Q- F  n* h4 ?
    0   0 days 00:05:00
    - v% g) |. F; I6 W$ ~0 U- [* _" E1   0 days 00:04:00, |/ ^: z# ~- Y, Q3 N3 x7 X
    2   0 days 00:05:00
    ' K& x) J- E- t: ~% ^* |% }3   0 days 00:04:00: G  g* m0 V8 Y/ E9 _
    4   0 days 00:05:00
    4 L" I8 ]7 f! p' UName: Time_Record, dtype: timedelta64[ns]4 ]( t3 e4 ^$ |5 R5 f  O
    1
    0 [4 H& R1 c/ a+ y2
    # y  ]( D. z4 U3
    8 N7 M# p) y7 ]5 F5 J" u/ \: }4, R, I6 {# x% ~: n5 u
    5
    ' a* b6 v6 w* Z; |& ^# ?# Q6  X$ [, G' ]+ ]3 C" L( ]
    7, X+ N3 R* E" X# l& j
    8
    ' \7 n& H* Q7 _8 v, a2 g10.2.2 Timedelta的运算
      W% S$ U6 p# r. F1 A单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
    & q: u' h+ i/ Y- V4 Otd1 = pd.Timedelta(days=1)
    2 Z: ^2 ^4 ~$ F. A- v# ftd2 = pd.Timedelta(days=3)
    - d5 u. F3 {, W9 G! R" m5 Wts = pd.Timestamp('20200101')
    ) X1 j. W# Q+ W) Z, c9 {( a1 y4 w
    td1 * 2. y5 o( d* W0 y- z! E; M1 x
    Out[70]: Timedelta('2 days 00:00:00')9 P, k  l( w# I
    9 B$ n& l& {6 J- H9 `9 {
    td2 - td1$ w4 b$ |9 d  v/ S9 |
    Out[71]: Timedelta('2 days 00:00:00')
    , c- h+ e2 C& e5 v9 I# |
    # U9 E0 u8 `! q# Xts + td1
    ' `7 w6 d: Y% |! I1 W+ i. {( o9 uOut[72]: Timestamp('2020-01-02 00:00:00')
    ' R6 F6 B8 |* h* V2 [& e& W9 X( W8 K
    ts - td1, w  p: q* L8 {
    Out[73]: Timestamp('2019-12-31 00:00:00')
    . Z5 D4 r' X$ X' X) O( L+ }4 E1' L- h! V, G2 d) m# F& n
    23 o, ~( _) O3 H2 Q" ^
    3; [1 P( G! H, l& i1 i& f$ _
    4
    # H& z! {$ _2 I! w: S5
    & y5 F% u# t! X7 D/ U0 _( y62 R: e$ T( A- Y) r+ d8 O
    7+ @1 ~2 u& j0 i$ X8 t
    8, R. U' G+ J$ n  h5 P' \- Z: n# z
    9* d# ]$ ?2 S/ D& s
    10
    , }) k  Z9 k+ D  w11
    ( x  g/ G6 h1 K3 j12
    ) N7 F  E8 p& d5 {3 h  i13
    2 H6 h( Y5 {  b/ z# ^* a14
    / R: U( Q6 f7 t9 w15% Y3 H5 ^! L. H
    时间差的序列的运算,和上面方法相同:5 P- g* ^; \6 l: o- I
    td1 = pd.timedelta_range(start='1 days', periods=5)
    ( H0 h+ w7 n9 o5 O7 h1 G- {6 ntd2 = pd.timedelta_range(start='12 hours',
    * S! \( r7 P4 B9 v                         freq='2H',% ^! F+ F/ U8 h1 F) \
                             periods=5)* I0 N1 i" j, E4 b
    ts = pd.date_range('20200101', '20200105')
    9 s% X9 ?: c3 Y& i0 k0 Z7 _, Atd1,td2,ts% Q! s/ z: F' d2 {0 {/ n7 M  }0 K

    ( j0 a$ t6 T( w" ?TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D'): s' }7 c5 Y# e0 r, B" V+ I6 f3 v
    TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',* }8 R: u4 |! f" }/ @: \+ @
                    '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')) y1 E, i2 ?; s" `5 `& K& u
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',8 w# t+ t( C* L& d, P  g  i# F
                   '2020-01-05'],# t- g4 g% T( |# q5 T6 r% D; J1 c
                  dtype='datetime64[ns]', freq='D'). H: U6 u/ ^0 K! H8 T+ g% |
    1& q+ r' |0 C1 @
    2
    7 Q# ]9 I6 v" c) Z3
    + ?9 z0 E  E& h! k, l$ Z" o+ y4
    : ?! P, P: a% g$ D2 [- k5# d1 e1 y3 L$ }* V: U) u
    6* x  X8 s- j$ m. q9 E/ ~, _
    73 C! ?! r' N! P+ W
    8; j0 g' l+ m( O0 d  p' u5 q
    94 ~) G5 q" X7 Q" D& ^& R7 Q
    10
    ; m. V: {- N& g2 u7 w1 M+ G1 V11# ]" S* t/ I6 }4 C' X
    12# T; B! G% w- O2 ~" G  @3 A: X7 q* M
    134 s4 N# {# c, @1 H" b* W
    td1 * 5% D. M- q9 V$ u5 g/ _) F) j
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
    - Q6 W& B2 M" b, ]0 K. S# Y* s
      [& j0 b# |0 a$ H1 Etd1 * pd.Series(list(range(5))) # 逐个相乘8 O1 T5 Y7 J- ]6 \
    Out[78]:
    - E1 C" x* O( n" C7 l7 p" q, l0    0 days
    8 G& o- O4 Z. X& r' _1    2 days7 }" s# ?$ L4 W6 l' c/ S8 H$ }
    2    6 days
    / Q5 o7 D3 O9 O% @, s# d8 F3 Z; K3   12 days2 e# f0 o" X4 Y. k8 k, l6 w* t
    4   20 days7 X6 X3 i8 e* g2 m" I. H7 @0 x
    dtype: timedelta64[ns]9 I/ W  E" V2 q& y1 ]4 p

    : U" P' m% x% `td1 - td2) G- c( r. V' K9 d# g# _2 H% O
    Out[79]:
    1 a$ P9 ?8 E* U8 GTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',+ z# K7 p' ]% l* F( [
                    '3 days 06:00:00', '4 days 04:00:00'],1 ]0 Q+ e" b0 d  B$ q/ E2 m! k& t
                   dtype='timedelta64[ns]', freq=None)& G9 H# [5 |7 t) k; n: \' k

    ' o: {; {3 ?) ]' X7 Wtd1 + pd.Timestamp('20200101')- e: @% Z/ z" n/ h9 w8 R
    Out[80]:
    : l- }# a- T4 nDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',# K3 L3 u6 j$ ~8 k
                   '2020-01-06'],dtype='datetime64[ns]', freq='D')2 \: X$ G/ c' t+ L1 B8 U4 O- c, J: w' H
    ; V* `! Z# P5 H
    td1 + ts # 逐个相加
    - I: q; m  u, n5 p! U4 O0 {* `Out[81]:
    ( s3 Z) o9 p5 o8 d# w5 wDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',( S6 A+ E/ u3 `# p# V1 |
                   '2020-01-10'],& B5 z3 x+ r. q; S1 O
                  dtype='datetime64[ns]', freq=None)
    ' `$ K3 |, R' ~2 y4 |# z! V! q# a0 ?
    1- P0 g3 d. I5 x+ \! G
    2
    / K8 O* P* l$ E0 U  S) M3
    3 e  Y5 x+ ~  F2 p9 Y+ P, S' Z4
    9 Q2 J2 _1 |: Q59 y3 }" L  z$ q
    6
    2 t1 j/ y3 P) ^7
    ! [6 c" u1 L1 A1 X; h9 I8
    ( Z1 n9 L' z, Y7 f$ o1 w9
    & f4 \2 ?# N" u/ Y3 r; {% J10
    $ \7 ?7 N' Y$ R# l- v11
    " E# \- @5 q4 q; S' \( }12
    $ G/ M3 w! @7 ?7 T( ~3 c2 r13
    ( e0 {: r9 S# b0 i* m9 O, s14; K1 d- S- ]% c: b+ v
    15$ o: ?! Q( p  [3 j( b9 P
    16
    & `0 g% U, _! Y  i2 J/ }2 ]- N17
    & Y4 ?5 d' m. H0 W18
    . V% ^! I) U% t' c; S& g19
    3 L, i2 J' h! B# `6 }2 I206 U" [& t# e$ G5 n7 }9 P
    212 z, P  z; O. U
    22; A! I  I% i. N' x+ i
    23
    - E# X' Q$ c5 @. q. u! \24' {# |8 J9 [) d$ ?
    253 r  R/ I- i4 m: I
    268 _8 j+ \; l6 @2 K# L8 ^1 @; E
    27+ V3 H( |8 q- U1 C8 {
    28
    - H3 T( g7 e* }& Z8 X) f, Z& B10.4 日期偏置
    % \4 b7 n# c: x10.4.1 Offset对象+ g; t6 \6 I! o( p3 p; s: f( _% P
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
    ! L9 y1 |! N- \' C6 y
    8 }# n7 i; n. S8 ?4 V2 }3 EDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:% [& D7 e7 M4 c: m/ T

    6 J7 J% Z1 [2 @" S' L9 ?1 {s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本7 K% [2 L) {6 A3 C) j- y1 y
    s.kwds:{‘week’: 0, ‘weekday’: 0}# N4 U+ c0 k/ _  S9 s7 B
    s.wek/s.weekday:顾名思义
    4 c8 V: A5 M9 q* D  X. O0 ~9 ]: m有14个方法,包括:! i4 n0 N; t! P0 _/ \0 z& l( K% ]
    7 I) g, c, R; S+ a  J
    DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    + B  O( e/ x: w9 c  N* Wpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
    + t: c+ m6 m, i% L* N" I3 M; V/ {. Q) n4 m' o8 ~  e
    有两个参数:( V9 T, f' v' |7 y# p% b. \
    week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。- P/ b" j; M/ `6 n7 K
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
    , A$ j! O: w: o3 Ppandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    " c: [, d! M# F' C
    7 D. n4 Z7 ^. _; ]4 dpd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
    1 ~2 F4 B' g1 ~/ ROut[82]: Timestamp('2020-09-07 00:00:00')
      l$ x/ y) t! u% Z+ Q1 ]2 O" m( A% Q0 F% ~* {7 x
    pd.Timestamp('20200907') + pd.offsets.BDay(30)) l- j: ~$ ?. o' ~/ o  v
    Out[83]: Timestamp('2020-10-19 00:00:00')! ~0 I9 d- g6 L% Z
    1) V' N: n2 I$ T! e7 R6 k- g
    20 i' {5 n9 d- z2 h% M
    3
    8 }& \0 P6 u. z3 o  E4 f& F2 R4
    ( n+ j- k5 m* c. N6 ~+ p5 Y5
    6 F( H1 O1 j  I4 T  B7 G  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:0 f# v) L4 I3 ], e; i. |5 h  K. Q
    - ]6 j5 f9 T# d4 @' R* o
    pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
    . Y) k& x. [3 f5 [1 a& q/ T7 sOut[84]: Timestamp('2020-08-03 00:00:00')
    % v/ Z4 a* Z6 a
    2 M1 m; X( y* ?  t% H: d+ Gpd.Timestamp('20200907') - pd.offsets.BDay(30)5 k' X  |! Y- q! ^2 J" d8 H; A3 Z
    Out[85]: Timestamp('2020-07-27 00:00:00')
    $ O9 q/ S, _  Q2 H6 ?, I7 `% N" K$ w: [" ^4 x
    pd.Timestamp('20200907') + pd.offsets.MonthEnd()
    7 `1 J5 N; l; m, M. p  O3 OOut[86]: Timestamp('2020-09-30 00:00:00')0 q; q) ^! w% J, J$ R/ W
    1
    : y9 a. P4 z3 t3 f0 ]0 ]2
      _) k. P5 k. t3 j( {$ }3  A) g3 p8 Y8 g) h
    42 J+ s! T4 l# {$ M
    55 ^* E: B6 U+ g, g9 [# V/ z9 e: D# m
    6
    ! g) C2 Z% j" K. i8 C1 O% |7/ R3 r% R$ l. ~' o
    8
    % J  b0 ?: g/ P$ o0 j- f$ Q& D  常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
    # G8 P" ?  C4 D8 V2 \& X* @  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:7 M; l  O7 a# z5 v) n6 E3 R" L
    3 g5 `5 t  s4 s/ Q  s9 U
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    ; i& Q3 O- W6 d5 G5 P" _dr = pd.date_range('20200108', '20200111')
    # g( c  T( g) \. ~- b. N& `# F( z6 q1 h+ v* q% c' G
    dr.to_series().dt.dayofweek
    3 ^3 {1 J7 \6 QOut[89]:
    ! w7 G( {7 N- u6 `  M3 P: b2 D+ K2020-01-08    2
    3 _7 H% G$ c3 J- D# b7 A; f: b2020-01-09    3% h3 \( a( c( y8 [
    2020-01-10    4
    % m# C8 d1 n2 U. K4 [& F2020-01-11    55 c  V0 V- A' z/ R+ _  J  ?
    Freq: D, dtype: int64, s3 @( X/ m' J4 O" `" \$ a

    " h: R4 l5 x5 M9 R/ K4 L3 F. w[i + my_filter for i in dr]9 t' |2 h1 ^. O+ F2 B: C6 {. a
    Out[90]: % S2 L" t% X7 n+ C
    [Timestamp('2020-01-10 00:00:00'),, {# S7 K% u3 ]
    Timestamp('2020-01-10 00:00:00'),3 a8 V4 Z; h, c( A5 p
    Timestamp('2020-01-15 00:00:00'),
    7 l/ A( F2 D+ f8 d# Q! G  _2 ~ Timestamp('2020-01-15 00:00:00')]: e( G' i7 l+ C

    4 x2 Q4 i% i6 }! m7 ^% W1( X$ e5 B9 ~) K8 _0 E, K) Z* |4 w
    2
    * J% X! u3 d# ^9 {, E33 s8 W3 u8 B: d- G$ s+ }& }
    4
    1 E, C7 O/ K5 i% i* |' F5& {/ \; X0 v2 B# F
    6
    5 V7 {. d# A. V6 U! a  `* k4 X71 e$ f8 |) ~7 I
    88 Z! d7 _2 b1 F$ }9 y7 ^/ C, q
    9( d7 w  A! |3 Z/ Q) E
    109 m$ p" _3 J$ X9 t  F
    11' o# ^+ w% ~* K) {1 a1 a
    12
    6 u$ p8 A) I/ t$ z3 T+ t1 L13+ e- x% A. `( a
    14
    5 L* q9 n& D" F& f1 A! ~  s- S# T152 H) a+ j" Q4 @* k* j% I& h( z
    16
    ! H7 [- V6 `; N! C% D5 [17
    3 ?, U& h" |% J2 i' G& u  \  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
    4 W# u1 W+ l5 u; N* @: K  S3 {) R1 p8 B3 q" b
    【CAUTION】不要使用部分Offset
    $ r! c+ A# O, K" G在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。/ T+ E* _! J* p7 a7 `; I

    3 q2 ^, z) [& K* a10.4.2 偏置字符串
    2 X6 B' h6 k6 W) h0 ?0 t) {  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
    ! z3 O! h2 M2 g( C, ~% b/ W2 m( H3 {$ y- ]# Z0 K
      Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
    0 _  A0 G& |4 N# Y0 d. y0 r3 q. ?* n6 ~- g" [' l, X
    pd.date_range('20200101','20200331', freq='MS') # 月初
    5 r' J* M6 S2 n# X& T1 S& yOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')9 U9 e# T! Q1 p% _' H

    8 ?2 x# B5 L! Qpd.date_range('20200101','20200331', freq='M') # 月末
      ?/ M4 N9 W. K6 I6 d) ~2 `! z( kOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    5 ]8 [! Y0 |( n# U% ^, T9 l8 C
    # n" ]$ x2 q1 dpd.date_range('20200101','20200110', freq='B') # 工作日+ }/ H$ n" p* ~1 k
    Out[93]:
    # g2 B- M; `  I1 Y+ I& K4 q$ NDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',% d* C2 O' A/ q  g
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],, k; E/ X" X/ [  {3 Q) F8 u
                  dtype='datetime64[ns]', freq='B')
    / Z) k, U5 j4 K
    ! H/ W- x/ \4 D3 g; V3 f3 L3 Tpd.date_range('20200101','20200201', freq='W-MON') # 周一
    $ R, r( K- @2 HOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    9 q" d, y6 w$ N" s$ z" N" G0 y/ T; ~* M) i
    pd.date_range('20200101','20200201',
    , L  }4 }* `1 H# i$ g9 D+ c+ f              freq='WOM-1MON') # 每月第一个周一# G* T* K% ?( T" R
    1 U. s1 r' p; p/ c7 L" L( `
    Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'), m5 @5 k/ P, n9 h2 I

    ! v8 C- a( N1 ~( R+ Z' f% k' [1+ B5 `% \$ ~/ ?
    2# _! S4 S: M: Q7 y9 A1 t5 a7 h
    3
    0 g# E" r' [! j& E4
      a9 M* s0 N* H/ k+ B5
    ) M7 u5 ~; z$ ~. C' ]6
    * e7 L3 ?9 E" c# p' Q. F6 H# e9 y8 h7
    - W& i* {, {+ O( H4 `) U4 [8% r) V( K' r  V
    9: ^- q% O3 G: }1 @, }+ B/ `
    10$ R; H5 P# H: \) l! i# s
    11
    + g" ^8 N: Y" G. a- k12
    - r5 \5 Z3 t1 b1 _13; r, t& L. B& a/ M& k% O- K
    14( E1 n# Q0 w! L0 z: M- {; H9 d) K* m
    15
    8 I- S' R) n; Z- [) f162 q5 ~- @# B$ H; y" h+ Y
    17' S7 D. ^0 ~: \8 x5 v& {4 F+ a
    18
    0 A5 L. ^2 `7 ~5 h- n2 V19
    - v  L& j* x0 W4 B, ?) U上面的这些字符串,等价于使用如下的 Offset 对象:
    * G- Z% ~+ c; C8 L6 h9 w# b4 o) q6 \/ y2 n8 f# R* ^
    pd.date_range('20200101','20200331',6 C2 D+ n8 Q& {2 Y
                  freq=pd.offsets.MonthBegin())
    4 O4 R$ ?: E( ?$ j/ }% H/ A1 b
    ! z. B, ~. o  D8 a) vOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'), X3 {  g  z. l" R8 j. [

    $ \" L. E2 |4 d& _  Upd.date_range('20200101','20200331',
    8 G) Z! p1 x, I. p              freq=pd.offsets.MonthEnd()), {+ v$ ~0 E8 H9 P9 C2 N$ e
    + y& y4 I% ]$ [+ ]
    Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')4 m3 E1 d% {+ d' @' t, r$ b
    ) K- C) R- _/ H
    pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    2 M+ E; c5 m3 R* E) \$ [0 D5 TOut[98]: 5 ?- K7 d) n: A) L5 f, Z3 V
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    : m2 S4 d8 ~5 G. ~, {* T               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
      ^# m. t0 Y* W7 d* N! I              dtype='datetime64[ns]', freq='B')
    9 z3 {9 P, W0 k) g1 W9 D' ~2 `  v* C% W# z
    pd.date_range('20200101','20200201',
    0 b0 M! w- O* G2 k1 w8 s% d* H              freq=pd.offsets.CDay(weekmask='Mon'))* ^/ e" T# V( @) [5 h

    9 j. h" K, J8 }* y! GOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')+ k$ R  ~/ }; p' ^, p9 F, n! n/ V
    5 Y+ [& ~, j# Z3 T- h1 `
    pd.date_range('20200101','20200201',
    7 x" S$ l4 V- a, u2 _$ D              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
    0 L- _% @" ]  {, r- {
    / n/ [( x* C, G: ^Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    & f  @$ m6 [1 [
    % \& B# W9 |6 I- q12 a- B* c* O4 o7 d1 [  i
    2
    + ^/ m& c0 `* u) V/ C' d3
    2 L; G# Z% I$ I4 S/ r% s9 `4
    / T8 L+ `4 `: l( a/ U) p  M5
    0 R! `/ l& q% V) R+ N( a6
    4 ]5 Q0 n# Y. T, G7  D. `& l+ r& P1 S) W- T# }: G* W
    8) t7 M9 \* i  M7 {5 O
    9
    # h/ i3 _+ q' e10+ a% s  V9 l( N6 |, T' c/ M
    11
    ( V% |7 Z2 t- P% b1 w3 @4 }7 x0 M12+ d1 O8 M( l5 t( }
    13
    5 R1 |" Z4 u0 \. @14
    0 ]! t* ^  y7 _! ^- \154 f% O, o# `/ K
    16
    + {/ f, {; `; @* `17
    . H+ h( x" Q6 L. y; D& r18
    * G2 s! k$ K4 |19
    8 N3 m( O! b% Q3 P! ~20
    ! |1 \; [( g0 V; [21
    2 H( @9 Y; }2 `22
    # n+ _- g0 h# `230 d, U. ~: Y4 N7 }1 w- j
    24
    / e8 m& O+ Y; Q250 l7 h, V& C# [* e
    【CAUTION】关于时区问题的说明4 K  J" \3 p" `2 [2 [; ]
      各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。& O( o1 `# S4 ~) o

    7 m6 v0 B6 V  O. N7 V. P0 y6 y, F10.5、时序中的滑窗与分组
    8 a$ M  E. E' L/ g! ^10.5.1 滑动窗口* z  z% @# Z5 r- ]
      所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
    8 o5 P' H$ S0 Z. V( W' I& r! S  X; g- T" z
    import matplotlib.pyplot as plt# U) i" ]; C% C  a, F
    idx = pd.date_range('20200101', '20201231', freq='B')
    ; H( K4 {  a6 J5 t7 K3 Inp.random.seed(2020)
    6 I$ \8 w* k/ Z. g/ D' b# P. n  T, l" L0 @5 t, w
    data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加: u: Z0 F% r) l1 N& E# d" h1 n5 y7 G0 O
    s = pd.Series(data,index=idx)
      r: e; {* u3 C: c9 }" k1 Ms.head()
    0 q# M) `* G/ |8 l& N9 C& r  MOut[106]:
    2 i/ J6 f0 Z2 U) l2020-01-01   -16 M8 ]  Z! j5 G6 m5 s
    2020-01-02   -2
    0 F" o5 w5 c$ l, ]2020-01-03   -1. S  `& r8 t  {. a3 o
    2020-01-06   -1
    + V7 i  U) B! c4 d: V( p3 e4 B2020-01-07   -2
    1 j  {( N/ z; ]5 [' ~( D2 JFreq: B, dtype: int32# Y& ?% x5 f6 q; T* f
    r = s.rolling('30D')# rolling可以指定freq或者offset对象( x, S; }! y9 C$ V) o
    ; \  u7 `0 `) _; j1 {
    plt.plot(s) # 蓝色线
    # _3 N, m2 z/ V% DOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
    ) [: D& o& m7 q7 ~  U! Rplt.title('BOLL LINES')
    " Q) k: D8 w; oOut[109]: Text(0.5, 1.0, 'BOLL LINES')
    * F' O, d( [5 ^+ H0 c+ M
    $ P% I$ S" z, [, e+ [plt.plot(r.mean()) #橙色线6 v2 u  O0 O( D' ?5 z0 H) p
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
    9 B- o9 Z1 k9 b; R8 A7 o: c, R6 |2 x, c1 J- @- W1 z& N3 E
    plt.plot(r.mean()+r.std()*2) # 绿色线3 X' _) ^  r' x% ]/ |4 b  w
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    & F# N/ v# B$ _1 N$ X9 I' p; h! [5 `' f4 N4 x
    plt.plot(r.mean()-r.std()*2) # 红色线
    0 w7 [8 L) T6 v% K/ x$ @# |Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
    ; ?% _: j( A1 C3 `! i' F/ Q) J! v: w" x, a0 q# @4 q
    1
    ' d& E0 ]# h' p' Y; [* L2 l' v27 \8 d: e: ]6 R- r2 Q2 ^! O6 ^
    3% H; E" U- z3 _! d+ V( T' w% D
    4' t* N( B) W6 _. H& ?* i
    5
    & W' _3 n- K$ ]2 `2 g7 N6( r$ W; f  K# t$ ]
    74 a% s6 r8 _; p, Z, Z& s
    8) T! N6 g7 D# j5 t, U& A
    98 s' M# L6 o# I9 q  i3 \
    10  f  y1 s" N& j& x
    11
    , {9 F3 H) e, j: @* w. r" `12. p! F7 P, N% L
    13' |' b( T( N: y- n: C
    14
    - A$ ^- ~+ T# t157 i! d) T/ Z7 |6 a  R8 h6 S
    160 Z) K( Y/ V+ t0 l
    171 x; F5 s3 q" }$ m
    18: O9 r. R3 I$ |; H
    19
      F/ X5 C" h7 G6 C20
    / p& H  {2 ~: [2 |" d3 _21
    . O/ m& G" x. s0 O22
    ' p' T. w/ S( w8 y6 Z237 |! x6 K. w0 p6 y
    24
    - e$ g* L$ }" A( S1 P( y1 L5 m25$ ~+ U2 }2 [+ P6 n3 J: N
    26) Z; p1 }9 B7 q( q1 i) G& s& ~
    275 `9 E. g1 h( C6 S. n5 H
    28
    ( ~  n! Z& F4 ~" B: i7 m/ x; H9 k$ ~29+ x; X$ w3 I3 h4 |) t% _

    ) z$ [0 }  l4 E, X! y   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
    # u3 J6 I% o* q* @   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
    : P+ p5 ^8 g2 j) M& U, N  u$ p, [. D! R. z9 e
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]- g5 p# G5 h5 c- E
    bday_sum=select_bday.rolling(7,min_periods=1).sum()
    & k( _2 z2 z4 ?, gresult=bday_sum.reindex().ffill()) X( p# ^5 w& X9 @9 d
    result
    ) z1 i8 q8 N/ X1 Q1 a  f
    % ]0 _7 s  n, i3 g3 J3 E2020-01-01     -1.0
    / z- I5 O+ t: M) h! p% Y2020-01-02     -3.0$ y3 t" X# M" a: e! c( |1 B
    2020-01-03     -4.0
    3 \/ B9 o/ i* w7 H& ^$ g& |2020-01-06     -5.0
    ( s. S- W/ b: G8 A% D2020-01-07     -7.0+ I1 j5 P$ [; b4 {& r5 y
                  ...  
    $ f; p1 s% C* ]# D5 K" X2020-12-25    136.0( t, w0 U6 c! `5 e$ F" |7 L; b' z
    2020-12-28    133.04 Y$ r8 A7 ~8 f, P) {6 H
    2020-12-29    131.0! M1 e$ |% v( U2 Q; y( E# h
    2020-12-30    130.0
    ; [% y- a7 L- P. I3 F2020-12-31    128.0
    2 B% W/ C, E5 |) M; ^& qFreq: B, Length: 262, dtype: float645 k% b9 q9 U7 O0 M0 ?, G

    ' e; X- M& @/ F2 s, Y3 z5 ]1" J+ l$ ~; o: b7 B
    2
    - Y- P% I  j* [! ~/ q) g3& W& ?3 Y* [4 `/ G- q
    4
      F+ v/ a2 W5 ^" o5
    4 I9 ^) c1 a5 E: \, X64 j& g# I! r# o8 s9 P5 _
    7
    3 R4 }/ x! X7 U# J, f/ U8/ S/ R* E' @$ N( |& `& U, x
    9
    3 l* X1 u( g  C" G3 c10
    " g0 L0 k& I: S11( ^) K5 o* A! m1 I" i6 }& Y4 P
    12% ^  V- L0 S/ V# y
    13
    ! H$ M' k' l" b& q0 d; T) [149 ^, z# R! \; A: {/ A
    15
    ' z, [& n) u2 ]3 d% P# ]4 Z( t) s& x1 R16
    - ?3 C+ H3 c  O6 F5 K174 j, D7 f, M: h
      shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。8 b! n; w! K9 M! H

    3 ]* Z2 g  O+ ], w7 I: _. g- n  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    : F: [( K' j+ f4 U2 p. @# F8 c: |2 A( P4 ]
    s.shift(freq='50D').head()1 d5 m2 ~* ]/ k7 E7 o
    Out[113]: . K$ h  k+ G0 O
    2020-02-20   -1
    * C: G5 s1 B% C+ |! I2 z2020-02-21   -23 K6 M! u7 u$ f; H. h! V
    2020-02-22   -1' ]( e4 E/ q7 C" x4 y% b
    2020-02-25   -1  ^/ f4 N  u. D2 a( j* T! w% m
    2020-02-26   -2# L3 ~! m7 H: l  g( `! u1 K5 ?
    dtype: int32
    6 b$ p9 |' |7 B9 ~$ z/ e8 |2 }1
    ; |: k2 D5 R% j5 e: h6 D& x6 N" ^2
    3 C: p+ m: P2 ^3
    + \( I5 o' R' Y# b4( b& X7 v/ L7 B- V5 Q" s
    5
    ( N3 _: ?7 Y- l& K" A# _6
    2 B) T' \& D3 o1 \7
    2 f' M* g* w5 \- R0 g( {0 d81 \8 S3 Y' D$ L& ?
      另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:8 B2 F' y1 ^0 ^: a

    6 \& \. ^' w' ymy_series = pd.Series(s.index). y6 H0 n. x8 A2 T1 p5 T
    my_series.head()
      B' `# i8 W) z& o& G$ G* @2 Q6 TOut[115]:
    , _* }5 `$ L- j3 L0   2020-01-01
    / ?/ ]* z0 L( o# T, \7 X% N1   2020-01-02- h: l3 M% \" P' A
    2   2020-01-03
    ) e3 a4 P$ d$ r# H+ m0 U+ [3   2020-01-06. H. F# e' |* r5 w4 v
    4   2020-01-07
    4 |# I/ l0 X+ U" ^0 v, A# |dtype: datetime64[ns]
    - H" D/ Y7 {) t) B! V( P# D7 F6 ^" ~( I7 B* X& C6 E& B
    my_series.diff(1).head()" Q, d. O$ W7 P* A( k( E! W; c8 n
    Out[116]:
    5 u% T  l, @1 i  g, J+ c0      NaT) Z" {7 ?2 O5 L8 {% {
    1   1 days
      a. U) a2 v7 P2   1 days( L7 a9 |" j) Z  ^' ~
    3   3 days
    2 y( b' c8 a4 }+ d  m6 v; g  |4   1 days* i9 J6 s. _: u% B6 Q0 x
    dtype: timedelta64[ns]
    : e( E3 h2 |& S2 Z2 ^! {8 P
    : k- i' a0 `* `6 h' h: A" P1
    8 W8 X. _- d8 B% s2: d9 d+ K2 ^) V/ d' ^
    3
    2 t6 V( V$ [, }4 N" U) p, {; w4
    * d& t( L0 Z' i, M5. _5 I3 E* A6 s* f3 Z
    6- h( c/ Y$ f+ \+ ~& d& p  H0 P
    78 j: V9 X# R" n/ [0 v) T" U. g
    8# Q: J+ A! v' V3 r& v8 `" z, p, [
    9
    4 \$ A, z% H: H3 {4 G10( o! E6 ]. T' x" T6 t0 D
    113 H1 @0 T- H+ L, F9 v% t
    12" T0 p6 J1 @3 m5 s
    13
    , l4 B* \- ?. x/ T9 V, n- J" `14  D# t+ r4 R8 J& g0 {1 H
    15
    # o: w% o0 O; a: ^8 z) T* y16; Q1 H+ \7 N& D# O' ?$ E
    17: y/ ]1 ~' v4 }9 C$ Y
    18
    2 X/ X7 A3 I$ F10.5.2 重采样$ r, a+ }- r; [: }9 a& X
      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)
    ( P. I8 P1 C& I, d; I! t- P常用参数有:" g- s5 O" p# |5 x3 j, o2 B
    - @; I  v- ~: o; B* b" X7 w" z
    rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    ) x/ s! n8 S! J- caxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样7 Z% y/ N2 z/ `9 z) ]) l2 r+ B0 f
    closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。5 d3 l8 }% I" C( X/ ^
    label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。  l- r' {. w- z
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。) v8 j# }0 d; r1 O. j
    on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。& u; [; \4 B* ^
    level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。) l' A5 K  b$ J- v/ g9 h- X5 a
    origin参数有5种取值:9 K0 S/ P7 e; \0 i2 J* A
    ‘epoch’:从 1970-01-01开始算起: t2 \4 u* Z+ x+ s" z% w; w
    ‘start’:原点是时间序列的第一个值
    # n0 o7 l3 Y" ^  \7 M‘start_day’:默认值,表示原点是时间序列第一天的午夜。& ~8 J8 T' ~$ e3 \
    'end':原点是时间序列的最后一个值(1.3.0版本才有)
    2 j% @" x. d' g: s' A# @+ p‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
    2 K# V. p" P: ~; R! b: p) K" I2 @2 aoffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
    , l1 L# c/ y% {* {; L  closed和计算有关,label和显示有关,closed才有开闭。+ S" C2 R0 j2 q2 Z9 e5 A
      label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。9 r1 z. {& J  l# M
    5 u" {4 C# U! f1 {: C+ z
    重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:0 O1 d' z6 s+ _% J+ O: h: {5 c# u
    s.resample('10D').mean().head()
    & L7 N( y8 x& j( DOut[117]:
    " F* I  Z  z& x9 [2 w3 \5 r0 L8 X2020-01-01   -2.000000* ~6 t* c" \& r( V
    2020-01-11   -3.166667# K: _9 F8 W9 U* n
    2020-01-21   -3.625000
    4 h- H7 U9 K+ f2020-01-31   -4.000000
    9 o0 f$ d" ?) R' H8 \$ G) j% L1 ]" L2020-02-10   -0.375000
    2 }  C+ t0 b; x3 G1 u' r% F* sFreq: 10D, dtype: float64' v2 Z8 [$ w5 D* T) }$ _
    13 L8 ~  n8 Q7 i" V
    2
    2 @7 p$ ~7 C4 t( T3) |7 j4 @3 c* R
    4' R6 H' y0 \. x4 Q1 x$ E. ?% k
    5
    ! L, h/ D, a1 S# I* E: [6
    . T6 |9 e' Q; f7- p) O' m0 K3 |
    8
    : r  Z: t" M+ T可以通过apply方法自定义处理函数:9 ]1 ~. U' `6 W* n# \
    s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差  d# H/ Y7 s% H2 {9 S# R- ~

    + D" \7 D! A- f% ?9 M8 TOut[118]:
    / t  Z# t0 a5 k/ j9 [+ ^2020-01-01    3
    # B, g' u4 F  Y4 [2020-01-11    4  ?6 U" L) Q5 |# I
    2020-01-21    4
    ; b: R2 d! F9 A0 b' K8 r2020-01-31    2
      M+ C- m4 x; x2 I  o  |2020-02-10    4( g$ y, }6 @/ C4 m+ l7 d7 K9 W
    Freq: 10D, dtype: int32
    2 @$ F# g( K6 @' F; q( x1( r6 Z6 z2 @# \4 _% W
    2
    ( N1 T0 W/ j. Y4 A$ c3
    ' I. c: y3 E0 _: p7 h6 J47 v2 U/ Q! m' Y
    5
    & N% y/ z4 c6 Q. s/ L/ z4 ~- ~6
    ( w3 F! Q  _; [7
    6 N4 m% m0 g1 J2 L7 P: i8# N4 ?% r! `7 c
    93 c  ?7 V3 r6 Z1 M) W3 Z2 Y& a
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
    1 p0 l8 X% R9 |* w
    . L+ v0 R5 r8 v/ f5 \, R' c) Iidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')+ z1 S% J7 ^- u! C2 P; w
    data = np.random.randint(-1,2,len(idx)).cumsum()- X- Y% |- d7 c* a2 o: ^3 O4 j* q) K
    s = pd.Series(data,index=idx)
    8 v9 S+ \# ~& |" U( r, y9 fs.head()3 N; p, O: Q, u3 r% n

    $ o; l8 {+ x) E2 A/ d2 DOut[122]:
    * T7 K( n, |) u/ B2020-01-01 08:26:35   -1
    . b- U7 c; D0 b4 Y/ g* x8 T2020-01-01 08:27:52   -1
    . I5 V% p3 U% e/ C. w: b5 N4 G2020-01-01 08:29:09   -2
    5 y& k# X! `( p) g! j2020-01-01 08:30:26   -3; D% i+ m" U8 N& T
    2020-01-01 08:31:43   -48 ?* V% Q; M* D" n5 \! G/ u
    Freq: 77S, dtype: int32' J5 o7 \; ?; H( W6 P6 O- \) V
    1
    ! {$ O2 B) t: W! t' d) a3 j0 e/ U+ B2
    / g! G7 Z8 F9 }* o3  l* G/ o7 @; Z" f
    4
    1 U, I2 L! E, p8 ^6 H+ ~6 w5
    ; o' D/ v* C* I$ s6
    ( A, y, |4 h( r' j+ L5 Y5 v* D7
    . `! Y0 u+ d- {& j6 [8+ _' B1 q. }$ b. [, H2 n- k1 p
    9
    ' G. @7 a% C% u9 X8 M1 ]. ]1 {10
    ) t. V) l  [7 w119 \) ~; Z. ~' X6 {
    126 a  q& e$ |/ k2 W" Z4 @
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
    5 T7 `3 C* V0 Z( U, N# \$ R9 J4 Q$ l, l! c, a" p# h+ b
    s.resample('7min').mean().head()
    7 _! R+ F/ e; y; [% I8 Z, i6 uOut[123]: + Q: o" n' K/ f( J5 V7 X
    2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值
    & m/ \/ [) t2 [- [% Y0 Z2020-01-01 08:31:00   -2.6000002 x$ e1 B5 K$ D; _1 T" b5 H/ ^! ]
    2020-01-01 08:38:00   -2.166667
    7 f% L: I* f, S7 o2020-01-01 08:45:00    0.2000007 G; T/ C' p+ u/ q9 h  T9 p
    2020-01-01 08:52:00    2.8333335 F+ u3 g3 \( e
    Freq: 7T, dtype: float64. e0 P+ s! M+ T$ I6 J
    1
    - E3 d% R/ K3 X2
    + k) _+ [; a5 S- K7 l- w0 i3. R5 A" X" w8 a" t2 l" D/ h3 W
    4
    1 c+ o% v; l/ S' W' c& |9 k0 m' J5
    / c9 b3 X9 ^# P7 b9 V+ o6
    , ^* p$ ?: B1 b5 A7
    3 v5 w. _3 r0 G0 W) v/ }2 y8
    $ Z) E) j4 m3 f  c  有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
    5 m$ P) P3 v5 h6 x1 Z
    1 G. {+ R4 y' o7 {% |3 V: W+ Ws.resample('7min', origin='start').mean().head()
    $ P0 v9 A: ?* l7 _0 J+ F$ A; lOut[124]:
    ) ]6 X0 k2 s: g* k/ ^. F  U8 L# j% r2020-01-01 08:26:35   -2.333333! o- n9 t) G+ Q4 b( N2 N5 {6 B
    2020-01-01 08:33:35   -2.400000; t3 l! U& q( O6 B' A
    2020-01-01 08:40:35   -1.333333
    " S4 a7 Z" j7 S6 A8 d4 D9 t4 @2020-01-01 08:47:35    1.200000
    2 Y, [5 @6 P6 }. v2020-01-01 08:54:35    3.166667
    ' Q  x" H+ A5 F2 X1 qFreq: 7T, dtype: float64# d; R7 z4 o& Y
    16 y/ O5 [; S$ b6 H. t/ S
    2
    ! G' ]& Y- {, B% R& H3; \: Q$ ^% \5 b& h2 X
    4
    + z. [; [- L$ Q5" S2 Y- S+ o, c2 F; j8 y% ^
    6
    / j. k/ D# H# O9 Q9 o7 `$ j1 ]0 ^74 N1 c, Z% a& H" Q. x% I
    8( ~7 L- o6 T' ]% }
      在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。$ m* s3 R5 o3 \9 R2 k' F
    9 [5 W' r: R/ N
    s = pd.Series(np.random.randint(2,size=366),
      ^+ y, |" ~; p0 C- ^8 q  o              index=pd.date_range('2020-01-01',
    ' b# a4 s" T/ O  x5 h                                  '2020-12-31'))% J& I: w* T/ E+ P) Z* C  H, f$ L! g
    + T- j5 i# T0 B6 I) Q

    * ?7 m7 Z9 f4 xs.resample('M').mean().head()5 `1 v0 ]8 W2 v
    Out[126]:
    , z8 Z3 |/ j! E% U/ r2020-01-31    0.451613+ r3 u9 P% Z! G
    2020-02-29    0.448276
    $ W# f" H  `5 ?/ o+ z8 ?9 \2020-03-31    0.516129+ P- r: K. l3 x+ `! Q. x& `+ k
    2020-04-30    0.566667; y/ v! O- i) ~3 f/ @$ f
    2020-05-31    0.451613
    : H$ M- S6 L: u+ z7 ~5 R  }0 q% g' N1 PFreq: M, dtype: float64
    3 s- Z0 Z. |) q  H0 D. f- b: ]
    / T, Y' R' W. R$ X7 L+ [s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
    / [8 X- b; N' e# N2 gOut[127]:
    " F: q$ W6 I6 `, }: B2020-01-01    0.4516133 U/ ?! g; `, n: q. O5 x, G
    2020-02-01    0.448276
    - d5 a0 T0 d, t, L# ^, z2020-03-01    0.516129& O5 c- Q% I/ K6 U
    2020-04-01    0.5666672 B  C; L) W9 p9 n
    2020-05-01    0.4516138 n$ X# ^9 b" ~1 M3 _# y
    Freq: MS, dtype: float64
    ' u/ E7 G% R$ l3 [0 g7 J1 h  ?0 t6 }$ l* `7 V9 Q2 V
    1
    9 n$ H3 b) g1 l, U# ~! `2
    / \4 t' }, V+ @3$ p2 V# V' J& o$ g) t5 Z
    46 W: [' S* I+ b3 d
    5
    & T$ Y  ?+ S" x1 L2 g! ]( P- g6
    ; ?! D. x6 x0 O% t5 H' y7 ^% F7
    $ Z0 \( p9 C7 r/ X6 B8( c- ~, X' C" l0 q) p0 P) y
    9
    ' n9 ]9 K8 k7 [) w& H10
    ; {! W9 d5 Q$ v/ n& J* I$ w6 @11! A% N# G+ M! j3 S+ _
    12
    8 h# V) L2 P& R% I, P136 b- K" `% G2 q2 q+ b  s; |
    14' Q3 g6 K  E  ^9 d3 J/ Q1 R0 g
    15. ]# k& E2 U5 u. w
    16
    , Y* M$ B( }$ h  U& F17) n5 i$ c/ W. G/ Y
    18$ K8 G" m, |# G
    19
    - w0 j0 R5 A1 G% ~* s203 b! Z# q2 G' g( b. [" M
    21
    9 }5 X- V* L& z9 d; q22
    6 D0 Q! f9 D" ~4 M4 l对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
    7 Y$ f" P6 B# ?* c) d- ^d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],% j' K8 ?1 I$ W( I  S" Y! w; \) V
         'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    7 P& M/ I0 _  udf = pd.DataFrame(d)2 w3 h2 G; `( |: J( u8 ^- `
    df['week_starting'] = pd.date_range('01/01/2018',7 I. [% t" U) Y2 B) U/ W
                                        periods=8,
    / `/ F0 N, N4 b                                    freq='W'), t7 @: Z0 I. O) C* s  f7 ~
    df9 H- @6 u' S" t. N$ X5 v
       price  volume week_starting" m/ f, h. @9 b$ H; h
    0     10      50    2018-01-07
    ; `* _# }$ _! V+ s1     11      60    2018-01-14% \% U: [8 @) T: X/ f
    2      9      40    2018-01-219 q# ~& |4 @9 V( X& E
    3     13     100    2018-01-288 K; @7 B8 k- F4 `* v
    4     14      50    2018-02-04
    ' K! E' E# Z, K  N$ M5     18     100    2018-02-11
    1 q9 z6 ~+ K: p1 H6 u6     17      40    2018-02-18/ L1 [8 c( d. e, k
    7     19      50    2018-02-25
    # V! _, `% d" g4 ?/ q: Q/ [df.resample('M', on='week_starting').mean()
    1 }4 Z; ?9 h  L               price  volume
    & d* }( z( T3 U  ~week_starting
    , @, ^/ Z' h/ B- [4 T2018-01-31     10.75    62.5
      g3 u* f# S( L! ?1 B. i% ^2018-02-28     17.00    60.0% f% Q; v/ }/ G9 W% ^- w: }7 `
    ' u7 l. T6 Q, [% J3 K& t* E* f9 L8 z5 r
    1
    % Z6 H3 U0 ?  C" w2+ u0 b5 j& K) m: h; j
    3
    & d& n- {. z1 m3 |' M4
    4 X1 i5 p5 N. K6 y% T5, E( [$ G! v, j4 s. k
    6: b; ]& O5 ]1 w  Z6 w1 R0 f
    7, Z( i6 j4 T* K+ u; m5 @! R' R
    8
    - d- Q2 R- V) P% U& z  W/ S9
    3 {. B% @! W, I5 K10. E" ]$ @2 i# S# ^4 n
    11
    ( Z  s* q2 J) p6 t: }+ }12  V# }* E: o# {+ u
    13
    : m# A: f1 `& w, X142 G7 a' T3 f% _8 [2 m
    15
    " W9 }0 ?3 l1 k) s1 l6 _7 h+ F) q) K166 c2 Q3 h# j, Z2 U$ S7 l" P# f- o
    17
    ! h5 C2 E+ d& Q) b18; T% _1 a' K4 z% x  c5 A5 t% Q
    19# {5 w( a5 n! w0 @" J3 d# W
    20$ D3 m0 u$ j; ]" q$ U" E
    21. c4 B0 V. p0 {+ E' v7 i* B
    对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。7 Q5 Q" y6 \4 x$ Y
    days = pd.date_range('1/1/2000', periods=4, freq='D')
    : Q. y% o8 ]2 [" x2 l6 W8 @3 i3 o" {d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    # T3 B  `( F$ X6 B. F      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}, u6 Q2 d8 k9 {+ {6 ]
    df2 = pd.DataFrame(# t* a$ N+ F( e9 w1 h3 {
        d2,
    9 X9 s. C( o) r" T3 [    index=pd.MultiIndex.from_product(
    , i% [; a+ `/ k2 c' N        [days, ['morning', 'afternoon']]
      q* D! V/ w1 O& N    )( d$ `1 K6 D# @* V  x. y/ X; T
    )3 k" `; e, Q7 J9 H9 O0 h& j% c
    df2( |. a6 h5 a: q2 o  \( P1 W
                          price  volume2 @1 H" t5 Y* b
    2000-01-01 morning       10      50/ h9 |1 M5 O9 t: I
               afternoon     11      606 r3 G5 n" g' {9 V
    2000-01-02 morning        9      40
    " }1 x( j5 [  l           afternoon     13     100
    ' J+ [5 K: a8 v" T/ @% Q2000-01-03 morning       14      50
    / U: v4 H) \- D# ~           afternoon     18     100& P  U' B8 e! j2 a9 k9 P) C5 {
    2000-01-04 morning       17      401 C# K; o# g; a& e9 `- B! @9 S  Z9 \
               afternoon     19      50- P5 s5 i6 f' P( P* h5 P0 r8 g
    df2.resample('D', level=0).sum()4 ^: S/ d9 E) a$ ]
                price  volume
    ! i( W9 T+ a1 k% O8 D2000-01-01     21     110
    3 g$ a  H2 G8 u2000-01-02     22     140
    $ C3 a9 I4 l  h* s9 E' H2000-01-03     32     150
    5 ?0 \# d! u. z# s  R9 r2000-01-04     36      90
    8 o  m$ p. E4 i8 m' C7 W# c# N) `1 V, z) T0 D
    1
    & C) I, _5 @" Y- Y( X1 n+ Y2
    ) L8 T# N, c. }/ D; x% I1 y5 v* ~3
    ! N0 M% J, j1 ^# @! R; f, n! H4* M6 P1 u! d' C' y& \6 u
    5
    * @, K2 c9 U0 [/ S, `; \6& A1 V0 Y, v. J1 g7 f
    7) W- ^$ p' z2 S2 C
    83 M( v: e% V8 r) j$ U, M% X
    9
    4 @0 C5 H" i0 E" x) T10
    3 s1 M! ?$ i# A112 m) L  @: i) U
    12& J- E! S1 P1 O2 T1 f
    130 A# Q: h; P6 M: o( H2 v* H5 B
    141 x$ e; A2 [$ f  c
    15% i$ V4 _& T! t: u) O; A; m+ |# L
    16
    7 r! |% C/ K* v8 e0 ?/ S0 V17* q% w1 O. ?* T" d0 D4 j; }/ ?
    182 x2 ^& }# k4 s+ T" N; h
    19  X7 ]& N) V6 ^; e- }% ^
    206 A* \; ~  K, w# Z
    21
    $ @& D) C& X. R* B/ B22
    1 G- |' b8 U$ X6 |+ H3 \/ H/ _8 @" Q23
    6 H3 A! N$ P! I24' E/ B# T0 N& O" |+ g
    25  k; F8 f% \7 E
    根据固定时间戳调整 bin 的开始:
    9 j, S& o* U8 J) T& ustart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'1 i: Q+ D1 D, m, ~
    rng = pd.date_range(start, end, freq='7min')7 t/ S' X9 X- o  [: y0 Y$ [
    ts = pd.Series(np.arange(len(rng)) * 3, index=rng)1 z/ G" B2 Q- ~" D  d& A! E
    ts- ]9 @  L  M) k. V8 U- |
    2000-10-01 23:30:00     0; k3 c9 B6 J' P- [, E# k& z$ V
    2000-10-01 23:37:00     3" X" {( d$ w' E7 m# |9 Q" S' s) {
    2000-10-01 23:44:00     62 ~, w  V. P. f
    2000-10-01 23:51:00     91 o5 m1 Z& h- k) H& P: |# X" ~$ o: X
    2000-10-01 23:58:00    12
    " S2 g) b' ?$ \" _- o5 _9 Q% [9 q2000-10-02 00:05:00    152 r, ~' d4 c( K9 d
    2000-10-02 00:12:00    18
    & ^- ~) F5 W% z, [' ]3 T" h4 j2000-10-02 00:19:00    21  B1 D7 r$ V. e8 ~' i# @
    2000-10-02 00:26:00    24
    ! U  D3 m0 y$ X1 _# N, ^Freq: 7T, dtype: int64) m3 W1 L- n. X/ k
    $ w' ^9 A* a. J; s, r4 @
    ts.resample('17min').sum()
    ) s% ^' R8 x+ a* v2000-10-01 23:14:00     0
    5 ~* H+ m% U* X9 T2000-10-01 23:31:00     9
    + p% I, B" P5 t6 @( @9 k2000-10-01 23:48:00    217 [4 P* e5 f. ~9 [4 f
    2000-10-02 00:05:00    54
    2 Y3 U  ^; B! t7 f9 |2000-10-02 00:22:00    243 n; j; c. F& D# {9 l- a
    Freq: 17T, dtype: int64
    / _! m8 R1 w0 o+ n
    1 g) }. T$ R! {& rts.resample('17min', origin='epoch').sum()
    9 m+ ?7 L1 v. v% R. P  _- C2000-10-01 23:18:00     0
    2 S; h% u" G+ F3 `* N2000-10-01 23:35:00    18
    # b6 i3 C# a3 ^8 V7 n, m6 H2000-10-01 23:52:00    27
    . C7 s3 {) a6 q5 n2000-10-02 00:09:00    39$ t0 w, J- K  @0 z5 I. |- [; {
    2000-10-02 00:26:00    24$ O0 g2 |+ I) q& P- |, ^8 r2 x" `
    Freq: 17T, dtype: int64- `8 o1 _# S' U2 x7 _/ Y

    : H7 }; m& R6 T# `4 gts.resample('17min', origin='2000-01-01').sum()8 j( d7 ]' d3 x. v; [
    2000-10-01 23:24:00     3
    ' ?6 s# h0 f7 t2 _, R! R- v! D2 }2000-10-01 23:41:00    15
    . D# w; ~2 [% \% ?! O. _2000-10-01 23:58:00    45
    / c6 ^9 x2 Z3 x/ X2 J2000-10-02 00:15:00    45( t- I( ?3 v6 @( U" ]3 h
    Freq: 17T, dtype: int64
    ' T  v! d9 T  m* X2 Q
      ?, n2 [: ?1 }  B1: a) z" I6 S, k- E4 }  u0 E
    2
    1 f- v% r6 [9 V9 g* \3
    % ~. P! g% Y; h; ~4, v- A( E( E8 o3 U# w. P5 t' F! G
    53 _$ l. h8 e( w+ t) o: p+ V6 w
    6
    ; ?# |. d+ K+ e. \6 J+ w/ E7- S3 s$ @% x3 l- @* i; {- o2 |
    8
    % T8 q& ~. C( @: O4 h! T96 e1 S: F. v) m
    10
    * y3 A% e. j0 ~; F" P11* M0 ?: k. C, c+ }: I
    12
    2 S1 c+ U4 ]: C/ E13: }7 C% e+ o; r" {
    14, V7 W- P# ?" f% W
    15
    ! G0 y4 R9 o2 F3 _# @5 B16" G6 S, t  r, ?4 V* T1 Y
    17
    , n4 V$ }0 }7 W2 e& |; P5 y18
    ; ]; W! L: i- z# \  D7 [* Q19
    , w0 s" T1 G2 J/ c1 g1 ]; {206 W" ]  H6 P/ E0 a. h- x
    21
    * x" P& `/ I- `2 w; F/ c7 b* w22
    # F4 ]" F5 b8 }/ M7 u23
    7 S! |0 z5 @- v$ Z24: I5 c: Q- d6 h' M9 a( j+ @
    25
    ( v' V6 b( k9 f; l264 @) A1 b& B( _+ p0 j
    27+ Q% b; `- l( d; H
    28
    - K1 y. A! r- y29
    * k( }( `8 j$ p& @% T30
    8 ]* M8 S) B- P/ P- j31
    ! r4 m8 @$ `( {( f; U# g2 A32" T8 @. ^3 N, o
    33& _0 a% G7 L( u3 ^3 H
    34
    9 V* ]  p7 h% c+ |; b" ^35
    2 ?& A# C. a. e36$ |# g5 G5 d( F! _9 r
    37: A5 F5 V  z+ z  R  O2 n
    如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:' S( H& m# T( b/ U3 O% F$ u3 ?
    ts.resample('17min', origin='start').sum()
    1 C$ i7 k) d4 P7 Wts.resample('17min', offset='23h30min').sum()
    - E. S0 a; F7 b" R- X2000-10-01 23:30:00     9
    5 m! }. l% }4 L! i% d( w2000-10-01 23:47:00    21# _, ?+ c/ y* K6 y% r
    2000-10-02 00:04:00    54) X' b- g0 N+ p7 V8 A- \
    2000-10-02 00:21:00    24
    8 b7 i! R3 t" [Freq: 17T, dtype: int64
    4 N' o; i9 [& p  a& y1
    8 X( O8 Q- X1 L, T1 z2
    % _  |3 v# h4 b; z. s1 I# J* S# n8 X/ U3
    0 I2 }" A' {0 x5 r, Z. B, T4
    2 e2 I2 u: i# ^5# v" I7 |4 Z! G/ y
    6
    / h; d6 c7 w4 C- R% W8 [& K$ H5 ]7
    & L* r% q6 ~: [8 N* J% |10.6 练习
    6 S" b2 ?  h9 L$ n" P; SEx1:太阳辐射数据集
    / N7 [1 \) u% G现有一份关于太阳辐射的数据集:+ {7 V5 [/ K4 E% k9 M

    4 a  X) _$ y* L, Q: r7 }df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])- W; D" Z- ^- x1 O
    df.head(3)
    # V. U1 M- n% }- h- h: K8 I% z2 I: w& b! O
    Out[129]: . V  ?& M/ T4 d4 ~( T
                        Data      Time  Radiation  Temperature
    ; P7 q5 D7 k: }( Q# U0 T0  9/29/2016 12:00:00 AM  23:55:26       1.21           48  l, ^% N; n2 @6 ?4 u) S
    1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    9 u" g( Q% T9 f' v  S) J2  9/29/2016 12:00:00 AM  23:45:26       1.23           48; W9 S- `0 Q. y  U; s
    11 x$ e* Z$ D& C
    2
      r" O1 _7 ?; \/ H1 b3
    : A/ @3 B9 c' Z' c3 n7 t4
    2 a; P* j0 T) z; {  p5
    2 ^1 {* C7 f5 g2 s% @& O: i5 y6
    6 u8 K! n$ a$ ]4 h+ D+ ^7
    : g+ V) c5 `5 @3 x# t, B0 _8
    3 y! f1 M0 c0 T  C" Z( ~将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    ; }% p* L; K+ d* A( z) t6 p每条记录时间的间隔显然并不一致,请解决如下问题:
    % o5 i1 Q0 A/ b$ i) O, a" h7 f找出间隔时间的前三个最大值所对应的三组时间戳。+ E5 x7 C( q* e  _# V$ S. E) O
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。5 o  s! @: a8 l- O
    求如下指标对应的Series:2 t8 l$ p5 ]% S7 E/ I: |- |' S
    温度与辐射量的6小时滑动相关系数
    2 y$ y& E& X: t0 `2 D! r以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    1 n( w- p, f! ]  j7 F每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    ' a% A; D% b# ^% W& Bimport numpy as np: {$ c% O% V3 z; D0 F
    import pandas as pd
    # N/ l8 K- a7 m/ }1
    . i% {8 K- n* f' v- w6 O1 D- t6 g1 q2
    % S4 s: U0 p9 t( Q+ H1 \0 D将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    ) m$ g* Y* L- Idata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列& ]/ Q+ q6 ?3 @6 G7 Z. Z0 K+ E
    times=pd.to_timedelta(df.Time)4 Q" e; F3 s- V5 g  c, {
    df.Data=data+times/ V( V5 V+ r7 s9 L
    del df['Time']% p" V  `! x: M2 p# ^
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留- e4 X( H- I+ N1 N
    df
      H7 N' G' {# ?7 Y" ~. w                                        Radiation        Temperature
    & N& I7 R! A( K4 [( s% ^Data               
    6 |& j: B& g4 M7 J; F& x4 Z# _2016-09-01 00:00:08                2.58                51# u. M9 d9 O. w  Z1 g- z5 O
    2016-09-01 00:05:10                2.83                51' s& @8 G% e) u3 d4 E' ~$ B+ f
    2016-09-01 00:20:06                2.16                51
    ( s9 j4 ^* |9 v1 y2 i2016-09-01 00:25:05                2.21                51
    " U& J  M2 F( U/ c8 r7 M; I8 x% J2016-09-01 00:30:09                2.25                516 G& g( x" N$ _6 g7 D
    ...        ...        ...
    ! |( V( O5 q4 n2016-12-31 23:35:02                1.22                41
    $ T- x/ e. Q. Y: s5 z+ z2016-12-31 23:40:01                1.21                41% O& p. v1 u  K9 e/ k. F! D  t
    2016-12-31 23:45:04                1.21                42! d" W) T1 B! T# [1 _9 e1 J" `
    2016-12-31 23:50:03                1.19                41, E) ]: L' I1 D5 W
    2016-12-31 23:55:01                1.21                41
    ! Z# _9 t; ^$ g/ T
    $ t7 H  r) d4 c$ R1# G$ g6 i* O3 B/ c0 A
    2" {# M& ~+ Y2 J1 J
    36 D+ r% u7 f8 p6 {" z
    4
    1 s! G: a4 l! |5
    4 ~0 I( V& j/ X+ f6& U( I# ^. X" z% @6 m: e  v
    7) s8 J  u. n1 s- Y
    8
    4 m1 u, Y5 t& t2 d, r: a% |8 W9
    ( G. d" r" e+ ]2 p' C! Z10
    / J: M) d; C$ D, N7 j11/ o& u1 [% s" j. q- T+ h/ j  b
    12  E. _, R. U3 F
    13
    2 c  @& N5 f+ ^2 ?14
    1 z: M) J7 _1 p) |8 b5 V15, X9 v6 [( `7 }- d4 G3 {8 C( ]1 w
    169 p; ^  M: ]: d& x, V
    17
    8 t; w3 {, }7 [! K6 r4 A+ Q+ G18
    ( w, b8 V. Y' b8 K19. |) z5 ?2 j& j0 _+ L& s4 }
    每条记录时间的间隔显然并不一致,请解决如下问题:
    9 }% _$ l" H! A8 S找出间隔时间的前三个最大值所对应的三组时间戳。1 g  z. o7 h1 c
    # 第一次做错了,不是找三组时间戳
    / Q  b1 `) t8 J7 w3 t; qidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    8 w9 c3 e. \5 Y+ P. I9 A7 E, }2 qdf.reset_index().Data[idxmax3,idxmax3-1]/ M1 R) z6 @! E$ T  ?- i4 W! k1 D2 Y
    7 @/ B+ Q. ]* w
    25923   2016-12-08 11:10:42& O* m7 h$ p& M  o- G$ N+ l
    24522   2016-12-01 00:00:02  Q" b  {) f0 A0 a
    7417    2016-10-01 00:00:19
    # P0 o5 y* D; e1 P# U' M" ~$ e/ KName: Data, dtype: datetime64[ns]9 |# g$ k$ R8 F# H6 `
    15 B2 `# d% l6 A0 x
    2
    # X! I* i6 O. E( r: T4 h3( N" ]1 W" }, E# K9 K% Z& V' i. @
    4! ^. S' D! w: }# k
    5- ~. n% p5 z# z
    6/ R; J0 d; O- v
    7  p2 J1 v9 B, V* W9 b1 }
    8% j* Y) x3 b; e
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    & B9 d/ H- x8 R& _8 t% z; s- Ilist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
    : R0 i2 y( S  p! {& y6 ~
    4 [  d) k3 @+ c" K[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
    ! F% i  r% W) E# L0 c# Z" _% |0 A/ z (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),* p0 y, l. ]* W5 K
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]4 i7 D+ p. B: @
    1
    , X) Y; |  S8 b! M9 T6 l2; F5 _. E! z& a) ^. M
    3) S4 d1 Y4 G1 |7 x/ e6 B& B# A6 ]
    4
    % D6 [% `0 x$ Y55 ]  ^9 g8 f4 J8 Z  f
    6% Y  I( f2 n! G6 z5 f7 `
    参考答案:7 L/ \0 n: Q. t# u
    2 q+ c1 R- ]1 t0 D' y, Z: _
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
    & k+ y4 U* H7 S; imax_3 = s.nlargest(3).index
    * w0 j- `/ c. S! G& L, K2 mdf.index[max_3.union(max_3-1)]
    # d2 @$ H7 T$ R- ]( l
    ' x' A2 m7 d9 C1 e+ h( g4 mOut[215]: ( X9 X; H/ t" \* N
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
    3 ^1 r& `( `/ q9 M# D3 s               '2016-11-29 19:05:02', '2016-12-01 00:00:02',4 H& F* ?8 S+ q0 k6 ]/ q3 }- \# A
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],% x- p- L7 [1 R9 n! Z  v; f( S
                  dtype='datetime64[ns]', name='Datetime', freq=None)0 v: n- e3 S2 f& r6 x+ g
    13 h' r! m' `- k1 U" X6 L
    2: g3 J9 R+ @5 N: K
    3
    $ M; k: [6 _. ~5 k! C, E4
    ! W/ ?. M7 P; b5
    3 Z9 o5 R5 r6 U6
    & G6 k3 m5 N5 ^$ {; o$ V+ U( B7, e! T! L& {. o2 j: k
    8
    " y6 X3 F+ n. H  n- G, I9# l* y  b+ z$ _. |
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。6 m( |5 j# L. ?7 t# R
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
    4 W) s% _( d  ws=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    ) g9 I/ g5 B' Hs.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05). o& n; Q# N  M4 K6 g
    7 @( L( a3 h. P7 U
    (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)# }. }: s5 z4 @& y& Z
    1- [+ C. B* x; S3 r) k( ^  k& m' T
    2" j3 P% }* J9 o$ Q0 K
    3) v8 O0 G' R; T) m
    42 ]& u1 E1 L& X0 f- W$ g$ v8 _* u
    5
    . R6 X7 L! m0 r  P5 J6 P9 N7 T%pylab inline
    0 P1 P7 x" m; Z5 x# b& q& F2 R_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    $ z$ a7 B5 C' ^5 w0 kplt.xlabel(' Timedelta')
    : I1 x% W1 b- F5 F5 z) y$ J  dplt.title(" Timedelta of solar")
    ) N: B$ M0 @- J  F6 [! b0 J1
    5 M9 L/ F: f0 i2' w2 l( c( t$ s5 A
    3
    2 c' L4 a* ~: F; D3 ^2 q' g! H% F4
    8 H7 Z- X  W0 v/ y
    5 c* T  X8 R& e8 L3 K$ W7 F; I5 k0 A9 K% G$ U5 `- W
    求如下指标对应的Series:! ~3 q- _1 d2 F2 Z. T1 G; V
    温度与辐射量的6小时滑动相关系数0 x9 W! [, l4 Q6 I( p) P, c
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列7 ]1 ]7 R( h; m2 q1 h1 N4 @4 u9 e
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)( Z- j+ f' O( o# o, T4 n
    df.Radiation.rolling('6H').corr(df.Temperature).tail()
    ( ^$ O, s2 n! j& Z1 |
    % o$ n3 [: v: H  Y4 QData
    6 ]# n+ I" c7 H) @7 v7 @0 N2016-12-31 23:35:02    0.416187
      |. g9 k5 F' s/ j9 U8 j& L" t1 \: K2016-12-31 23:40:01    0.416565- `: l3 [: D( m& P" ^0 F
    2016-12-31 23:45:04    0.328574& T  B! n$ @- v) `* H7 v% K1 c8 x
    2016-12-31 23:50:03    0.261883
    ( `( A' o* ]+ V8 H2016-12-31 23:55:01    0.262406! e  d! H% ?5 C; Y3 r
    dtype: float64
    1 O- q1 t. x2 v" E* l* [1
    # R' r! ~+ q  ?+ \, V; b8 T23 \. I- `* f7 g$ x
    37 Q! [. E+ E7 D0 i# P' {
    4* v9 k2 b8 F% N  y" l  x
    5/ y) t8 `' Q$ ?4 T; e$ x0 k6 ^
    6  l2 N6 l3 P0 O9 u. V+ y$ q/ Z' r4 n
    7
    ( u) i. R- v, ]8 ^7 u81 o' t) ?$ _9 N6 s6 \
    9
    / w8 c4 E" r  `: Q% j  t6 e( F8 gdf['Temperature'].resample('6H',offset='3H').mean().head()  v( q4 ]: z5 u0 K) M

    ' R2 r# q5 ^7 @3 I0 o8 w/ D' `* HData
    " P' F, ]/ M8 ?$ n7 P0 k+ V; Q2016-08-31 21:00:00    51.2187505 B# o# B+ h  ?) M, q- f" u/ L
    2016-09-01 03:00:00    50.033333
    4 Y/ \) {& K! a& Z5 n/ b( ^2016-09-01 09:00:00    59.379310
    ! O" S9 U% k. Z( a/ ]9 K" V% E2016-09-01 15:00:00    57.9843753 Q% A3 a8 ]2 V) U" b; D' M
    2016-09-01 21:00:00    51.393939
    % h) N% M: c3 y( M$ m1 s# uFreq: 6H, Name: Temperature, dtype: float64
    ( `( O8 R9 @+ x( s& P7 _1
      d+ q/ J0 ~. s2. o' B: v5 Z3 m/ i# B6 c2 z) I
    3, S8 D! ?5 f( _. Z5 @' V
    4
    8 r) D# o1 k- a3 U' v2 N5
    / R+ I# O$ a% w1 Q' }, T0 i+ G6
    , O5 W3 D( _- i* b: |$ e6 P1 j. A3 k7
    " ~6 V$ p6 X4 o3 Z( ^8
    + I0 L) m% v- G9 a9
    + N; S( v) o4 ^8 y+ y最后一题参考答案:) S# M0 W( |' ^

    " R) g# U, h( t8 T. m8 A# 非常慢' r, i1 B4 [; {
    my_dt = df.index.shift(freq='-6H')4 v6 R9 p5 z* l* u/ p
    int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]/ g, ~/ {+ Y# w4 X
    int_loc = np.array(int_loc).reshape(-1)( Q9 T/ I! w4 d. B* h4 ^+ T
    res = df.Radiation.iloc[int_loc]
    5 N  ~7 v- B/ O6 yres.index = df.index
    / \8 ~% K* S, s9 L- z8 u% m9 jres.tail(3)
    & J, A! |3 D4 b. x) H- i1$ H; L% V6 g8 Q0 v4 g
    2
    9 M; f- w$ t0 Y2 N, \3: ?' F1 D, ]  ]/ y" r2 \4 p
    4
    3 F3 g  f1 G" e, c3 r5! u, Z# g7 g2 E; h5 Z' a
    6
    2 G- O6 ^3 l  N, @7
    0 e1 f6 {! b9 f. H+ B, b; u# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    , j+ v8 c8 n% f: G8 _" k; dtarget = pd.DataFrame(( z* W# W* h) W/ x; `: C
        {6 `  k, D# Q5 n- c: z' d
            "Time": df.index.shift(freq='-6H'),4 r9 W9 A. U1 L& G
            "Datetime": df.index,
    * X7 f, }$ d" ?; u+ Y* g+ h5 ]    }
    : d7 I7 X8 P+ u& x# U3 g7 j)& Y$ l8 h$ y0 e

    0 `2 e8 o9 d( s+ U  m- xres = pd.merge_asof(7 w: D- D5 U: x8 w
        target,+ ^* e$ B3 M, k* B. s
        df.reset_index().rename(columns={"Datetime": "Time"}),
    8 l" J$ ~+ {+ q, s4 M    left_on="Time",
    ! {- W1 _' n/ Z- r  B    right_on="Time",1 W4 J  h  ]2 ?
        direction="nearest"
    ; v* c9 s' ]  h3 n+ l).set_index("Datetime").Radiation
    " p( m# {" ]# u) o0 @- Y/ r) }
    & B# q8 i9 A" ?res.tail(3)" @0 a* a/ n: l' S8 F9 A3 t
    Out[224]:
    ) x. ^/ }. u" `4 W' S( gDatetime
    , P/ g  Y/ v, L3 S$ q2 p2016-12-31 23:45:04    9.33+ s. {, i% p  R7 R
    2016-12-31 23:50:03    8.49
    4 ?3 q& e' y9 e3 q* ]% Z2016-12-31 23:55:01    5.84! y2 S0 l- {' @3 S0 p$ l& s& P- \" t- J
    Name: Radiation, dtype: float64) q3 r) |( a0 u, @$ A, H7 ~

    2 L5 |' M/ M; u7 Y* p  v! U% Z1
    6 N! t& @5 F0 F: t+ @; k2+ U7 S8 B* K/ e  f/ M
    3, M9 N/ @' q4 I! h
    4/ k- q$ h4 M/ D5 w# q7 S9 p
    5- ^2 S1 u$ _1 ~3 H
    6- {+ [  h; ?4 t
    7! L3 |" T4 W  w  h; P" H
    8, @/ [& i. n+ I" \7 @; }  P
    9
    4 L- K" c0 b8 y, I' f10
    # Q" [8 S' ~# E1 _7 Z! b2 u118 J. n4 F% r6 S& w& R9 [% _
    12: C, D6 d( W( r% A, i
    13( }( X( @. `# M) C
    14
    4 r+ i' |, p/ ^$ x) C15
    8 r6 {/ G7 I2 g- l" s( N" T+ @' M16
    - m6 W* Q: N6 z* w% V17
    + [: |5 K6 b" x9 t! J18
    4 ?) V4 v& e; ~; ^& k6 L19
    9 x' v) ]0 M/ j7 I20+ i* j+ h; L$ o- r- d7 m
    21
    & Z8 Y) h8 r  \* x4 J0 q22
    9 H1 c9 t) |9 V23+ D' Q. _  T- X9 B' k
    Ex2:水果销量数据集
    * I$ \/ i' u6 r$ z) Y: |现有一份2019年每日水果销量记录表:
    1 Z  q' c2 @: p; [/ L+ |; R  X: e7 ~# y. d9 X4 l
    df = pd.read_csv('../data/fruit.csv')% {6 y8 u4 ^, e5 r& Z: A5 }" q
    df.head(3)9 P$ y; Y( J2 s

    - R/ Z! E5 n9 h$ x4 \Out[131]:
    : p( X& f! S4 N         Date  Fruit  Sale
    $ k! [0 \/ O$ }  G4 }- ^0  2019-04-18  Peach    15+ ^, J$ ^  _, O8 W5 b
    1  2019-12-29  Peach    15
    + ]9 U; u5 P3 [8 E0 r2  2019-06-05  Peach    19
    0 |" {: L' Z" K/ y4 n( @+ }1$ ^1 h9 C( m: t! G1 j& L
    2
    ; a  z* ^/ r) u4 }3
    7 l  e6 Z; i! v; R9 {; a41 ~0 P8 O! A% Z! u: ?. x
    5- X2 A+ H" S! M9 z0 J
    6/ Y5 C% |! I( V6 _0 x, X; N
    72 S0 G) D( n  D# L  q
    8& D' C& `- H  m, c  p; ]. W: P/ R! _1 j
    统计如下指标:
    4 }5 S$ E$ z; t6 U, b每月上半月(15号及之前)与下半月葡萄销量的比值
    . _# o# E! D) ]7 q每月最后一天的生梨销量总和
    ( _) p0 d' v  D  _4 ?9 c# s7 o每月最后一天工作日的生梨销量总和0 a/ T1 N; e6 _) O+ Q
    每月最后五天的苹果销量均值' I& j. u% i; v2 V1 u
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。" L+ o0 c+ y, K/ {7 t
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。% l6 m( I3 w* G9 B1 v% Z
    import numpy as np; X+ i. x* E/ P' n# ]' N6 [5 o( `
    import pandas as pd
    2 u% Q7 S9 O  u: s0 V19 R# s9 K( i$ q- Y- p
    2
    9 X: Z" C- |$ \# [: l统计如下指标:
    2 P/ W! E5 K0 {) Y& a: R每月上半月(15号及之前)与下半月葡萄销量的比值0 |; g# f9 I6 X. _) C% V4 F; L
    每月最后一天的生梨销量总和. @; d0 @, ?" I1 i' D4 I6 s$ x7 O+ e
    每月最后一天工作日的生梨销量总和- b' U8 T9 ]7 Y& f6 h3 w, i
    每月最后五天的苹果销量均值, ]/ d2 |) T4 `: D7 u/ U( H
    # 每月上半月(15号及之前)与下半月葡萄销量的比值
    2 v2 f) u6 b4 f$ F% Y- c# Ndf.Date=pd.to_datetime(df.Date)2 |' f9 L7 t3 H! G
    sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
      u# x2 N% B/ U9 B  z* G' C6 i* Ysale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    ( @( \) [- r  J, c% m+ Z# xsale=pd.DataFrame(sale)9 U" d7 n' R, T" P; ]7 y: K
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},1 L$ k# ^4 P/ e
                     columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
    $ _4 d! H; e% l8 S" V" x3 z- Fsale.head() # 每个月上下半月的销量
    ! t! F- m$ a# ^! q% P& f0 X
    & A, [- R9 n& t7 H. C+ F9 Q  Month        15Days        Sale
    1 k. Q2 I- i9 A5 u- t0        1        False        10503% G$ p1 E4 i# n2 M$ [
    1        1        True        12341
    ) h. C( Z" b7 M* V2        2        False        10001
    ( i4 J- q/ |) U6 t2 ?) F  c/ H3        2        True        101067 a% {0 t8 ~% Q  d+ b: |
    4        3        False        12814
    + A! ?; L% K7 c3 ^. K& `1 \
      Z. ?6 l2 |- \* J" s6 u) X% s# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序1 c8 @9 ?3 v/ d- O9 y$ b) G
    sale.groupby(sale['Month'])['Sale'].agg(
      n5 t( K7 M9 Q: p3 |( M( o5 P                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    . w  ]" p' q9 I6 l( h
    9 ?' N- I1 b- J4 ]Month
    & d- v# ?9 g- p" g- c1     1.174998" [3 `  d4 C7 y8 X3 j
    2     1.010499
    ' T4 E2 N4 l0 N2 q% G+ q- F3     0.776338
    3 X& B+ D  T8 O; H$ W. F, k# L4     1.026345
    6 C& R0 ^0 u, W1 H% M8 y+ Y5     0.900534( m3 R9 x# l3 @9 B
    6     0.980136+ l# N. Y) ~" W0 V, H
    7     1.350960
    ; C" d) Y3 G6 p% ~% l9 a# S0 d8 c  `0 t8     1.091584
    # B8 p) w5 i* P3 @4 s  \9     1.1165083 s/ o8 s% ?) c' M' n! ^: z
    10    1.020784' _3 I8 v  B$ R& H
    11    1.275911
    2 G3 i# d+ H4 G1 b12    0.989662
    : ^' C- `  S+ _* }Name: Sale, dtype: float64
    / {0 k; c9 T. Z0 A+ `
      F% u: C5 d2 {& |7 @6 f- l$ I1
    6 w7 E' X- e* B6 g' Q2
    8 N2 g6 M3 l# T" ~3 @& j, Z3
    6 W$ A1 x$ ?# q( [5 ~% z4. Z8 ]: G+ e7 C5 F- X# C8 ^
    5# \, e( W0 W$ v4 a: \+ J" ?9 X
    6" l. J0 x9 B* i/ m
    7
      ]. D0 C. h5 O# Y( m$ E) H8
    6 G9 |3 q$ r; A9 S3 ^1 u9
    ' ~. z' Q& z* J7 @10
    $ U4 F; a7 a3 b! ]7 l3 r) w, v11; G" F/ r+ G5 ^6 U6 O4 ^
    12
    " H5 F" w! ~( o13; L8 Q7 L# v, z# Q; m3 @8 n
    14
    * G% }+ Y8 A( n% A15
    ( F$ q  x3 A, R( M16
    # H' w* E2 E2 x, i5 c17. g: E- ]3 }4 E2 f( ^4 c& f' H
    18
    % z2 m1 y3 F  e( r4 U+ X. z$ U190 x+ g( g* z2 w! ~
    20
    4 d+ ^6 X+ ]# y2 Q4 `6 `21
    + @( n$ v, {1 w+ S: c4 h( g220 y, D% Y4 Y2 G# _% I9 l
    23
    ; i8 s+ W7 |$ y. _8 S0 p' y24
    " {  s. d8 ^4 ?/ z25
    & l! v8 J) ]. m' L4 p. m26
    7 N0 R# H% z; C27
    " c2 U) k8 _4 c2 l28- O$ h, I/ f9 r5 @( N' }
    29$ L0 b* K% V7 Q0 t' V
    30' T/ n' R. w3 a5 V- E; D
    31  p/ @$ L2 c0 A& R( F- g1 t% j- u
    32
    1 ^1 p+ x) A1 f# ^) Y33
    * B" e: }5 M6 L6 {+ o2 y- J34
    1 n: h+ G- L* E: N7 }# 每月最后一天的生梨销量总和  J- P2 q, y: b  C3 x
    df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum(); G' s. U( s# A# v' D+ o7 \) T

    2 T: ^' e+ i- u. o1 E1 W$ JDate
    4 L3 z4 x$ h2 @9 H+ E7 J2019-01-31    847
    3 \8 K2 B( I; {; I+ b+ j# K2019-02-28    774" t4 U6 \4 C" F6 g5 j2 K0 E
    2019-03-31    761# v% {" w$ ~, q  Z. j+ D
    2019-04-30    648
    * b% _! {3 |! ~9 P" W! a2019-05-31    616. S0 i" @1 q8 f; i
    1( i( d( k; G( n
    2* e; d5 b* U0 c/ ~7 @
    3: D7 S+ b: T5 _& _' ~
    4/ a: n, W& c1 \6 V
    5' y, U8 x" x: e. {2 e' C' p1 e/ _
    6+ n2 P0 i0 R$ u6 G' I/ J
    7' I. N, R/ `+ K9 J5 z/ H
    8; G  B: d1 D2 I! @5 b
    9
    . T/ _- c1 F! K) K# 每月最后一天工作日的生梨销量总和6 O: Z  ]1 z% u1 Z
    ls=df.Date+pd.offsets.BMonthEnd()
    ! E1 v0 m5 X3 Hmy_filter=pd.to_datetime(ls.unique()); W5 h% _6 O; Z) T0 j  R% [
    df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()5 X  v- e& \$ X! n3 `
    3 w- B9 j" {% w4 N3 a
    Date
    4 k' Q8 m: F. N3 E% `0 P2019-01-31     847
    ! \9 ~, {2 z. q  \1 Y3 a/ J6 M  [2019-02-28     7749 ~- ]0 T3 w9 E$ b" Y4 U6 e% ~
    2019-03-29     5100 n4 z; D7 _9 n+ a
    2019-04-30     648
    ! {: ^- D% t- N2019-05-31     6167 ^5 \' x1 [- W3 F3 m9 F
    12 |- {, ]# O0 u
    2( q7 y0 c; n8 I* E/ W
    34 }3 h: s- g' s- c( T/ ]* p# T
    4
    7 E+ W: O& V8 g/ [0 j5
    4 ~! S6 ?, W  S8 m% ~3 F5 _6
    5 c4 }( D. g5 t! x7 E77 _) Z, o9 P9 s7 k5 h$ l1 x
    8$ z- b$ \" C% C
    9# ^! H6 R2 s" X3 ?* \; Z
    10' v) B3 M6 J2 U4 ]
    11
    : c" K8 G! f) `9 O: Q2 s* C# 每月最后五天的苹果销量均值
      M: C9 ^* H+ M( Estart, end = '2019-01-01', '2019-12-31'
    # p+ n  P, Q7 |0 Z" Lend = pd.date_range(start, end, freq='M')! \0 G. @5 L  p& n: e
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差! Q2 }8 n* n7 M
    , a2 Z- z. c, N; D1 @/ k9 r7 f
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    # d$ o8 {( H8 J* a( Y( Ftd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
    0 K0 i2 _, Q5 p& C, gend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表/ {' F9 P. O) ]1 E9 p) p& s/ p
    9 `* b" z/ K4 |1 D" b
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量1 a9 k5 H5 ^, C: e% ^% w
    apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head(), k7 i0 K( l, a3 S, ]* [. ^& O
    6 W9 ?3 A& g4 }& Y0 m! T* w8 J! G8 I
    Date' M$ g: T) v  W/ [) }( c& P6 L6 j
    1     65.3137258 s. }- [) ]! d, A) d  p
    2     54.061538
    , d% M/ o0 _% R3 l+ `- ^1 [, z; D3     59.325581/ w, }0 _) M* V
    4     65.7954551 U$ V2 a4 R" d4 ~
    5     57.465116: W' i: i: y4 u0 o9 }+ ^+ k' ~

    1 ~5 w2 f" S- s7 \; U1+ J; @# L$ W' u: r, a8 m; E5 x
    2- s% c( y+ r  P( v: U
    33 o6 G* a# Y9 m9 C/ k
    45 M/ F! h  m) U, Q2 Y
    5
    ! T; H& D* _+ J( K$ V4 v6
    2 z# o9 J# e1 n% ~: ~75 H# k& y# T4 [) p' W9 n
    8
    4 P- J" s0 f7 ~7 ^: Z! c1 k92 k1 J0 S0 k$ T/ b. l1 @
    10
    # H$ ]$ v$ W% @11' n) M7 @$ Z% p$ i
    12
    " m" y3 J" s# R3 C* `: t13* S7 Q& ]  R3 V, m- F4 P- m
    14
    $ k6 E( ?  Q8 ?7 s3 A3 L6 ?15
    1 k# e  R8 _4 H9 C  L' U16
    3 }& U3 ?& n: x! U* J176 H3 C: K4 o7 P& M! v; B7 _( z8 `
    187 d; _1 }. |& m' S6 _
    # 参考答案:
    4 M  s6 e2 `1 A9 X5 x8 V1 Itarget_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
    5 Q: d5 N* f5 A5 l            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
    : }) q2 W. `% E. h
    ! I4 `4 v1 K  Tres = df.set_index('Date').loc[target_dt].reset_index(9 G* q: \# F% T) ]8 b: l" A
                ).query("Fruit == 'Apple'")
    ( h  F1 a9 R, C3 O" ~: |
    ! ?2 Z% m/ \1 s# X; Ares = res.groupby(res.Date.dt.month)['Sale'].mean(' u/ ]6 b* p0 b1 s/ u* C
                ).rename_axis('Month')( Z$ K/ Y' I2 v0 \
    6 c& [; t4 ~  t- C: S. S9 S% q9 }
    * |2 H" G3 M. d- x* K* k4 Q9 v2 Q
    res.head()
    " o( N3 K" E# COut[236]: & m4 Z9 [0 t! Z5 _+ H! E9 w
    Month
    ! W6 N5 r  W0 b9 e2 k1    65.3137259 g) e, ?% J1 q) m
    2    54.061538( ?  I+ f! \2 U/ q1 n0 q
    3    59.325581
    ) P- H$ ?: O4 j; ?/ O4    65.795455# e4 z. S  n  j* @
    5    57.4651166 Z" G9 U2 i4 u% X/ ~* N0 r
    Name: Sale, dtype: float64
    . h0 i; x+ ?" U# f$ A% F/ w% N) Z
    ) T0 I! b# \3 a8 L7 Z1
    ' _( e; c9 w: U1 a, r& m7 [26 N3 K, z! P: l# f! d* B
    30 o( n7 @# y& @6 I6 i0 S& W8 R6 K  o
    4/ A2 v/ T4 V: x' O2 k; \) r$ f
    5
    # d0 z3 B/ t( A' G5 S7 h+ B" W6
    ' n) N* d& d8 o3 k76 }$ a5 _& N" i# M# a+ \: I) W
    8
    - n& U# L' W. n9 V( I8 J9( i6 S3 {$ }+ f. p" P* c8 S3 r
    10
    5 [. B9 x3 n2 ]8 u+ g11: Z2 n- s. y" M, B; }1 m
    124 z7 K% I9 S' U0 N( r
    133 j3 ?3 M6 B; ~) s
    14# M" o6 {0 n, p) e
    15
    0 U* T: W& e1 G, h0 e! U16
    $ k2 H4 ?# y2 X% G17( \! U! e* H- z# v
    18, R0 \1 m7 n' r
    195 ?  |2 j3 E  o. o1 {
    20
    ; I  S3 N) B  w8 Z按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    + D8 `/ e2 b( r. F# Bresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.  P; p* d- y' D
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 ) Y9 m0 B' {: v: ?" E1 @
                                            1 h' x. A2 G. [' f# }
    result=result.unstack(1).rename_axis(index={'Date':'Month'},
    1 V% m$ ~+ n" N7 I( d                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.* S  M1 `( r# n0 n
    result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
    , ?" l7 w( f9 U9 lresult.head() # 索引名有空再改吧* A: r$ [. P! Y. b% f
    . N' o* v* r3 m5 F5 H2 _) I" _  I
              Week        0        1        2        3        4        5        6
    & w: L2 R1 t$ f6 T5 T4 U0 C3 K! ZFruit Month                                                        1 {, A' |8 A& M1 R: ^' Z3 ]
    Apple        1        46        50        50        45        32        42        23
    4 s1 Y! p2 n( d5 @0 I* uBanana        1        27        29        24        42        36        24        35
    2 d' N8 r' _7 T; y5 e/ v/ iGrape        1        42        75        53        63        36        57        463 j4 i, `1 @8 h0 F9 M
    Peach        1        67        78        73        88        59        49        729 a, {$ W. J. z/ O
    Pear        1        39        69        51        54        48        36        407 \4 N! w9 F% H
    1
    ) m8 F" [8 p/ t& b2
    , P0 b& m# x7 `6 I0 h3
    ) S6 Y; n0 g7 W& W% O$ n4% H* ^5 c! }4 a1 S
    5
    " }, h2 N) D( _6 O5 [62 P9 }* m, m7 E
    7% r+ z! I: X6 V
    88 x6 Q! S% w8 |. U) T' s
    9& G! B% O- E! [- k6 z! p, ~
    10* h: F1 z3 T  @  y& o
    11+ C  y* B$ w" Y2 w! n
    12
    ; ^, _* S/ R& w13
    # e; E3 r5 k# s7 F1 O& r/ g14
    ) b% G& ~8 s/ V5 J9 ]  \' [153 y4 H# J, k8 H5 u3 b2 w1 ~
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    * @6 `( B$ d' R5 Y$ r# 工作日苹果销量按日期排序
    ' V3 x* r0 Z6 N$ s, qselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()  W' K5 h3 d8 T& |
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
    # Q/ s- i6 u/ g1 Z* A9 ], {" wselect_bday.head()- q) ^! q# v2 w- D

    % {4 B; v3 z% U4 BDate
    % `  A! ?( T9 B) u6 B2019-01-01    189
    % @) k, L, ^" \( |2019-01-02    482+ \" Z" \9 G1 C; `8 E
    2019-01-03    890
    ) ~  E1 |8 }/ L2019-01-04    550
    9 H7 `9 C  V( m2019-01-07    494
    : [- W$ ]7 ^7 C/ h  f
    6 G' G' O/ m0 K3 W2 N1 L2 ?# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。7 q1 }- p; P: N4 ~* |: `
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()/ j' ^* Z. k: O1 [/ v
    : j+ b2 a! C: k7 R8 l
    Date7 q/ v0 n* e( _( j; ]8 S+ @
    2019-01-01    189.000000
    7 d; M" ]* ?, M4 e/ o, M, Z4 f2019-01-02    335.500000
    $ h; D1 y! R7 t; |( M" [2019-01-03    520.333333  [& A8 t% ^  k( e" s8 v
    2019-01-04    527.750000
    $ i- j1 q, h+ k  T. b! K+ N2019-01-05    527.750000
    $ r0 T, x3 B/ N
    5 d# Y6 E: p5 u3 N* Q! }————————————————
    7 F% z5 n. V; v版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    / f, @* Y. G- F2 r% e9 v7 Y原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913' Y6 E1 S% }5 g7 v
    ; i- ]# A. y- o- x

    & U, |/ d8 ^' U
    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-10-12 09:13 , Processed in 2.404932 second(s), 50 queries .

    回顶部