QQ登录

只需要一步,快速开始

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

    . `% k$ Y% p5 Y" X3 S+ l: Y7 G! }$ Y6 i
    , N4 r* {! H5 r7 G! o
    文章目录
    # P7 n, E% P3 P' ^8 ]第八章 文本数据
    " D- C  m% N- V( W, L' M' D' E9 [8.1 str对象
    : Z+ I% ], a  q8 ]& S8.1.1 str对象的设计意图' ^# ~9 D/ J. ?
    8.1.3 string类型2 I' r$ K6 l, y! Y
    8.2 正则表达式基础" o$ f$ @, D( _3 j0 o
    8.2.1 . 一般字符的匹配" ^( u2 O% |2 b6 z4 P
    8.2.2 元字符基础
    - @4 e/ g+ C* S& b+ Y8.2.3 简写字符集
    0 w* I# k- z7 k0 p8.3 文本处理的五类操作
    7 E) c( C8 S) {5 H" T7 x. K8.3.1 `str.split `拆分' O2 W# D2 N* d" {
    8.3.2 `str.join` 或 `str.cat `合并4 g8 W1 m/ S1 U: w' x# d
    8.3.3 匹配: h$ p) V5 ^/ r* h
    8.3.5 提取* {9 P3 ?& a" s1 `3 D* R- r1 ]( K) S
    8.4、常用字符串函数
    7 ^; C, \$ N# f8 M. T" n. X' r0 N8.4.1 字母型函数  K5 ]( @/ Z+ s1 q+ o
    8.4.2 数值型函数+ `) ]) z! J1 A) w  @
    8.4.3 统计型函数. `9 p1 T* L* e( D
    8.4.4 格式型函数
    " H8 M9 B' ~3 j. p5 l  b' X2 C8.5 练习# b& p0 r5 j( V2 @% B+ ?
    Ex1:房屋信息数据集- f0 ]9 q( m5 n* v  z
    Ex2:《权力的游戏》剧本数据集; P6 g: X, C( t0 F" ~2 u
    第九章 分类数据8 f3 ]0 u; X/ e
    9.1 cat对象
    & \2 G. u( T  ]4 D9.1.1 cat对象的属性. E# L: P% T  [/ S# r5 \
    9.1.2 类别的增加、删除和修改* V/ w% e6 @+ c! `0 \  [' q
    9.2 有序分类) \3 N) C# X" @$ }) {. `
    9.2.1 序的建立* v3 A6 H& P3 d! P" B
    9.2.2 排序和比较
    0 K6 @3 R4 Q) _2 k9 m9.3 区间类别, i& |6 v  T" |( k
    9.3.1 利用cut和qcut进行区间构造! H3 M" Q9 @' w6 p# i2 `) S) M
    9.3.2 一般区间的构造5 k" o- \! x6 y# x. ^/ N/ i) e' Y
    9.3.3 区间的属性与方法
    9 I$ Z7 m4 F1 v7 _* d  e9.4 练习& X% e7 v4 {2 z9 e: X4 u1 o
    Ex1: 统计未出现的类别
    ' W4 o% O1 E& {! t' h' a. V! wEx2: 钻石数据集, _: f4 [( [& W" ?+ C
    第十章 时序数据
    4 M8 ~3 I/ I/ q6 D# k1 E* b10.1 时序中的基本对象( I" f/ p, Y# U4 Q  j# \. u
    10.2 时间戳! j& l+ X7 l1 I$ U
    10.2.1 Timestamp的构造与属性* H- o0 }! E: i+ G' {
    10.2.2 Datetime序列的生成
    # Y! G# f& G% u10.2.3 dt对象( N! V7 o; q2 S; Y% t3 ~
    10.2.4 时间戳的切片与索引+ p, R, Q5 N- D
    10.3 时间差
    2 [/ [9 g5 G3 u0 M" @5 }3 p0 M. c4 V10.3.1 Timedelta的生成
    8 a1 A- u! G9 W0 x6 J10.2.2 Timedelta的运算
    . N4 V) U$ Q9 {) n. Q! S0 e; ?$ @4 Z10.4 日期偏置
    7 W6 r7 V3 h. E' o/ r, d10.4.1 Offset对象
    1 A4 w7 P; V4 m2 G10.4.2 偏置字符串
    - d, E6 G; z; e* X' f' ?10.5、时序中的滑窗与分组
    # j/ E- i% x' V3 l( R+ v$ A+ F+ b; h10.5.1 滑动窗口* k2 C6 M/ T4 Q; j+ h  R6 F
    10.5.2 重采样" W) z* |& N4 R) `+ a4 D* G8 m( s7 x0 l
    10.6 练习
    ) M- s& I. q, b3 DEx1:太阳辐射数据集
    ) s5 q1 P$ @# q6 S% K& v% pEx2:水果销量数据集5 j# Y9 Y" j) P3 G9 P
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
    # @) U) m$ P- f2 T" j" [传送门:
    7 s  U  F- z2 y0 Z: ?
    , w: {) b" d& Y* q8 c* Fdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)) o, ]2 e7 q. E: v( L
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
    0 a) S! \  ^, ^1 q$ X第八章 文本数据
    * I+ j6 y* l9 j  _# ^1 _- l5 u* I8.1 str对象) ?# s- p' U  t" u- p
    8.1.1 str对象的设计意图
    ) H! k* R, v$ ?  str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    8 q7 k; K/ @; F( i
    ' r1 y5 E% t  }3 }6 N: E$ nvar = 'abcd'
    & H: f3 l! j$ n, X/ \6 l; istr.upper(var) # Python内置str模块
    # r) I4 N; C8 d8 c) M$ U$ zOut[4]: 'ABCD'& l( H( ~( k4 a/ `# f1 ^

    " S& n$ m! x# h9 }; Fs = pd.Series(['abcd', 'efg', 'hi'])
    . E# [6 n8 n  u7 T) L$ S! |: ]5 f+ U/ H( |+ ]; l. o5 H3 u
    s.str7 A' x! \, N* O- o" T. u( o
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    ( N6 M. R+ |) n# e$ A7 ~6 ]0 A) g# ~  c7 `4 O6 s) @
    s.str.upper() # pandas中str对象上的upper方法
    . L; S8 v. n' ^5 i6 t- TOut[7]:
    ! }/ A, U5 a5 r0    ABCD
    6 d9 Y1 S3 A' E3 N" F2 ~4 n# U1     EFG
    : D/ U# ?# }' Y) `2      HI: B7 A3 \, ~. N. e
    dtype: object
    ; ~3 S% `( a6 e1 \1
    ! u( R; m! n% u. F4 F  M$ w2
    3 _8 W1 ]& j8 m5 y# \3, R8 E2 d7 Y/ B0 G: m! M! h
    4
    5 z1 g! f3 E- F/ E3 r5 g: Z2 V5
    / x7 Z% N3 i& d) y# B" \66 d, {0 p. q! W1 Y9 E" T
    7- A- Y% d( e5 ]& a" U7 V
    8
      I2 }3 T7 V) ?2 a1 R# E3 ^9
    ! l) @% M3 n0 ~+ I/ c10! l* B1 {, ]8 q# f2 Q+ Z
    11
    9 F- R) b4 N3 R1 o12
    ; g  d0 {  R$ y2 T$ f13
    4 H  {" c+ J4 f* `! S0 B) D14  M! }  j8 F" o% }; m% Z$ _7 ?
    15. M- q5 V0 O" \5 S6 `
    8.1.2 []索引器) m( @# y9 [3 E3 H+ q  `
      对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
    ' U/ M" e& t% \4 n6 k. R  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:' |$ z+ x& V* o" e3 k+ Q

    ! K3 h6 S; A, r  P5 F9 Ts.str[0]
    5 Q4 @; g8 j! m1 @, ]9 R5 MOut[10]: ( \) W) W: L9 Z4 e: |9 g
    0    a( h; U* y# V- H- [( }: b
    1    e& `' ?( F' b7 B0 A- v  w7 u/ j
    2    h0 V6 z& q* Q# P; [6 Q% Z
    dtype: object, ~+ j1 @6 T/ y: K" n$ Z* \5 Y) b
      ?, ^  R/ s7 F9 F8 L
    s.str[-1: 0: -2]
      @. C- j; F% ^* x, f- U" e" ^( [8 lOut[11]: - X" Z$ f$ D9 `) K
    0    db
    7 c/ e# W# y% F; }: W# t7 `: u1     g* Q& ]0 i3 q& Z. ^' k& U
    2     i
    % \# p$ P4 ^0 b; E' Ndtype: object
    . X7 c3 e$ ]) L& Y3 R' w* w! i- {; M6 W7 n/ u* g0 H
    s.str[2]
    6 q+ v7 S0 s9 f: i; KOut[12]:
    5 V- R& x/ x; z5 {* {% P) W  r0      c- ]5 R* B- q  X* |( L# x" W
    1      g: d  G# N" T) X( M3 B5 d
    2    NaN/ q# H3 d1 L$ v, F
    dtype: object3 L4 `9 M* U3 M" B* G4 n
    1 \- s4 B) O, r: A# z5 a
    1
    8 a- o7 `/ Q7 V( e/ [: K" E2
    2 T8 L, N  Y) T* f- }3
    ; @2 h/ R, C7 W( M7 Z3 a4
    * M$ j- m5 l) K2 y6 n5. G0 m2 n# b4 b* i5 U
    6
    ; c9 b" t0 y( _. q( w/ ]0 g7 ^% x7
    - a- a: p0 u8 i: L4 Q8 }- N8, h% f' Y5 L. N/ O: S
    9( E; t& U( G* K" z# j
    10
    6 {2 r: p# t4 D" e# s; `8 a  ~11
    8 A5 {' y! Y; r" W, e3 r128 ^, a3 v( W/ j
    13
    * {, }. Z2 P- c" ]( }14
      R/ l4 r" l! U9 X% p15) A/ ~# b% ?' F* C
    16
    2 ]3 x; L7 e  K# O4 O17
    / v, G0 |7 w. `9 y9 G* G18
    & {5 \' R' y% \: w9 L7 b# Z' l- D19
    : W# P- h3 g' a+ }& k1 ]# f( U20' `6 ^8 ?$ r( O% ~, g* d
    import numpy as np! S# j$ R7 n8 @0 F( W
    import pandas as pd
    1 W( F1 W. w& i7 ]
    * }6 H8 i0 k3 `" b& `& Cs = pd.Series(['abcd', 'efg', 'hi']); s/ f$ h2 ^/ w* P. a' @. J( u1 n$ h
    s.str[0]8 n, e0 I1 X5 z4 W' P7 [
    1& Z6 g3 B+ U; X, H8 C, y$ V& ?. U
    2
    % T, ~! ]! E4 e, p38 g8 `" z7 ~& `. _& f
    4
    0 W& j6 ^# T# o" E6 b/ V  Q6 G* v2 i58 C( R( f, k5 g! K+ |+ |# [( T
    0    a6 y3 `: H0 @5 {
    1    e; L& o- Y6 |7 g
    2    h8 k* F( x; c/ i" h8 s/ k
    dtype: object
    ; W; e0 J0 [2 Z7 P; R) R1
    - @7 ]3 Y! W6 h& D4 f/ I2
    3 _: c2 \7 w3 e4 E; V' i3
    ! P7 i+ T/ v- i( k! S( G4! \* l1 t7 q. [: x5 V
    8.1.3 string类型
    + C# D- q% a1 v+ J7 J  在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
    ' N$ G' }' _2 O/ ?; d  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
    # a# d2 i* [# ?6 a# y/ }3 g8 }2 t1 y8 b0 d- {; A- d; ~9 S. y8 J
    二者对于某些对象的 str 序列化方法不同。
    . ?) u8 l; {; m- w  |$ l9 i- ]: [可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
    * c9 r9 v) f2 W& m7 {0 B; a0 [% z9 Gs = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])  t% Q  I# J, Y9 E) e# S, K
    s+ U8 o5 O9 D, d) D
    1
    + w) F/ u7 ?! K/ {8 Y3 a5 b2
    ! `5 I2 F% W. ^3 G0 y0    {1: 'temp_1', 2: 'temp_2'}# D5 J% v. ~2 W
    1                        [a, b]) n8 V% E) v! v% Y% [* t
    2                           0.5. j0 N* q0 T. M8 f5 D) \
    3                     my_string& W- M" B( z! V3 r
    dtype: object
    + \  d  u  p! U0 _) ^4 S19 R4 ^* W8 C1 O" S
    2
    7 o" Z: e8 J3 r- I) F3
    # d1 p- w5 N, X( d( f4. z1 U7 a" N1 D; F' e- S
    59 I" j1 l' c& n( E
    s.str[1] # 对每个元素取[1]的操作5 ~# }9 \7 T1 h3 z! `
    1
    0 E# Q7 O6 p) U& i- Y3 e: h0    temp_1$ e$ D7 k% b2 |3 G
    1         b
    - }! Z) I) V7 r' Z: y2       NaN0 B3 s- {4 q8 ~0 P% Z$ B
    3         y$ [2 X4 K2 Y9 ~
    dtype: object
    2 k, X* g0 R1 I, W* e6 G1: D$ [7 }2 a" \8 V% h
    28 g0 I. j/ W- c1 E4 g7 ^9 k
    3& T& k6 M$ T1 ]& K, F) T3 A9 c
    4
    # t$ H7 N; G* T9 {50 N, u* \* `/ [7 S$ h( [5 S: |% V
    s.astype('string').str[1]
    5 ?, M$ I  f, e: A& @  L  ^16 E+ i: o5 m7 a. S( B( T/ u) Z
    0    1
    * V( l; t! `0 d9 H5 R4 V4 F1    '
    9 K. f$ Q$ W$ R. ?2    .5 w, j, ^0 w* B7 r" q
    3    y
    ( f' K4 g  L" h$ qdtype: string
    ) v, x1 }0 @- @, s0 f& }1/ [! e* A2 H( }/ L# u9 Y9 F3 S7 [
    2# H- v* j" r  @& p$ Y( _2 G% @1 F
    3
    9 \& b+ ^* @) y/ R- |4
    2 d% g- k" G) Y3 X5
    : ~+ n2 H4 W, C9 x, {. |2 P5 C5 r5 S除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:7 Z7 c+ b' [" n7 K" y9 z9 Z

    / w; }. p& q0 d当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    2 G8 @6 W% R! Q) ~$ w0 qstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。/ R3 r& q: n7 F  r4 u$ T+ `
    string 类型是 Nullable 类型,但 object 不是3 W+ e% L2 @# q/ J* N; e+ r: U
      这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。- q( b0 V" M: t0 A1 p. o6 F5 v
      同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。' A- C1 ^9 K, F- U4 r% z
    s = pd.Series(['a'])
    6 ]% D7 h! Z* O* z+ u7 b/ {) J2 b/ O( s3 w' \9 ^1 u) P; J
    s.str.len()
    % o6 }" W# B4 t, b1 JOut[17]:
    6 `8 [  u2 i5 K2 x! F. C& A5 }6 P0    1
      r' ~6 H1 h; ^* J6 ?5 C% rdtype: int64
    % R2 l9 ]) g$ \/ X" |7 E
    9 l/ j4 I) R7 ^3 u$ z. Y0 y4 Ds.astype('string').str.len(); e2 I; @9 s; p; A& I1 ~
    Out[18]:
    & u# L) t% d$ ]0 A. s1 H0    19 x/ `6 L4 w3 q* |3 t
    dtype: Int640 _4 D: l/ j& {0 m  J) _
    # L4 p0 Y3 e! k' S5 l
    s == 'a'
    " E3 x1 U5 C/ _: B' _2 F% FOut[19]: $ H* o4 J2 y1 L0 K) O* B3 P
    0    True
    4 R! S4 i9 Z% l6 u6 _8 c3 U% B  sdtype: bool9 y& J2 N. O0 O; O

    0 w/ n- t, Y$ G  M/ Ms.astype('string') == 'a'/ h* w. `8 l  x: A7 K; h
    Out[20]:
    3 q* F1 ], v1 q* j- g0 c" X/ W0    True
    1 x" I% Q% z# b6 X  S6 |; u7 {dtype: boolean
    % K- [" C5 K5 B
    ) `4 D/ k( ~  s/ g8 N1 Y8 bs = pd.Series(['a', np.nan]) # 带有缺失值- ^' L+ o/ o; L- W# ?+ l  q* T4 N8 `

    * w7 T+ W! p  E5 ~s.str.len()! ^2 k9 {3 [, u% x
    Out[22]: : V* l3 i3 S1 ?3 K* h# I
    0    1.0
    $ w' R2 s- }' Y3 B9 G/ ]1    NaN3 h; D, F8 j) _% A  \( K
    dtype: float64" P5 B3 T) L; f2 _/ T0 A

    - {1 k1 U3 l, S! X1 Cs.astype('string').str.len()9 m4 w. w1 q" H7 H5 P
    Out[23]: 3 T" f1 v. e2 Y+ C0 V' M
    0       1; ^" [" r; X$ c5 R
    1    <NA>: j2 t2 m1 z* R8 y, i) R
    dtype: Int64
    ' n, L2 ?3 P9 x% V+ i8 h
    0 @: r; D+ `( ?+ X$ ^s == 'a'
      Y: Q& x" O) H$ oOut[24]:
    : p1 R. j0 i8 p0 L/ B0     True
    ) P# i2 A* z+ d3 R  Y9 K1    False
    $ u7 D# L' ?9 b" }. X) b9 Xdtype: bool2 \+ I. ~8 I( z1 T+ J) L$ }' M
      S  R/ U; r4 B) X& P
    s.astype('string') == 'a') p$ Y0 n# _* w- M. d9 k6 B
    Out[25]:
    ; |* @( C# B0 r0    True
    ( P4 @( G4 b: Z7 G! z1    <NA>% t) I6 L" M% h0 b1 Z$ X6 M
    dtype: boolean, }; \$ \. [) Y& u* ^

    * q# c' A5 g7 e# x1. ]" g) y0 E  O' j- w3 L( J8 n
    2; E2 U/ N& z7 P1 w, Q+ ?7 U" b
    3
    # V, k- [5 c, {& K' E) n3 w4
    ) L# G3 ?/ N- c5
    6 f" A# v8 F" L! R6" x9 R% ?: f( \7 B1 x1 _: W6 k
    7
    * v- d7 b* T9 w8
      d0 o- H2 y' A9+ v  B# b+ f3 w* P8 t
    10- k3 M* ?% K0 r: }" V. Q- B. b$ P
    112 R/ Q! |% Y" u4 w* R
    12
    ; U8 |  v" Y7 T4 W  I139 k1 I7 j+ Z5 X# J
    14
    . l  ]1 s8 S6 V5 ~8 r15+ L0 C1 d% j8 H' H5 S% {
    164 ^. I6 I3 U0 A+ F" T# `' q9 C7 S
    17* x' w4 F% @& V: Q9 a+ }+ [. B+ V
    18
    - C& r/ K, m9 |19
    * d- R$ J* J  c8 C! Z. n- t6 X20
    & V6 G- j  @9 l/ F" q" M21
    * T+ Y. M! Z) A% J22
    : y2 E0 T0 b$ \7 `, E6 O23
    ) C$ b2 P' o6 k4 W* S; O% z24
    7 u5 q5 z4 E/ z8 N6 \' i- s25
    6 s* T% l# x; \. M% |/ f8 ~0 B26
    7 T5 E: r* O0 X, a3 E5 B2 G27
    : T# Q$ O! r( V' \/ X( y28
    " M, m; x" Y2 g& j290 @9 @4 i) k% g0 Q& T: G
    30' i& ^2 Y  j, K
    31& r4 Z4 W' W+ M1 d3 g9 w2 j; y4 X
    32, \3 D- R. ]! |8 A8 H1 ]
    33
    # `: o* i/ Q2 x. T3 U34
    3 d0 D- L  B  p0 A  n7 C* K35
    / K. E" D1 z+ k) @% H% O: h36
    * J, q4 g+ G* @37
    . s* ^, g) K1 M7 d% a38
    ( d- ?- X* K5 j' e39
    $ @3 n+ Z- _: }2 C7 G40& N/ D# T7 u4 m3 I' Q; K. ]
    41
    ' V! t# ~/ @2 |: u42; B4 C  ]. T7 g( J! t1 _3 c5 s% E
    430 Y( D0 C& B" K& C
    44
    8 `; F  p- Q- T3 ]& f# ?/ q) n+ O45) A; T5 Z" X. ?1 ^3 o1 v9 Y0 k& U
    46
    , F$ X+ o! A0 M# C+ Q' q47# C2 b- D0 t; o; ^+ E( o' g
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
      E! X! _. \: B3 H) J) c2 s4 J& c( t0 E2 I
    s = pd.Series([12, 345, 6789])
    1 q8 h; |+ q8 X8 l3 k: q, C( [, p4 j6 w0 ]/ u$ Q& K% u1 @. D
    s.astype('string').str[1]3 A& g* L  O5 t: o+ |& H# o8 P
    Out[27]: $ t- K6 X' b7 ?; H8 X8 C( h/ @
    0    2
    $ b( O3 [% _4 p1    4: u( l& G# ^+ ^! W# R, ]: S$ a
    2    7
    & U  L" v! S. l# N2 Ddtype: string
    6 ]$ R9 J( |# R, k17 m* J* |8 @( l9 C" d: r: U, @
    2
    ; Y% o7 u8 \" o  [/ L8 o# Q3
    5 m5 e+ J4 O) l, ]7 T2 t4
    . m* k' I; i8 w3 d  Y5
    + f3 q' u# N4 N5 l6
    4 q; N8 d4 a* a! l7
    ' W9 f! V. H0 i% l8$ V& N6 J" \! W" N
    8.2 正则表达式基础2 t7 F; r1 T2 O, }" X- G) K: f! v
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书+ V) k* |8 L+ q$ H! C& I

    / b- S  s1 f! C+ p: d+ W4 z8.2.1 . 一般字符的匹配
    ! \2 V6 T4 l9 J/ Q正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    7 q; s6 }' t. \/ a4 A2 a- X$ [' r+ |! w  I8 N$ T6 K) f. }, l
    import re
    & }* g2 P5 H3 C& o5 K  U0 F+ d; F- {, ~; G9 P( d% v
    re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配8 W" ?, l1 O' k: L  p# N
    Out[29]: ['Apple', 'Apple']
    1 }' i6 h6 y% `# [) m1
    & s$ {% _1 Z  S& h0 R2
    ( m$ i8 K8 P' L! a3& f2 a2 h8 a: N* J4 H" k
    48 w% {1 J1 z0 G. V+ _2 I
    8.2.2 元字符基础5 m6 C! I" ?: p6 o8 D' C/ ^# }
    元字符        描述
    ' T+ }" g& I6 p' c.        匹配除换行符以外的任意字符
    % y6 c! v: b' w[ ]        字符类,匹配方括号中包含的任意字符
    , J$ ?- N3 w+ P[^ ]        否定字符类,匹配方括号中不包含的任意字符: `% x" X# k" W+ U1 C
    *        匹配前面的子表达式零次或多次% K" P5 Q' r* M& ]/ ^- V
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字1 k; d4 K2 k8 |$ ?# |$ t
    ?        匹配前面的子表达式零次或一次,非贪婪方式
    5 x8 R  _2 D/ R; r( G{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式7 c1 ]& M# w, ~% n2 c- s
    (xyz)        字符组,按照确切的顺序匹配字符xyz
    0 J' C: H6 B* _|        分支结构,匹配符号之前的字符或后面的字符
    / b# G5 k% I# t' b( V  ?\        转义符,它可以还原元字符原来的含义
    0 d# B7 ]. ?9 ]. R1 D! t& l^        匹配行的开始
    ' _8 j9 a9 w& O7 p/ h; A$        匹配行的结束# M- m+ g' X- @
    import re5 u3 x( B: J1 \% ^" |1 R
    re.findall(r'.', 'abc')
    ' k9 r5 |5 I+ Y( H: `& TOut[30]: ['a', 'b', 'c']
    * l4 B- z. Y4 y& D4 P/ ~- ]# p3 L) v' o6 V7 A5 }0 h0 h1 a2 d
    re.findall(r'[ac]', 'abc') # []中有的子串都匹配9 f2 y( X% a8 {- a4 U$ i
    Out[31]: ['a', 'c']" u# C/ r% _7 n! @% k6 b
    # Z0 q. @+ B' t; W2 {; [
    re.findall(r'[^ac]', 'abc') 2 g5 K( s6 y6 n" Y% `
    Out[32]: ['b']9 T1 L& l, M0 N& L9 Y

    . p: M! u# y+ k! o0 F( {' i& S/ Bre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次- o' ~% w3 a2 ^# |6 M% X# W
    Out[33]: ['aa', 'aa', 'bb', 'bb']7 Y3 }, I- j/ c* y4 u  y4 I  o+ B
    0 U  Q# X4 h& ~% j% W8 `- k5 ]3 _
    re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
    * q* x# c" G+ ~7 R. x9 |6 H, B8 B* LOut[34]: ['ca', 'bbc', 'bbc'], l1 d; v2 X+ b
    3 b; _1 B7 j4 k/ \7 }! [1 A
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。2 X$ y$ q2 Z# C0 s" S3 V, s) W
    """8 K) i5 t$ n2 Y. E5 ?$ C; B
    1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。) x1 Y0 F8 s; g- S6 D
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
    + n0 G8 @5 W9 f, F+ J/ g9 s8 @5 ?0 M3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
    $ f- t) X; _3 |但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    2 k' ~) F& F. ]8 u0 W, c"""
    ' G( T) {) j! M; W( H# d% M9 p# l- U/ X2 @
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。9 u; k; x. D  t9 M# X* N, v/ K/ t
    Out[35]: ['a', 'a', 'a', 'a']: k# k1 a4 z0 W) S; H+ b1 p$ y" _9 c
    ) O* T# b3 V2 [2 f
    # 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
    9 t* f0 c3 t1 s) m# K: Y3 c* c  a# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。+ A0 Z+ e: }9 }2 I7 W
    re.findall(r'\\', 'aa\a*a') 5 C# E$ B: a# b
    []. R4 Y! |! R) b9 h+ \
    ; u8 X, y8 g( l( q" C
    re.findall(r'a?.', 'abaacadaae')
    $ ~* h0 t) A( p1 z" ], }Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    4 i& @2 W% n1 z: a9 Z) }5 V6 ?4 P4 H$ Q' U8 N9 P) O& j
    re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
    8 B- d+ ?, j: n; B[('width', '20'), ('height', '10')]  Z' J) ~. M8 r: e

    3 Q9 \% i4 k) E# q* p% L. E1
    # _/ v7 {, }0 O8 Y  ?( r* @. C2 d- n2
    # Q0 q& @" H, p. B9 I3* d- \% C0 f) _/ I) N; Y5 y4 l" J
    4
    1 L8 b$ n7 t0 ~5/ E% Z, a% t1 ?. u8 p
    68 `- V; |, g- O8 }
    72 e) [9 e, k% _7 O+ `0 R
    8  L1 w$ d9 [, a
    9
    " q; }+ \: f: ]101 U8 P! s1 Y" p# D" }
    11  y. `: b7 Q/ l- E2 V, V. H
    12% y7 B1 B' {1 h0 l- a4 F* T/ Y  ~
    13
    & ^0 ]# U( P1 [4 C& X; ]8 S3 P6 z14
    3 @9 B6 r' `% P15& e% ^0 u# i6 a9 g2 G! f4 c( {
    16
    2 `& p, v6 o9 x( J$ y8 Z! u' `17% d; J0 w1 [4 R  x6 T8 J
    18
    $ V- X' {6 X; r2 I" J# r4 i19  ^' c( r+ \) {$ K
    20
    1 S/ o1 G# V  }4 w21
    2 S8 s" v. k5 R! K/ G226 }" H" E9 S% h, r3 e
    23
    ! F% y  J1 ~3 {+ o' F# s24: x0 e) p, V8 ~9 L+ C4 N0 w
    25' l8 h: @5 z( x2 K) m
    26
    : t8 u; e. V9 E* X27
    6 U, x1 c# D. N) C; _8 _6 e288 N% O, Z  |4 {) Z3 n1 [
    29
    & X0 A- I9 ?* N, @30) c2 l1 F  B0 O3 M
    31
    5 t1 N$ r, v; r; ~328 _# R3 @! Y  B1 A  \! r$ V7 E5 i
    33, s/ c0 s. r5 X8 P4 ]
    347 j) L! M. M, B* L
    355 ]& H; w! d& v( F& J( R# |  {
    364 M, }4 y4 X* ~, C
    37
    7 d! w  B% k1 q6 [- G1 |* Q. b4 Z* |8.2.3 简写字符集6 d: C; z. e! S; Q
    则表达式中还有一类简写字符集,其等价于一组字符的集合:! [% Y8 J. U) q3 R

    8 M# r  H" o( g, Z" E! L简写        描述
    % @: o/ p1 {( G9 S$ j\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
    : W" W" M; x7 H' \! R/ m\W        匹配非字母和数字的字符: [^\w]
    ' g+ Y( Q( B1 n; i9 p  d1 R\d        匹配数字: [0-9]% h+ y6 k( M9 W0 k. b$ }, ~
    \D        匹配非数字: [^\d]% _8 G' o) v6 R# ^  }
    \s        匹配空格符: [\t\n\f\r\p{Z}]
    1 D4 u0 s- Z& @$ D\S        匹配非空格符: [^\s]
    9 S9 m) Y- y, V5 s6 F1 v\B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。! k4 B4 G: y2 y5 G' ~& p/ T! V
    re.findall(r'.s', 'Apple! This Is an Apple!')1 F/ V. n$ ]" m
    Out[37]: ['is', 'Is']2 m) F" ~$ `/ Y2 b. o& _
    . v/ \1 }# u! L7 w4 r
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次% l/ E; J4 f& K- Q  w% q* ?' ~: G& \: d5 X
    Out[38]: ['09', '7w', 'c_', '9q']
    8 t# F# a' w% j7 p) k
    7 \0 |7 ^) U$ w# |re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
    2 _' J& }( ?9 W( _Out[39]: ['8?', 'p@']
    % I" e2 @( ?* E" L) {. e  L. |! h
    re.findall(r'.\s.', 'Constant dropping wears the stone.')0 K- ~$ V6 l- p
    Out[40]: ['t d', 'g w', 's t', 'e s']% @7 e, k. u7 D  p+ n- J8 L, \0 i7 Z

    - B0 `- W: C8 u' [re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',
    + @- z9 d( F* `$ v" ~           '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
    " k* h. d2 i1 b- k# I4 U4 J& T
    3 @, Y2 K4 A& Q) F0 R& i1 EOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
    3 K" \3 E) ]9 C  h5 d6 o( e% q% f" O; H0 \- [/ O2 p
    17 s9 r6 W9 r2 f; V5 A9 g
    2
    0 o, _  ]/ a: O& |32 _" f. s: _$ X
    4% \3 I6 S2 m: V1 {
    51 O/ a. U. w" T! M5 F
    65 V3 F+ ~5 h% ]: }% l
    7; e9 \) F5 \" Y3 k6 R5 L1 r
    8
    ( D# E& E4 ^/ q0 }6 V- i. g9: L% r  l. M' I" I- |+ E/ ^
    10
    + q4 j3 k9 b' H# k; x8 l111 J; H+ p# C3 c7 X, @; y' w
    12
    7 U, p, V( B7 S7 l: N13" p- _- A; B" |( R* N4 u5 f$ d1 b
    144 b' l( V$ ^/ R; m. C
    155 z) [+ g4 @5 q8 [* U1 p7 ]+ {
    16
    1 C* \% o, [# D6 X! V1 H! |; D8.3 文本处理的五类操作
    . g8 b: a& l( a0 l: o8.3.1 str.split 拆分, J$ R0 O* \2 I1 h( {
      str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。$ n# l9 ^( V8 v# s

      K7 x  L" t  V9 _$ d6 p3 \s = pd.Series(['上海市黄浦区方浜中路249号',
    . x3 D- W7 u3 p# Q/ R            '上海市宝山区密山路5号'])
    $ e" @- j3 O4 N: U/ k. g* A. g, ?! q# ?

    2 v* f+ _; H  {; H( B0 f/ vs.str.split('[市区路]') # 每条结果为一行,相当于Series
    4 O: X$ B% R# J# E  ^( \' r0 F# `Out[43]: " M6 Y* f1 @" s# J( N" Y
    0    [上海, 黄浦, 方浜中, 249号]: l/ F# E6 C/ l  V7 S
    1       [上海, 宝山, 密山, 5号]
    # U. l! E3 }( d% N0 Edtype: object
    ) H- S" H; o( q* P- C
    2 _' B- @& o4 m8 W9 qs.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
    5 d+ Q- {+ h& i$ |+ K  cOut[44]: ; p% K0 ~8 G3 v: V2 c" k7 S2 E
        0   1         2" i7 z; u9 u( b- Z1 ^+ W
    0  上海  黄浦  方浜中路249号
    ; p0 K" i0 @5 n  A# _6 _5 v1  上海  宝山     密山路5号
    6 u" x7 e% T+ l, C5 ?2 {: @2 v1
    9 @" `- \' Y1 r6 O7 f2 K( ^2
    & M8 i  b3 \* [# |3% N* v3 o: V0 l1 Q2 z# g1 u
    4
    0 V% N' U9 R6 f8 ?2 L$ a* v5
    : p6 |" G2 }# x1 p: O& H6
    & j# y" `' N& z8 t8 A7
    & }3 S% Q. Q* ?: ^0 V9 a8' Z; Q8 ~( w, M5 H- [
    9
    $ N, e, z; D. c  B% V7 X10
    3 X3 F! `0 P! C$ G. a1 H: s9 H5 [0 G11
    5 L8 h8 ?  S9 y1 R2 W, b& \121 K& x7 A/ `3 d0 Y' `! O( a+ `
    13
    * g1 h* k4 W7 c. Y145 f/ K1 O* m( x. b4 ~0 K
    15
    ; f/ p+ [8 |4 {  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:4 w  `: a5 Y# i

    5 X3 c% o& A, |s.str.rsplit('[市区路]', n=2, expand=True); w2 b! K( A/ U  D; P2 B! Q
    Out[45]: 0 b2 q4 P8 ^7 L
                    0" n# u9 c% ?5 M, z
    0  上海市黄浦区方浜中路249号
    8 C5 o/ {/ n8 z* n0 t3 r' l1     上海市宝山区密山路5号% W- |; a& d- m$ E( w$ F
    1
    , L/ d) `- b+ S+ q) S  f* B" K4 j) ?- W2
    & H4 e* D: o: l3 b; L3
    % p3 P- f' ~; O! y! o+ j4! m, H5 D" V- K/ `) b* ~
    5* K* U9 }% I  J, ]* D1 H- ^
    8.3.2 str.join 或 str.cat 合并  k6 k! M: Z# Y: \; Z/ a( {
    str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。! F% A7 N( H( J
    str.cat 用于合并两个序列,主要参数为:2 v( T* [: w" Q7 E, F
    sep:连接符、" b0 K. O6 n% o/ c/ j
    join:连接形式默认为以索引为键的左连接, X0 N3 C# X; ~& L7 o2 M
    na_rep:缺失值替代符号) u0 P! p' i1 Q: s7 C' |
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    $ M. k; M$ h6 X% o" j9 A3 u6 Ks.str.join('-')
    & E1 H; w- ^/ Y) o; m; [Out[47]: : Z6 S0 r6 O+ y% [
    0    a-b! \% t: L& @: q2 d6 Y
    1    NaN/ k2 x6 |- ?: E; r
    2    NaN6 g, O5 p4 c8 n3 K3 l, H
    dtype: object: f; `" e5 }. r! C
    1/ Q- |. C' x2 k& D, _
    2
    / b. U- }+ Q8 W/ |3  T- G6 q' N/ R" ]# k
    4' p  {* F; r) _" ?( ^1 [
    5
    - g# y6 }" E2 }) O6, f2 E7 I8 Q5 W" {
    7
    0 P6 F' H# T# m* ls1 = pd.Series(['a','b']). h! m* x- c- s& X2 |
    s2 = pd.Series(['cat','dog'])/ i9 k3 L8 D, t3 ?% O; B7 X
    s1.str.cat(s2,sep='-')7 }! |6 b) S: K; d7 a
    Out[50]: ) G$ @5 Y" w5 v  N( Y
    0    a-cat
    # V  u+ j5 I- |% P# I* h  `1 k" X1    b-dog
    * d2 A1 q$ C& f2 Tdtype: object
    8 O9 h+ d( @/ z1 }! P& |9 @8 m; A, s& v" h  n7 m! l/ M
    s2.index = [1, 2]2 |( j3 C; S" d- v
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')$ G! Q  }5 c6 ]
    Out[52]: # G) j, j+ @# s1 p
    0      a-?
    , i5 d/ x) K2 U+ j! e1    b-cat
    : L# k+ Y" G7 o( d$ K. d2    ?-dog
    " X" O  U, h& l; v# edtype: object, u4 \$ u! g, Q+ e+ p
    1# O" p) y$ }' p: Q8 K8 k# e$ F9 U
    2
    % x; ^  p- l  |1 s9 I3 `2 V6 r31 O6 \# F) ]0 q! c0 l( D
    4# Z- B4 Q0 ^& N1 m' e6 r  _6 i0 F4 }
    56 _* y" Q+ M: p  g
    6
    : a/ q) ^* d4 W* X/ x71 S8 H) E7 [7 \
    8. P' ?6 T4 f" u) }0 _- e
    9' j. G/ H3 B2 n" `% r
    103 P5 w8 t; O+ a1 B% N1 _( m6 ]
    11; v  ]( R, a$ [( Y' |
    12# a1 u5 i& ?+ C
    13- T3 L; n( G' g% Q
    141 f" R- V5 u6 w" c; S# q" _1 V8 ]
    15
    ' u0 d$ x, n% t* G/ e0 \8.3.3 匹配
    9 t; T# L3 n; T3 l7 Lstr.contains返回了每个字符串是否包含正则模式的布尔序列:2 c/ I% d/ }6 F
    s = pd.Series(['my cat', 'he is fat', 'railway station'])9 m9 f1 O0 Y7 r- q/ v
    s.str.contains('\s\wat')
    & @, f: q& j' W5 J! K4 M' ^& }  K* j3 f: v3 e6 f$ |" t: a' N
    0     True
    + u8 T, s7 p5 @1 y* P$ z1     True4 t# b' K. y6 H+ `
    2    False
    & V( w  L' M! X( s8 W! ]8 y8 Gdtype: bool7 y7 S: l8 i& m0 E/ F6 ^
    1
    5 @& D( Y2 B# S, t7 X8 ~; g  R, l- W2
    ; z, x* o  i7 f7 t+ H; O4 `# P3, r! H% }% g7 Y- H- ]
    49 `+ c$ b5 ]. o# Q/ C. O' P  R. a
    5  g  }1 z- {8 ~) K4 L% A
    61 B$ l2 b6 J& Y7 ~& o
    7% G$ U4 R- A$ [% a: D$ W3 ?  M
    str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    , S# D7 y+ L7 ^6 a8 k* h/ Ts.str.startswith('my'), l. _, D; B& M- m; B
    ) Z4 X# a/ [# b* N6 I2 E& G
    0     True( F/ _- ^7 ?. k
    1    False! \9 y; g7 }( [- K/ O+ M- R5 C
    2    False
    ! ~: t! T- r# W9 S; M5 `: Cdtype: bool
    $ P6 b* {. W& B  p1
    2 Y% [' g, o* l2
    % p) w! @. D' D$ [3+ ^6 y& ]1 g) ~  z2 q  L
    4
    3 T; P  a9 K" n6 G5) ]6 ^  k" o% c6 X
    6
    : w: {5 K# [) t3 L2 T+ d. F& \s.str.endswith('t')7 }) [  N5 X  a
    # |* G) _" W5 ~6 x  x  K& J0 ^2 k8 J
    0     True
    * {6 H: E. q. t0 r1     True' V1 _1 S4 T& `& W% E6 q# N2 _
    2    False
    . U2 x/ ?' Q/ C3 a7 A  gdtype: bool
    8 m: y' G0 J# N" w1
    $ t5 o0 m3 h  v- c3 i& U; [. q! W9 ^2# u! b  L! h; B1 I7 B* V. U
    36 N6 o4 S4 W: ^* p" N# ^
    4
    & h$ f  a5 S+ g2 f7 E( x$ V5
    ; }4 f1 ~- R, [; P6
    9 D% i+ ?+ ~5 r% m% \1 o0 qstr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法): V  Y  ^3 n- w
    s.str.match('m|h')9 Y$ L) n: F& Y4 x- b7 o# a# Q
    s.str.contains('^[m|h]') # 二者等价
    $ o( Q' j  s/ k- s0 s5 J
    + b+ w' j6 y$ q7 f' f0     True
    % r0 T9 l: b: i% E3 Y" w# t1     True
    7 y) z7 u8 l5 F6 D) ^5 W2 |! x+ I3 y2    False
    % A% l+ k% Z  G& gdtype: bool7 j$ ?8 ^* o# h+ H0 N0 R; T
    1
    . p% A. Z, L" f/ N8 j, ~2& h- N/ R; j! F& q! y+ {
    3
    9 C  P2 V' ~: w* F5 o) [3 K4
    ; f6 ~- z. v. n5# k) i2 e+ \/ z, c/ t, F3 u* N
    6$ J7 x4 [( E5 X* I( Q; a8 |) B" b
    7) J* ^* P. u( l  o( h* Z; Y
    s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
    8 P$ D& `: R% m4 d  S$ W- xs.str.contains('[f|g]at|n$')       # 二者等价2 p: e- b: h) r0 J8 x2 h1 J3 m5 j7 r

    % g* a$ D4 Q3 A7 S0    False6 ~! A* G$ l' v9 t
    1     True  d( u' h# h1 ~* i& y3 F
    2     True/ {/ e" X8 l- b. D
    dtype: bool
    1 W) d* y' k( I* V2 T" ?1
    6 H  w* J5 Y' N# W2
    $ s. F9 a: u( \/ m+ E37 b) S! \. [4 T4 P
    4
    ; e6 P- D0 U' f. T5
    - E1 ?' A) r8 T4 r; J63 E+ u# F+ P7 `: M
    7+ @7 }0 D4 O0 N) Y
    str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
    6 r6 F0 }( O7 v4 ]. t6 a+ F% @s = pd.Series(['This is an apple. That is not an apple.'])
    % y" T: B4 {2 g7 W% d$ A1 V, s' N% k$ I" L& n6 y5 l" r
    s.str.find('apple')
    & a( v! c% l% vOut[62]:
    + q8 T% N5 }: R' X1 k2 w! I* w5 K0    11
    ) C, P0 q  E* D" e' w5 adtype: int64
    $ C: H6 l$ c  X" l, {" U& H  H2 \1 s" i9 P: Z: r
    s.str.rfind('apple')
    ; `, d( y$ D- B: s5 U+ |Out[63]: " Y0 F  }% ?) m  m+ x2 A& b
    0    331 O. e1 f4 m/ a1 r; L
    dtype: int64, h9 M5 y; B5 k+ i
    1+ a+ I& S( H& l; Y9 o% Z: e3 w
    2# f) |4 a! v6 u4 b. a
    3
    - U. _* j/ I+ o; c& i7 y5 a8 z: X49 h3 L1 v( b1 \$ p+ {2 e
    5
    . b+ |- o* V' C* d1 f# R6 y! l  _' S& \( j6
    , [% u; f- A( H6 j) [' |7
    2 r; ?' l% V5 ^& I80 B- e4 P% p0 r0 i5 a6 b
    9: C& o/ T; T" _8 |' j7 Q
    10
    2 L8 O0 H4 R% n8 f$ z, I5 N11
    ; w3 ?- |! I4 A, T- C1 j" ?) _替换" v2 _% k( S+ W5 d5 V5 g; P
    str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。+ E6 k& R/ X5 y
    s = pd.Series(['a_1_b','c_?'])
    - \+ S( A) Z9 q, k' u% ^7 Q1 _# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
    ! P$ z3 m- A5 c* {8 d/ f6 n' C/ ps.str.replace('\d|\?', 'new', regex=True) 4 F3 j( i4 \3 @

    0 N4 h! ]0 u; ?* x* j: B0    a_new_b
    ! f* Q( i, F* [1 @1      c_new
    6 J8 P- o9 R- R! z" Ndtype: object
    1 v) Y! O& Z5 |1% j- X2 B* m9 F
    2" d0 s% B7 ~* u6 y9 m7 ^
    3
    7 J3 @! z& F# J: u0 t4
    % W. n( A+ J# f& l. H4 ~: v2 A59 o/ g) p0 P7 ]# e7 F$ P0 X9 G6 P
    6
    % b/ C# i# \) x5 J6 B: K* {2 n3 _7$ @( @- s3 d" Z5 Y
      当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    2 J; W5 [) i) l* ?; B1 y+ M- q" t4 ~! ]+ X
    s = pd.Series(['上海市黄浦区方浜中路249号',
    3 K8 e( J0 I8 n  G: R' N                '上海市宝山区密山路5号',, |. f; A. B' z. y: d
                    '北京市昌平区北农路2号'])* q7 ~$ M/ j5 A& z) U
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'7 @: z5 M+ `) X% V  y3 P5 u
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}! n/ i5 J' Z9 K$ ?% C* \
    district = {'昌平区': 'CP District',% |' Y. E0 o0 O' w/ w1 K
                '黄浦区': 'HP District',
    * J7 |! _7 a" Q            '宝山区': 'BS District'}6 x% f$ M3 N8 }+ x* S+ G# O
    road = {'方浜中路': 'Mid Fangbin Road',
    . l6 l, `* j! q2 l! v4 ?        '密山路': 'Mishan Road',
    ) K4 E2 S1 ^! U, t9 h3 g: F: t        '北农路': 'Beinong Road'}
    6 I9 I1 q  k# Kdef my_func(m):
    , G: |0 }, C+ J( S* O/ q$ S    str_city = city[m.group(1)]9 @) ?6 Q* z7 f( H% E
        str_district = district[m.group(2)]) W* p( W! l: E1 [8 p
        str_road = road[m.group(3)]
    * s' }  j& F2 Z9 w' g    str_no = 'No. ' + m.group(4)[:-1]  R( l: d, u- T% P9 W# ~( R9 `
        return ' '.join([str_city,  [5 p" |# y/ k2 b/ C# \0 E3 E
                         str_district,
    " |4 p, T7 j* ?. H8 }# q. ?                     str_road,
    0 D0 Y: x( e. V6 {  t" _                     str_no])
    9 k0 F* t& v( p2 Rs.str.replace(pat, my_func, regex=True)
    : o; h0 C1 R8 B# x9 y
    ! v4 ]* C- z6 {5 ]6 p1
    ) Q1 D7 |8 |( {+ s9 x: @2
    . X9 W1 e0 g& i( K2 N3 b" d3- k, F6 B2 v% J! c6 g6 ~4 b9 o
    4
      k# g) h' S, D0 }& F  u5/ }) f8 y& u7 C; \7 a
    65 r# w/ S5 O6 x
    7
    & m; R1 U- h: a7 @! U. W1 x8
    ) d) j, G& Q9 ]  c9& U9 _. S) J3 c3 t: q9 D3 m- j
    10* Y  B" B2 H& x
    11
    5 u8 P! D5 y) b% q8 [: @12
    5 X, r/ z2 c" C* C13
    8 b- i/ G8 ?/ A14, w6 N- c4 D3 A2 @
    15
    * ]2 y4 i: `5 L- x- [166 z! ]4 U2 }' X0 w3 O
    17- M: l& p8 X' M& U) W0 J
    18
    9 k2 V+ x3 V8 I1 p19  ^% B. `3 V8 \! }( s3 ^& W8 o1 r/ G
    204 Z9 p( H2 S( X( [
    21" d7 D- D$ k/ j
    0    Shanghai HP District Mid Fangbin Road No. 249
      b( w% T0 w7 J4 f8 M9 P1           Shanghai BS District Mishan Road No. 5) @& z& L5 y, y2 K) r: l# ]9 S: U( d
    2           Beijing CP District Beinong Road No. 2
    5 z! C/ ?8 x2 k* F, h( t( j, ydtype: object  B( u1 @- Y7 N, G4 ?
    1' H5 {( n* K4 J+ x& M/ d. x
    2
    8 U4 {% {' s, H; x7 f# M& n9 T3( G# D" s) r; h- R  F
    4
    $ q+ |( o$ {3 P& V- [% ~这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:3 t' w4 S0 ~7 o/ i
    ) X8 ]4 U% C5 ?8 f
    # 将各个子组进行命名5 A. j& V' Z, K$ i- z- x, T4 ?
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'& Q, A7 x. D/ X- O7 _! ]. d7 c: \
    def my_func(m):* U/ V; w" ~$ L6 \' Y4 I" M% u" q
        str_city = city[m.group('市名')]
    + a$ [+ U/ y: u. c    str_district = district[m.group('区名')]: @& x! E/ A( V3 ?1 x. H8 r
        str_road = road[m.group('路名')]
    2 g% F# D" [  f; j3 C4 L; Y) J$ P4 [# t    str_no = 'No. ' + m.group('编号')[:-1]
    0 E$ u% w6 S# Y1 ^! Q# w) c  V0 ^: v; m9 Q    return ' '.join([str_city,# K: o$ X( u; y) w  K
                         str_district,7 f6 F2 N7 |7 z8 J1 z9 L% g
                         str_road,
    9 T- W7 w+ S. b                     str_no])
    / S/ G" H$ ]5 [# I& {8 Ks.str.replace(pat, my_func, regex=True)
    2 K1 Q' ]/ y' M# @# u6 S$ z: d* `1$ s5 N1 \' B( k
    2& O% M5 [5 L2 g9 O% ^
    33 ]. ^$ ~9 l) E" `
    45 k8 v$ m2 g: \, I+ A( H
    5- A$ f  W4 _# y& p7 }6 h- A% l
    6
    ! }" h  P8 @, H' |7
    $ z7 i. x" p% H# u; H8; @8 q8 r+ t, h: Q2 g! X. Y
    98 E* o  S/ F/ ]
    10& q0 l; P8 I. [) V8 t; a5 q* U$ D# _
    111 j" Z  B8 ^0 K/ s
    12! f/ N; L& k1 i
    0    Shanghai HP District Mid Fangbin Road No. 249
    , ^$ c- ], E8 A9 w& C+ j1           Shanghai BS District Mishan Road No. 5
    $ T% `; s: Q) G/ m# S* p2           Beijing CP District Beinong Road No. 2
    / @1 y; k( ], Ddtype: object) p0 N7 b1 \- j5 o0 p) j; t8 d
    15 K+ S0 E7 C4 u" V! V
    2
    1 O- Y8 F* N& u0 `2 d* p3
    , R4 y. j9 f! C1 m' }- q/ \# @$ m4
    1 P& d, k9 d6 ~: r2 a5 o# i" Z  这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。) [- W% O: n+ B9 G& E# j) @

    1 C0 X+ W6 p3 F7 ]  H8.3.5 提取1 v( T: f' P. W3 Z$ ^% Q$ _- }8 o
    str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
    5 t- f' Q+ T4 h$ K9 Z$ Fs.str.split('[市区路]')
    + i* a0 J: G  `- U% lOut[43]: 8 l- L; k: B: n. L) r
    0    [上海, 黄浦, 方浜中, 249号]
    8 c" f; _) J+ Y( O# @" r1       [上海, 宝山, 密山, 5号]
    ! C" ?( W6 o% M" R2 N' v# Cdtype: object
    - t2 }3 E: r1 _/ n' s1 x) B; _! g( ~7 ?% q) U
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    7 w4 I1 [8 E/ ^& D* Q- Ts.str.extract(pat)
    7 h6 b' Z8 a' x6 W+ I1 LOut[78]:
    . \: J9 R! z% d; R/ t2 z# S    0    1     2     3# h! x% Z6 d1 M4 {2 e- }9 k8 ^
    0  上海市  黄浦区  方浜中路  249号& w& R+ e- C0 [: V4 ~
    1  上海市  宝山区   密山路    5号
    0 k( i6 N  T( m# _/ q& W. e2  北京市  昌平区   北农路    2号
    8 u% Q$ X- C* ?6 n) i, P# \. s0 W7 z1/ C& R) e! {* j9 O
    28 ~$ }& d6 I/ a* o4 S0 M
    38 A, ^# I# Z0 j, \9 c( I  Q
    4
    & A4 @- p/ e: Y/ P. ^! `+ ]  A- N8 {5
    2 f' |/ s. v2 [% `5 K0 j2 d6 a6
    . s8 k- U# P3 U5 h1 o% |! X7# K$ B. d& @' {4 A$ o) F5 g
    8
    3 v* R9 R! _6 Q2 @% p* q, }9  s; x. z0 g+ n; j
    10
    3 `& r, d8 a( X, O113 |5 Z6 y. T1 _: C  D
    12
    1 t) X  u0 V1 L- m1 Q13
    ! K; |4 K6 A3 c( E' ~通过子组的命名,可以直接对新生成DataFrame的列命名:
    - W: b) \0 f4 B" Q! f
    9 M9 J" B% j$ t9 w  `  W0 X1 Dpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'7 o0 W8 G9 y1 k4 P
    s.str.extract(pat)9 \% {5 \6 T6 S1 a
    Out[79]:
    ! q6 H3 {9 A0 d2 a9 T    市名   区名    路名    编号/ O4 A" [- y3 Q3 q: i( Y# i
    0  上海市  黄浦区  方浜中路  249号. ~* D8 Y- \. K! _' \$ \, O+ Z# O
    1  上海市  宝山区   密山路    5号7 V' M' J* j, S0 d, L$ r. C; P( E
    2  北京市  昌平区   北农路    2号1 y. w+ o5 a% ]7 R7 h- d
    1
    / ?# M0 d* `! S) x' a: ]$ f22 u% x0 `6 N2 f" X/ |2 V: A
    3! T/ ]; t/ Q+ O
    45 N+ t) B& d$ [* U2 K& Y' L" V: W* v
    5
    + U, J; d5 V  C8 G) d2 c1 y6" z; x6 E  J) ]' S) Y4 o- k
    7
    ! E/ C: L  q  D) n1 pstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:, }$ n, c3 P+ ?( Z: ]8 x
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B']). v! ^/ U3 n+ I! q+ X' q* S
    pat = '[A|B](\d+)[T|S](\d+)'0 k1 r+ M& O6 |( j5 m7 K
    s.str.extractall(pat)
    ' d' I: u1 t$ A6 a' O1 nOut[83]:
    7 `! R& M( r3 i6 _  j* W       0   1% L$ B4 D8 l4 i$ j  h5 f' N
         match         * _% ]5 k! U5 Y- J) f4 l# a
    my_A 0      135  154 k' \$ H, J+ w% e, e% Q
         1       26   52 i# `" y  z( A
    my_B 0      674   2, q$ q; s+ I# d* u. I, l- O% w
         1       25   6
    " l! q2 ^( C( E9 v" {; u" N1
    4 G0 U- Y+ b7 O+ a7 a4 P; t9 W2
    6 R7 l8 s7 e' t' F- I' ^3. L% |" ]. f9 `) |
    4
    8 @! o2 |! n" j5
    1 t3 w* H2 z$ J, j$ X, \6
    $ b) ]( T! x! P* `, `7
    ' f# J$ \1 ?5 n" C9 X0 m8% Q7 Z& [' @2 i2 ]5 k1 f/ J' r
    97 w8 w; W! ^7 [) k. l
    10% D- y) K6 l; ^- ?0 w
    pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'6 L4 j. |" u4 L; M' U3 e: B
    s.str.extractall(pat_with_name)+ ]5 S( ?4 X4 C4 B3 ]
    Out[84]:
    " i) t; f* U0 a% i           name1 name21 r7 S$ e/ C, G+ _
         match            - Y- }% A' i: o7 R. [6 I
    my_A 0       135    15
    , J9 L! [; k4 J/ W, ?8 a: w     1        26     5$ s0 X1 L$ q4 X
    my_B 0       674     2
    ) E& ^, l2 s; }  H- s$ _     1        25     67 b, O# H6 z7 b; O7 x
    1
    ( Q$ ^$ @; u0 }2
    # C# V% E0 X% F  y6 W! E3# a" x% K: b* `3 `! `* Z, h2 [' H
    4$ u+ t9 d: a+ H" A! \
    5; J; e) d; h* L2 |* H; N
    6
    ' Z9 e  N/ D  D( D70 L4 N! L) |1 }" ^' L7 A$ J8 b. ~( h7 a
    8# q' p0 c5 ~' m- \; {  s
    9* `9 j0 T  F* T% }7 Y
    str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
    # Q0 R4 j+ l  B; [1 S( qs.str.findall(pat)
    7 ?! h! ?5 D$ Q8 f1
    - F7 q! ^( s0 N+ g7 _4 Mmy_A    [(135, 15), (26, 5)]
    2 J8 V9 a% @+ @my_B     [(674, 2), (25, 6)]
    # ]+ f# K4 a! U$ ?7 sdtype: object
    ! x" g$ N. |9 N4 w  B( Q/ P15 u4 j. Y6 U  v
    2
    1 `2 g# ^) ~1 T; y3
    & n/ F& _& M5 N6 |, Y5 h1 |8.4、常用字符串函数
    , @& b( W- u9 t  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    ; I3 Y8 t  F$ `0 p3 J' I) t" a' n9 f' z* L& N' I; Y
    8.4.1 字母型函数
    9 a; i2 z7 E8 \& ]  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:, |" ]2 e1 b7 C

    6 X4 J& ~/ }9 W2 ws = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    9 E& k. G# N0 ]
    4 f  T+ B/ ]( E& Q! ^s.str.upper()
    ; H6 F4 B5 b7 UOut[87]: 7 e. P  j* ^( l* W5 M  D% U
    0                 LOWER
    8 x% N) \( a8 @# }1              CAPITALS
    % t1 j5 }0 C- l9 z% s2    THIS IS A SENTENCE( N4 F7 a  ]8 r+ v% c. U
    3              SWAPCASE. q* D1 }* [& k1 W# o
    dtype: object
    % |7 l' s0 F& P- |, p3 g5 Q/ N% a* h+ ?- d3 e, I
    s.str.lower(). P; ?+ E8 H) G  [5 O/ V
    Out[88]: : C; h) f+ o/ o
    0                 lower6 Y6 z( o5 v$ C* J
    1              capitals& M3 G1 ], Z; M; ]1 M3 x
    2    this is a sentence/ |8 y+ A: H  j2 A3 f- E
    3              swapcase
    % O7 D# i9 o" \& ddtype: object' F$ A7 ]- H' ]
    2 S* B3 I% P% y! N1 M( _
    s.str.title()  # 首字母大写) ^7 e+ H1 O  S4 c
    Out[89]: : o& W: {% a! i6 C: B: a& a
    0                 Lower
    . y, W0 k* z: p$ Q# \! J1 w0 j1              Capitals
    ! |* f" {# H# Z3 E1 l' Q. V2    This Is A Sentence
    7 ]1 O5 b3 X  J7 g3 j" W, y3              Swapcase# K1 k6 ~3 _+ N& e
    dtype: object$ X. X4 i6 z: k

    & {- O3 ^! w5 d0 }) e4 Xs.str.capitalize()  # 句首大写
    ' ?1 E" B% W. n3 d$ s5 T  f% QOut[90]: 7 a& J) o8 [! E  Y
    0                 Lower
    + W, ]) c  I: L& |2 a  c1              Capitals
    - D) z$ M' U; f. I% S2    This is a sentence
    & K/ @5 h( Z+ [, n: I3              Swapcase) Z+ S. a$ Y% a
    dtype: object
    - r3 m+ o& T5 Y$ T0 l7 u; R- \% Y: I' I! u- ~1 a' F
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
    5 W9 E7 b# D' f. u6 b$ VOut[91]:   @/ W  P- |! J
    0                 LOWER3 m- c  y  h4 T+ @! J; s# `
    1              capitals
    : E9 D6 V* Q, b7 ?" x5 s" {3 z2    THIS IS A SENTENCE# e1 O1 e2 R2 `+ o4 S
    3              sWaPcAsE. d3 ^1 [  z- u
    dtype: object# C8 w* w  [5 z* e

    : L# F" n' x, `5 z. w2 Y' ms.str.casefold()  # 去除字符串中所有大小写区别
    * C: `% e4 K2 U
    ! v% h) {9 J0 r% _0                 lower% O+ [, `, P0 ]0 v4 ~" C# a( I  w
    1              capitals
    % }2 P# H6 `1 }8 L4 C# w2    this is a sentence
    3 S. D0 o$ W5 _. I6 g  o  o+ L3              swapcase+ r- a. L4 U+ Q# f, a

    ; ?3 a0 k1 |' o8 A1
    0 s' R) N- m/ Q# _2 _, `6 Q2
    & J" y6 `& K3 j, U4 Q) L0 b' r3$ p' J/ U/ {* [4 k1 x3 t  B, f
    4
    5 R; W- `# U; G2 r( V, V5
    $ O; T: Z& }" g# R4 X3 t) a5 t8 R/ b6
    0 ^* A' y; J8 z) j, b72 |  [0 _9 t! L9 Y0 @
    8
    7 f$ c4 u, U+ ^9) W, u+ @+ G& h8 K  b
    10
    8 i4 G: c+ r9 I$ k11# B. D" A$ q1 ~# Q+ I% `" L$ w1 f8 ]
    12  A2 i. `. t3 \9 h- J- `% r" n
    13
    9 I8 v. W- M' ]- w14
    : y+ N6 s3 E( R, Y1 f& B( H2 l  h& u15
    & L( ?4 ~5 }" H$ ]  \' q6 D16
    5 |5 x: V5 P8 ^17' P) A% D! F8 [! z7 |3 B
    18
    ( ^, C8 u7 b% q0 ?" T6 |+ e' l/ o/ U19
    1 |) G5 \  m0 _& l& W# c) C20  {" T; d. L7 h3 B+ Z/ \
    21: ~% k4 Z8 f. f0 O$ D1 B! v
    223 f- I, P  n9 x" Z$ S9 }
    23( H  b+ \/ y  e5 ^, n, L
    24
    9 m! e; h4 |- S/ u5 Y& ~25  E, I& R" C  d5 ~8 ]1 T2 |" A, e
    26
    + ^: j( ^3 d% Y3 f6 w- t- [, N27/ t7 W8 @# j1 Q. H" n3 {: p" I. i
    28: R( m6 P6 e" _$ q# `$ Z
    29
    9 y0 D9 l, \# a/ q8 o30, w" B% |/ R# j, x1 Y
    31
    % w. N5 z. n9 e* f32
    , W% G. L0 x$ {* ?2 g$ E33$ n5 h9 o- b: T$ Y8 P& i
    34
      ^1 V& D8 n7 P350 v/ z0 b; `/ S8 ^+ _" ~
    36' o) [. h* V' s$ c4 n6 {" R
    37
    ! f5 B" p1 k% Y- A, }0 q% [( v38& c0 P+ f; t4 j" `
    39
    & F7 Y0 C- ]" S! y6 Y: M7 ?7 j* w3 e40
    ; _3 d( I+ m  ]& Q+ l- a2 h- O41+ S5 F3 k* |; E
    42
    2 I% i9 Q0 M( d# F, n435 ^7 b. k. O; G( k
    447 w/ Q* @( {# x9 ?1 C- I
    45% B1 O/ b4 z1 |/ d3 ]" W6 L9 u/ D
    46+ i/ }# n4 s9 n' {  t( G
    47# E9 t. P5 w) W' a4 N
    48
    ) s4 G# s) F2 c7 i- Y$ d8.4.2 数值型函数# W4 ]* e+ m/ {: K& I+ {
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:6 O8 n3 ^8 ?) \! e, L+ P

    % |" h( G  d  e. M) Z  H0 H7 oerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
    9 K+ x# o' d* Y2 nraise:直接报错,默认选项, S& @( G: r3 d% ~3 B% N
    coerce:设为缺失值
    2 X3 o- E& w7 P4 R% w) J7 mignore:保持原来的字符串。
    8 V- \# [# ^4 i  Z+ Pdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。2 [" i7 m  ^: b% \" X' K% y
    s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    ! x; Z; T  }9 l6 ?
    6 }. B) r& P, H' B0 `+ Upd.to_numeric(s, errors='ignore')8 d* C- Z, {2 {) T' H& ~4 |
    Out[93]:   j7 q, a5 W. N0 h8 p& x1 |* {
    0       1) G& q; j  P2 D8 R% J
    1     2.2
    ; ~) M( s  b' E" E2      2e" c: e2 Y$ u/ r3 h! ~  f
    3      ??& C% a/ R& Y0 d0 ?" r7 d# o8 F
    4    -2.1
    ! q2 Z( X* k' v5       0
    ) s& U# ]4 e5 ldtype: object
    8 s: r& _+ |; i: ~: E- L$ `
    $ U7 n: Y3 _( c' v/ xpd.to_numeric(s, errors='coerce')2 l* N# m0 f5 h5 R
    Out[94]:
    1 B! k0 y1 X0 O/ }0    1.0; T: y6 k; E* z4 k2 D2 p5 l! @; n
    1    2.2# f2 r( m( A0 X* ?" I
    2    NaN
    % p+ d% X3 g1 i# F& e& ~3    NaN
    1 t! N. }$ Y# M# d7 W1 o4   -2.1$ x  ~% O1 o" y& T2 \" i2 n/ z6 `6 K8 G
    5    0.07 a& g$ z1 }4 W% X1 K
    dtype: float64/ {3 A4 K7 n/ u6 b

    5 r+ y) _0 H5 u4 r1 O2 i. Q1( ^0 N" ]1 P1 J
    2
    ' N; C1 c/ ^8 R7 H) h3
    : g* p2 y% `) D# ^1 |4
    $ Z& k/ D$ c6 `) o! K' W. W  o3 d5( Q' b8 q& W9 D; Z
    6
    ! ^8 p$ Z4 w% L3 ]6 h1 a& k& i7. `$ w. q- g  H( U( e3 t- W
    8. m, c4 k+ P' H4 ?7 u  L. R4 A
    9+ `' ~" d( r7 k7 B$ q& q4 H8 }/ D
    10
    & T$ ^  s9 Z8 d( U2 f% u$ c0 |11
    * S1 r6 b1 b0 |0 |& D125 U1 ^* x  y+ I4 C
    13
    5 p6 {. q* F# o, V. X* x* ^: A14
    , v- I: I4 Y) I15
    & ?. s  k! `: q; o3 V3 A+ J4 z6 [16
    ! h7 m; j( V0 j. x0 Q17
    0 L8 |: ^: d+ Q8 D; {18, R  K: S8 @  z
    19& t9 d( n& R* C
    20' z  D4 G  m" s4 A
    21" C* J* ~, c8 I6 w: Y$ ?
      在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
    " h7 k9 G" Z+ `) T+ S! t# Z# s  K/ z) W) @
    s[pd.to_numeric(s, errors='coerce').isna()]
    8 J; Q$ E. O4 N5 r9 rOut[95]:
    1 p( l* h4 X1 d" T7 }' b2    2e
    4 v$ z" u+ l; e6 n; j3    ??
    # ^- ~" U9 v0 Zdtype: object
    . r4 q+ S6 ?: z- S4 H# y1
    9 H" A6 V9 L& p2/ f6 r- ]$ d. Z- s
    3
    # A4 y8 L1 o) G6 }2 ]- h46 t" j+ L+ J8 F+ g# T
    5; Y1 X" Q( b& V5 d( [/ |3 {3 P
    8.4.3 统计型函数
    2 d) c2 m6 K+ |) d, N! ]  count和len的作用分别是返回出现正则模式的次数和字符串的长度:8 i9 E0 P. [/ H2 D/ G
    ! [2 s" P& f4 C8 `: N# G: y
    s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
    4 g1 C# q# v2 S7 x
    $ `+ Z8 Z2 K+ m: v5 ^9 g- P4 ss.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
    + G- r4 ]4 `. UOut[97]:
    & V; |; C1 i7 X- k7 l0    2
    , x) s$ R5 D% _, B1    2
    - ^$ @0 ^( I8 S" `, T  Udtype: int64
    : Y) B2 ]+ N2 E7 U6 [% K$ n6 H; }& H! u+ A4 c7 \% L
    s.str.len()9 _% g: }/ O3 t" \5 ?
    Out[98]: - E# r* F. H* i& N; A8 h9 K% ?
    0    14
      C: N8 f' d2 }8 ~! B+ `7 n1    19" M: \5 l5 ]5 o5 h
    dtype: int64# z5 y6 ^& b3 X! |0 |3 d3 u8 E1 D
    1. r  n9 s. p4 W) h! V# |; T8 a
    24 t* P0 r: |, R. D  y) c9 j, O
    3
    4 \8 T. c. W/ m& C1 P' y4
    ' F" N" N( ]2 G' O% L0 m" ]2 g5 _5( h/ O4 x6 l1 ]4 J( _9 y
    6! l& L* }3 m* F2 m! I1 [) r% m
    7+ p$ _1 C- I1 W* z/ [
    8/ t+ _% @% [! B
    9
    7 f1 q, F2 `5 a, ~7 f0 x: k10* L' ?! W& i3 Y
    11% G4 w& y2 B; C; I$ x
    12
    8 R, e# g7 j3 `) D* E13
    # g  \8 Z' [, y( H* o% U- j' r8.4.4 格式型函数
    0 t0 s5 y( V9 E  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。# d3 V# O. c7 U
    " q! \8 x/ {, ]" ~
    my_index = pd.Index([' col1', 'col2 ', ' col3 '])
    / n% U7 |/ V* l2 Q6 ~2 i7 m' c) }5 `" Y5 L8 p. V3 `
    my_index.str.strip().str.len()3 F2 {1 t! o  u6 k" g/ I7 U2 e6 K  S  [# c
    Out[100]: Int64Index([4, 4, 4], dtype='int64')
    ; \: n! y, F7 D0 V" _
    5 i$ P) i" N9 t$ y8 Wmy_index.str.rstrip().str.len()
    6 [& Q; s% [4 l3 f2 m+ YOut[101]: Int64Index([5, 4, 5], dtype='int64'). M6 O5 ?$ z8 A5 i

    6 e3 w  \7 J3 s2 a" Z% Kmy_index.str.lstrip().str.len()
    ) G3 E+ I& v. I4 M: O+ ~& JOut[102]: Int64Index([4, 5, 5], dtype='int64')
    9 |0 Q  x3 F" V13 I6 n  V8 t' u5 n
    2
    1 U( E: t0 Q* q  [# Y8 p3
    3 X4 `% w& ?% g, ^- [0 E8 p8 p" ]" ~4
    ' u! n* R. N4 a3 K( a5, @! k: C, _) G5 s0 |
    6
    5 S2 u* \9 r1 H% y/ s( r# W7
    0 Z3 W1 `$ w7 Q4 q) `8
    " e9 K* T, d- `7 x9
    , N6 x7 \; `$ J1 \10
    4 W- B; n! R: V5 Z, u" T( a6 g1 U. E+ B  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
    6 |' c5 y6 V) x" Z- W) h
    ) f& U+ x' ]3 f+ A0 z; c( xs = pd.Series(['a','b','c'])
    4 }' [; C! D8 B% C& w* S+ r: m6 R+ G& x4 a- `- o: A
    s.str.pad(5,'left','*')
    ' [3 Y, ^3 Z3 _- U2 u& LOut[104]: ) @" F1 {3 ]1 q1 K& P
    0    ****a, j: v7 T5 N) X
    1    ****b' C8 e) {" P9 Y, G
    2    ****c- m1 z0 I  D1 m" k9 U! O: _
    dtype: object
    " i+ h7 f0 b2 J6 ?: k
    % ^; ~. O: P- l6 xs.str.pad(5,'right','*')7 B% w% D  C7 X* w! ]2 H, J
    Out[105]:
    / w( q- L* O* W  Y4 H0    a****
    1 T, ]& ~8 L8 ^  c: d$ S. B1    b****/ d( o! A; q/ t$ B- i" Z; J
    2    c****: p' n5 j7 D8 T* q  i$ e. W$ Q
    dtype: object6 e2 t5 W5 I, V7 V! E% V0 ?) d, n
    ' a" A) v3 R0 X9 a3 s" J
    s.str.pad(5,'both','*')! H1 U3 _; \) M6 \+ Z/ K
    Out[106]: ! {( v- @5 w/ U0 |. n3 r" ?8 |4 P2 n
    0    **a**
    & E1 D- |4 e! l% s7 R1    **b**7 b" {4 U0 F! o% Y5 {
    2    **c**$ L3 g' A  l7 ~3 D" j- P1 A, D3 T
    dtype: object
    # Z, Z' T" y% H8 S/ m8 B  g3 J& O. }( F% a- \. A
    1
    5 c2 t7 u' Z( E4 w' Q5 S1 s2
      e$ ?5 s( U0 L3* U0 o5 c; }, b0 t$ B  I* X
    43 i5 x! R2 x$ O6 x" B
    53 B, E' b; Z8 p1 C  j, j
    6
    7 _: C. D9 `9 V7 T; \7 U7 t5 f5 |7
    + G; d1 h" Z( s3 F4 n8! @* X5 D3 g  P3 t
    9
    + T9 R- Y" N* M7 x" x$ S8 |10
    ( I5 D0 J' N1 F/ |7 h. S$ Z+ c, u11
    $ f3 F! J. }/ k2 T7 `12# Y+ P' k8 L0 X6 Z6 L$ @" @
    131 M+ G+ F4 N% @% B# A( @. H$ Y
    14) D7 u2 f! a# \# c- {
    15
    & `, V% W8 S/ K! f9 Z16
    * q0 Z* b7 J- c+ [; O: U( t$ K$ Z0 G17
    * m- M  ~* w, J" p* b( D. e2 x18
    ( H! S5 u1 {$ x6 [19( p5 z) A7 G) k. O$ F& Y* }. S
    20
    ( ^. k5 w/ Q$ z0 g21
    . ]. m- u, L4 U- G+ I22
    : _1 S, ~( n  [5 \; n# Q* V  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:; m6 w" u2 v5 A( i5 p

    ) P4 w6 P, ^9 r" \# z6 Js.str.rjust(5, '*'); {/ f( I/ ^" r0 f8 A: z
    Out[107]:
    ! x" b6 |, M- m1 o) q( b! ?8 L0    ****a
    8 o, B+ @9 @' b8 x1 C: F' g! G* m1    ****b3 P$ R$ x8 r4 [% Y; w8 ]0 h7 z. d
    2    ****c- J3 x5 C6 t" v# J3 k9 x8 L+ H
    dtype: object
    - b% ?1 R; J! v; r/ c% S) i! ~" [: z( Y; @3 V" g
    s.str.ljust(5, '*')
    9 a) B3 c% Z: q) V- p! ~$ WOut[108]: " ]* B% M" {& F% d8 d7 \$ [3 ~
    0    a****# i( S7 B* B& O2 q
    1    b****/ a6 \: f1 a  ~* S( Q
    2    c****
    , R' Q  F) j$ Z& O  i) y5 ]1 z# Udtype: object
    / v& ]7 i# w/ _, c  U) i( T* |4 A: k5 m& Q: Y& Q
    s.str.center(5, '*'), ~- q# O. A0 f! j
    Out[109]: * f$ X+ N/ \/ H+ F& g2 u1 z
    0    **a**
    8 i7 I- i6 j& e; }1    **b**0 M8 q3 F& m, O$ e
    2    **c**/ [+ S$ _7 ~2 ^1 s6 l, S. j5 L
    dtype: object
    9 @9 ]4 Y' ]* U5 S4 R9 q. k$ H* x) f4 V6 l( S8 `) a# c
    1* X" y8 W+ B* h  i( x# ^2 S. w* w
    2
    4 F+ Y+ X0 z  o4 x5 E3! Q7 X! @. B6 D
    46 @3 E8 Y6 j. |4 K2 y$ {( R' B" n
    5% ]+ F- d8 N8 z# x" e" K
    62 |) R3 V8 w7 C; {
    7
    3 G0 \- I& A- c9 W8/ H# r3 F2 D) w' [! K  U
    9# ~1 M$ D3 k) N
    10
    & K! E7 P8 ], T7 D$ p11
    9 Q% k. O' E& _( `6 Z% p& G12, D3 B  [4 H9 a
    13; B+ x5 z2 T3 A+ e2 U
    14# D6 _: a8 W* r( {8 K3 U1 l
    15
    ) O- D: \9 G" c- E: G5 {16
    & K# q6 r9 D! `4 g) v17" c2 D% U3 B$ E8 \
    18
    1 }% V; G; C$ c" {8 t+ x19, I- ]4 K6 C( ~( _& u9 P
    20
    0 |/ ~* ^5 S$ l2 K  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
    ) h$ k& C7 j4 [
    $ F7 j1 I' S' k+ Q: i* Hs = pd.Series([7, 155, 303000]).astype('string')
    ! d" j+ u) o2 o% T- u. T' o; {0 s; n1 ?6 L% S
    s.str.pad(6,'left','0')
    5 C- V1 L  S, N* AOut[111]:
    2 {( S7 S8 R, N0    000007
    3 I$ q! i$ a- N6 h( x1    000155
    / ^" L5 b" b) b! ^; |; l# a4 X* F3 i2    303000
    6 N% p; B0 i$ s* G$ Cdtype: string
    6 w: s3 y2 R- ^8 l9 p* N- d% s2 g
    s.str.rjust(6,'0')7 S1 z6 z. Y. G; i
    Out[112]: # l1 R, q8 u4 ]; ^& x
    0    000007
    ) ~8 p8 H4 m; ^2 H# s1    000155
    & U  v8 B2 q5 n- J2    3030005 c1 m6 y' i+ [! M# e: [$ y4 [
    dtype: string0 r- _+ F2 ?+ k4 d: x9 W2 r

    7 D3 h3 n- Y. j1 w% B& W$ zs.str.zfill(6)
    3 ]: V9 [; ?3 F7 g2 R8 J3 ^Out[113]: 9 h2 `+ R. C* g$ L
    0    000007
    5 P- M: p; n; I1 I3 E& c0 t7 G1    000155
    7 \; t8 @3 c0 b$ v7 m2    303000
    8 [/ _& K0 ^3 J  Wdtype: string
    ; m9 a' G1 ?% W7 ?1 W) P+ V0 ~/ g5 T2 a2 g- t0 b. ~0 i
    19 E9 Z9 Z! S, k+ u+ Q5 O5 J+ Y$ n& R
    22 S! G& J! w# ?$ `
    3; m/ O- J5 u3 A. U  S
    4
    - C  n9 ?! x# t* d' ^% J5! o* O5 q8 E: l0 m1 G9 Y' V9 q# U
    6
    7 h% ^9 n3 B7 q$ T0 e& t1 n" c5 s0 W. Q79 r: U7 e0 L- ]
    8
    6 d; d) a' S# ^) @2 V9! w0 h3 Z; B$ O
    106 f6 [) o  o2 j
    11* x2 x% E7 R) x; l
    12' l2 d' a# D+ V8 t
    133 A/ L6 }- g) g& Y
    145 Q8 c8 ~! q4 n" {5 t+ \. I! E
    15; f9 q( j3 |) f7 a
    16
    ) b' @* N. U% u+ `. x* S17+ ]6 B" B, t7 P$ B, K. j: h; O
    18
    - i( n) w- Z  G$ s1 C19* O1 |+ J* K+ m$ }7 @5 |
    20
      Y- q- Z0 Z( h! h' `21
    2 v8 c/ q" ~" Q- ~22
    2 O8 o4 H# z1 H% L/ B1 e- J  W) W" r6 T8.5 练习
    ! I8 A7 ^) u$ {8 x( EEx1:房屋信息数据集4 B; f2 E  ]. j* k7 o' K  _  E% K
    现有一份房屋信息数据集如下:# e4 e/ s* ~! m

    ' v6 t7 d" [( r& W; z% a7 Sdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])4 c3 z$ u; j% U  ]! ]+ U6 |. Z
    df.head(3)
      H/ V& l/ s  i' I+ P; dOut[115]:
    * Z( J- |( y7 p8 f% R      floor    year    area price
    * q- [* i8 g4 A* X1 t0   高层(共6层)  1986年建  58.23㎡  155万  S' B0 \! H( U! W% K
    1  中层(共20层)  2020年建     88㎡  155万% f7 I. q1 [$ [. j( }4 t) d
    2  低层(共28层)  2010年建  89.33㎡  365万1 a/ C, I- B+ X0 Y- g/ O% b
    1
    ( O6 `$ }. Y0 e8 X  C: A0 m26 ]0 b! `9 S0 T6 x
    3
    , p8 K1 x+ ~! S& {2 }4- j9 g. L4 e( P! Q9 ~: O/ p" }4 J
    5: K. G9 a$ o) M* n" V% e
    6
    0 U- r, T5 Y& x) c. ?* Z' N4 I5 \7$ _' G3 b  g! A$ K7 C: P7 t5 Q% [
    将year列改为整数年份存储。
    " o' g7 k' a/ K2 G- D# h) I1 k2 s将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    * {, n1 A/ P% _" n, [: X. I5 O计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数0 f9 H4 E- u3 v/ @
    将year列改为整数年份存储。: A) L  O4 u' R' q( p) S6 G8 o4 P* k1 `
    """3 |8 D( Q! C+ \5 M
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
    6 M& Y3 o' q1 {' }4 C* \注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,+ p) y$ M3 P5 n3 Z
    转成int后,序列还有缺失值所以,还是变成了object。: e3 e4 @6 [. U+ S
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。( Z* @% {+ g0 j
    """
    9 _% V9 X) J: W; E2 x! Ydf = df.convert_dtypes()
    ( O1 J4 x. r: ~- u7 `( r# x5 x" adf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')) p+ X8 c8 p  p9 A5 z, @4 i
    df.loc[df.year.notna()]['year'].head()
    ' L! _2 n" s' c7 ?/ O: P* L* g# Q" q" s$ Y
    0        1986- e$ F" J$ ]+ [  Y$ W/ h
    1        20208 I7 C7 Q0 H* b  O
    2        20108 I$ W. h3 c4 d2 N
    3        2014
    ! g0 n# n% U. z' v# c) V" z/ J5 ]4        20154 H6 ~" D* v1 O' h6 x
    Name: year, Length: 12850, dtype: Int64( [6 P# w/ y  {8 D

    , u4 _0 K5 G3 j3 ]. `19 ^6 H* y! K: R. P1 a7 o4 V
    2
    9 G4 i. w) q- ~8 f# @5 o3
    6 ~2 X* q; \$ Z3 l+ d, i( J& c5 o8 j6 |4
    ! C  K5 n) Q+ X! M4 i+ `5* [* B; R: C" A2 P
    6
    5 @' m( G8 z1 H! `73 t- J0 s$ A" D8 {5 y7 ]/ l7 _
    8) z! X* k) S3 `6 F2 D3 X" U0 x. T
    9
    3 f7 F/ i- I1 `. D) Q10. @7 z  z# [0 _7 _
    119 r' H1 U7 T1 |9 r  s
    12
    1 u' E# D4 {8 C! |- G8 r/ x13
    0 B$ V% u/ h) y; H$ J4 m143 E; r4 X4 M0 L1 {0 _
    15; {! L* `4 d# [  I' @# [7 W, Z. _
    168 l: {6 W+ N/ C1 x! _
    参考答案:& d2 F+ y, ~- Z" Q1 i# S" j8 r
    * c0 |4 ]3 Q5 x
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么& H+ A1 i# i4 }4 j+ v9 k2 t
    , b/ ]! t6 O4 Z3 X* \
    df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    - b1 T# Y& `/ s8 h3 V5 J7 N. Adf.loc[df.year.notna()]['year']
    / y- f8 |) b) ^% l3 s1
    ' B1 A& W) n, O0 A& F2
    : k  c4 D5 B3 R6 l: G将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    2 o( `" K( C/ l7 Lpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
    0 t6 F" @: Z+ E1 j, K% r" V* S# ~: sdf2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
    7 l9 g& e& T3 q9 }0 G7 x* ddf=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型2 U' a& y/ i. R- }
    df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
    $ }3 ~- O+ W3 \7 M0 T  C3 _9 ndf=df[['Level','Highest','year','area','price']]
    9 P, u; R6 [8 W2 B6 Q& B# t% Qdf.head()1 p5 ]- e! ~2 y# e. w( k! G8 H1 I

    / W3 w- l( a/ J9 T   Level  Highest        year        area        price/ P$ t/ W" j( C, Q: J/ _& q
    0        高层                6                1986        58.23㎡        155万9 m4 Z4 N; E7 K6 X! }
    1        中层                20                2020        88㎡        155万+ [- P8 r$ a) c: d% Q
    2        低层                28                2010        89.33㎡        365万1 u/ d; h' o6 D. K  M  u9 F5 k
    3        低层                20                2014        82㎡        308万
    0 H4 h2 x: F4 V" w4        高层                1                2015        98㎡        117万
    + M5 W8 b0 e$ m) z9 q  f1
    & f: }/ J# E9 X* p, A+ O4 K2% _' Q8 q! R9 \2 ?
    3
    , d1 I; W  [' T& ~* W* \' H4
    ; `8 U, Q, ~1 H3 z# b' g# `+ q5
    / @5 Y* j5 [( |/ T6
    ! v+ |3 Z6 D7 O" Z74 v, R) x" z# l0 M  E
    85 ~2 U; S0 u  J! |* t
    9
    + f6 |/ k# L; U" D  P$ ]( G/ l* O10$ Q! _7 d/ B1 o: d) s6 H
    11
    ( k6 y7 i# n9 ^7 n" B# i, U+ F2 l4 W122 c, I4 P$ |9 o: w: I* [% @
    13, v5 x# R! N* E, l2 ?% i( @
    # 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
      V8 ~0 v  v' X/ ]* K- tpat = '(\w层)(共(\d+)层)'3 a' Q& l4 D/ E& l, }3 e* U& \* Y
    new_cols = df.floor.str.extract(pat).rename(
    6 l/ Y4 I3 H2 i% @- z                    columns={0:'Level', 1:'Highest'})
    $ f8 k# Y3 P/ c1 Q' o3 ?2 Z! l8 ~& a. ^( O/ [" D* W1 E- Z& u) P
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)
    7 D9 f' O8 n/ v2 G2 r; xdf.head(3)
    . u( p/ K4 f- X9 v. C0 D3 k: F1 ]8 a8 O! a( Z; a; ^5 K$ x2 }8 @
    Out[163]: 9 [% W5 u; e( N7 `
       year    area price    Level Highest
    , A7 @/ D0 J/ \, Z% f- i3 H9 f0  1986  58.23㎡  155万    高层       6
    # P( L* F" M& t' k! A- Q  ]7 E1  2020     88㎡  155万    中层      20
    2 U6 `, f" d$ V5 O# S1 h, q2  2010  89.33㎡  365万    低层      28- i/ w4 M- z# t
    1
    / O2 M  N5 j( D( [25 x' ]1 F+ l' w: a3 R
    3
    4 o: q  O1 |. [% J' Y* X) l/ z4
    ; K6 [' m' s: D! ^" _" ^  N3 L5
    ; _+ _5 ?  @% M/ K0 S% J6
    & M) V  r2 {% ~7
    2 E  g; q, y. @. G% ~7 ^9 [" E8
    6 `- I6 {" U: l2 M9
    " Z" R" N; g8 |! ~8 v10( V: w( y5 G  }8 }' x) z8 @
    11$ Q- }, `/ q3 f( u& W( B
    12  H5 n: z2 @2 v5 t" X
    13  A- C9 f% w& S- [+ _6 b: I* o' k2 ]
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    3 i1 @6 q5 V, Q  n"""* I: b0 F5 Z) W' _
    str.findall返回的结果都是列表,只能用apply取值去掉列表形式
    . k% `  Y( p+ Y8 ~2 I: z  z参考答案用pd.to_numeric(df.area.str[:-1])更简洁& m5 X; k: d( ~" W( i: @
    由于area和price都没有缺失值,所以可以直接转类型  t4 C1 f% ?6 e# _7 G
    """8 |' h% j3 u, M) m1 K
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
    - [! I5 z7 H: s4 v2 f, l6 }1 v$ Gdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
    5 p7 h; V5 E# X* sdf.eval('avg_price=10000*new_price/new_area',inplace=True)0 z" c2 p, j: A  g* w4 t+ J" D
    # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))& c/ H9 B: W' _7 r5 w) Q% b3 k
    # 最后数字+元/平米写法更简单# w5 y3 u, @" `
    df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'% b9 ]; J: B3 ?3 {; ]+ H7 X+ d
    del df['new_area'],df['new_price']+ g- w: u' q6 W( u9 R# _# a
    df.head()2 Z$ |7 _2 x% {# U* S

    + R/ C5 I2 ]) [/ o! C6 k   Level        Highest        year        area        price        avg_price2 K: S7 q" l  E8 @' f( m4 Z1 ~
    0        高层                        6        1986        58.23㎡        155万        26618元/平米! h' z! {$ h% q5 y$ e
    1        中层                        20        2020        88㎡        155万        17613元/平米7 ]# K5 `# o# m* }% B& `
    2        低层                        28        2010        89.33㎡        365万        40859元/平米
    : X' m: m. R. K& r7 [$ q3        低层                        20        2014        82㎡        308万        37560元/平米7 z6 N' Y; ]6 a: p. c
    4        高层                        1        2015        98㎡        117万        11938元/平米0 a- t/ ?. L6 Y4 v
    - W% P) S7 Y" c( p, k; ^0 _# b
    17 N. v. K  a+ d! g8 W
    2; ^% _4 {7 L- j2 H; E# K3 B
    3
    - {7 {& f( H9 ^; d$ K+ N; V# A" ?. g4
    ! M! {- j. @- y5 P$ J5
    & K# i& O, Z/ a9 J9 V8 G: ~6+ _; @' b& h) Y; u
    7
    " O: c6 v" m) M% b" j8 M4 \' _8! ]' @7 ]" P; F- G3 t; O4 O
    9
    4 z+ {, {. r5 ^8 m& \10
    / ?3 U# s6 S4 D: Q11  l) [7 |( L4 ^5 e1 X* P) ?
    12
    0 u+ Y, D4 S: O% d) i13
    , f) K) ~: z  R7 d* C146 R& n3 d0 V& g5 @+ [
    15
    0 L: ~4 _: o# {0 M) ], D# [7 R16
    # ]5 c5 J% k; @17
    - r* q7 O$ L( Z4 o187 _9 H% c. _' k/ q: I  p" Q- G$ S  [
    199 G: ]9 o2 G4 P# X  A6 k1 \
    20
    2 b5 Y- o# E- _9 K, q! d& H' w3 H# 参考答案( j% \5 g! [0 v+ j
    s_area = pd.to_numeric(df.area.str[:-1])
    , s0 k. v, s& M2 z; o2 b9 ys_price = pd.to_numeric(df.price.str[:-1])  N* |8 _5 x/ y8 e& i% |- h
    df['avg_price'] = ((s_price/s_area)*10000).astype(- ^- N/ l- w7 M; B) ^
                        'int').astype('string') + '元/平米'* e6 E5 b1 h3 B! ^3 @+ c

    - m" _0 G8 b0 o9 b5 z0 O% gdf.head(3)! N8 I' a, ?, s* L
    Out[167]: ( @4 |- O+ [2 E8 j7 S: |; c
       year    area   price   Level Highest  avg_price9 G5 u. A' c' H% E( i
    0  1986  58.23㎡  155万    高层     6          26618元/平米2 l1 e! N, ?% k( _
    1  2020     88㎡  155万    中层     20          17613元/平米
    . B* \3 E3 w7 o  `' K! }  {- w0 H, R$ |2  2010  89.33㎡  365万    低层     28          40859元/平米: ^  Y! D& z9 f, L2 p
    1
    - f3 K3 b" V; o$ M7 f$ w3 ^26 \5 l* p8 @7 h) h% ?+ n
    33 S  r4 }9 F* @0 p) \: Y! y" {- o' \- p
    4
    + p7 e, V2 H, f7 G/ r% V4 Y5( K; [- V( I; j) {: h
    6! j+ X4 D: P! y- H: d
    7
    9 l  b" Q3 C4 F+ ^82 r- q! J. w1 l! N( x+ Q  R
    9
    1 N5 C( K: g6 U2 K  l/ d10
    0 q# f$ _" D' L) F; {. Q. h( C8 J11& Y9 z* f/ `# a2 y3 C7 A
    12
    6 R) n! e0 Q# I. |" H+ ~  y7 W; v4 TEx2:《权力的游戏》剧本数据集
    6 n' k5 }5 ~- U; C/ ?现有一份权力的游戏剧本数据集如下:
    * {* [% @5 K8 A; O
    8 s/ a7 P# ~7 f+ l! y3 t5 e7 Gdf = pd.read_csv('../data/script.csv')" e" ]( D* c& W
    df.head(3)- a  q) J2 _! R- T$ f; s
    8 h) K5 Y; A5 J( w+ N8 {
    Out[115]: " l/ ^5 Y9 N/ D4 B3 b# _- B* o
    Out[117]: : s' O- |; P0 f
      Release Date    Season   Episode      Episode Title          Name                                           Sentence
    8 ?6 `! K( |1 i: C; o% I) L0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...: c4 \) ^, S# d6 F
    1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...
    ' y- G% T/ L- }9 u2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
    - ^$ u9 D# @+ E5 L2 ]: I. K1
    ' R, N( O3 c- @2& {# T1 L4 j; Y' V0 E
    3
    + X8 c4 v6 d/ ^4
    ( _! b5 c  A, T& z' h5
    3 Q* T. S/ H6 K+ F2 ]% @- C+ K6
    , u  L2 v/ S  Z1 X8 p- i7
    # E  A; P# n- i" o  O& G+ Y8
    & q* J& v2 _! ?- B9; G3 q  t; F0 @3 e3 T& ~* e2 L
    计算每一个Episode的台词条数。- K6 E! |; X, p. E5 B3 W7 B
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    0 w3 A* ?6 Q' ]& g若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。% S9 R. t, A9 Z0 X) j# ?4 v
    计算每一个Episode的台词条数。9 U* C5 q+ {( ?  c6 \4 q) u* g
    df.columns =df.columns.str.strip() #  列名中有空格
    5 I7 q0 U1 V8 m$ _# f+ W# odf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()$ I' M# z, J, T8 I; I4 f2 r
    . X' M' Y+ B6 Y  b* e
    season    Episode  " x2 _) K# U4 D* T0 x" t- w
    Season 7  Episode 5    5058 Q5 f4 p& l) w. x' a
    Season 3  Episode 2    480
    ' W/ R) z2 p# U  Q4 i* N: qSeason 4  Episode 1    475% _: r) t  I4 N' p2 d2 U! G
    Season 3  Episode 5    440
    . t5 k9 t6 `: a8 Z$ ^5 H, ~Season 2  Episode 2    432
    " ]+ \/ l7 ?. |% Q# P  _! W1& o& s  v2 }; {
    2
    & [  y$ g# G, v! w3) `8 C' O# h  m! H
    4
    ' T& {3 o: G  Z& D9 b0 @9 ?5
    3 d/ P4 v0 o$ G0 t: a. R6- C/ Y1 n" x" P* E; i. a
    7
    # n1 M$ H0 g. z( B1 M; }" {/ ?8
    ; D9 [4 H4 n4 B. B2 m* U; z* u9+ s8 l* L% p0 s- R) a, A: t6 W
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。8 W. u( J# \' n+ @: ?( d" V+ w5 m
    # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
    # @4 g6 n* M* g7 [* \, F; f4 `df['len_words']=df['Sentence'].str.count(r' ')+1
    ( e' q. V# J* n) h8 \df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()2 n9 U& l5 d, i2 _* h
    + \) F6 R4 M/ y4 N3 s6 A5 l
    Name
    8 o  {& a! m, w- H* Omale singer          109.000000- l  w% f& w( [4 k" d- z& z
    slave owner           77.0000001 D2 T! F# @7 y- D
    manderly              62.000000
    ) u: O5 m- A5 C; f9 w9 V  H5 A( Qlollys stokeworth     62.000000" J* h; \* Y/ S; S2 u
    dothraki matron       56.666667
    7 ]3 r1 H8 r) Z* xName: len_words, dtype: float64
    ' g/ t! L7 p2 r% P* e! e% \, E1 P: }1( u; Z/ l) D+ K& _8 j- d- K! h
    2
    6 p& s/ f" r, O" ]3( r  K& w4 P' ]! ?& q- y% J# b/ x
    4
    ( [( q: [( [3 k* Y' P5
    ) B$ ^7 L2 Q/ D+ R$ @5 k8 z6# n) M6 N1 K* k: @0 m
    7
    - N9 e+ e  t: Z# E( L8
    # N# E+ }$ f. h+ d7 u: q6 c0 e93 u: B% c4 A3 h
    10, `; l# j) ?$ y+ y0 i: [; r. _9 {
    11
    + X1 C! C1 K+ E% k1 R& B若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
    + y) y8 \( ~1 A  U2 |$ Tdf['Sentence'].str.count(r'\?') #  计算每人提问数
    2 m3 V0 ?) z3 z& pls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0% I* B* }( h1 ^+ ~+ I+ e
    del ls[23911] # 末行删去
    " T! o9 H' |& {* ?2 [$ I% qdf['len_questions']=ls' e, }. T' Q3 S8 v  q5 T( _
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()% o. k7 @* X) h/ t* r4 U* C

    . O1 _* J! }5 G$ j0 K7 gName$ r. W& G: u2 L4 Z  D7 ^0 D
    tyrion lannister    527( b. Q9 V; J9 |; `3 ], A2 o) @8 W
    jon snow            374
    2 [; [8 D9 R& ]6 X1 \) W: Jjaime lannister     283
    2 t. _  s( P; M: p3 q% Tarya stark          265
    ) y' P& h4 m9 J8 Y+ @cersei lannister    2465 j; d& [& ^) C5 ~  C: x: ~0 u9 K' N
    Name: len_questions, dtype: int64/ ]9 ^6 ]. |9 H! Q' W8 k
    # t& O+ m* Q! X4 t% x
    # 参考答案
    ; ^7 V2 W( @! b5 u+ G. Ts = pd.Series(df.Sentence.values, index=df.Name.shift(-1))/ Q/ b5 O9 Y# U- B3 R9 N0 r. T
    s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
    % H* V7 P3 U, o) u6 n8 b3 p* @$ v2 K' Z* v$ T) w+ {
    1
    8 }( R  q9 I6 ?# k+ t2
    4 e$ w- D2 ?, w+ n# I4 |% I3
    6 N% o- q$ b& K' Z4 B4 v4 t( V4/ Y& n+ U8 K/ A5 c# x/ I0 P
    5
    - ~5 O3 u& U. \/ V2 q  s68 i3 U6 Y/ S1 h& `/ D; A& R6 E
    7
    , g4 i. u) d! m0 E4 `2 h% h8
    9 C( t! z7 R3 V, O- Y9
    9 g) G  U" v3 ~10% Q4 F' _0 y3 y- W2 e
    11# n, u0 V, A4 y% c% a& n/ N! q
    126 k# g, I8 Y% v9 L! p+ M9 @. N
    13
    ( c. Y" }; g2 y0 t2 ^4 x* X2 G14  f1 L/ T& }  w3 n, E
    15
      a/ ^& n7 y& A* t) S) [16
    . u( r4 d5 |6 o# {' v9 Z4 }" l179 b/ |. w/ k. F  o% c9 Y
    第九章 分类数据$ |; r: c/ ?6 ~6 X% K# b
    import numpy as np7 L1 C% R1 ?( I0 a
    import pandas as pd
    4 l, \  }# F3 X# ^" Q0 U, `1
      T0 N8 v' O( R8 u( N8 _4 {0 T2/ L# b! Q3 ~  k8 ^9 Q
    9.1 cat对象. v3 x+ M" U' s0 x; h4 T
    9.1.1 cat对象的属性4 I% Y: U9 ~6 |; z, J$ B. l
      在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。, a, w6 C0 g% F  \/ s6 q

      ~$ R- N* G1 K  Tdf = pd.read_csv('data/learn_pandas.csv',; K5 s6 `* l, {- ~
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight']), _' v, m% v) R$ [& P+ g+ `
    s = df.Grade.astype('category')1 f" h+ Z2 H9 j5 l4 C- r3 [
    ! c+ j" ^, X( f& h& r; Z
    s.head()
    5 b; l6 d  W0 O( b- hOut[5]:
    1 J; z  a6 q% _$ Z, k( X0     Freshman" V5 t% Y* S, [, d- a+ a
    1     Freshman# b; e" j0 H7 x2 M$ c& \. [5 Q" s
    2       Senior+ T* x% f7 J: l" j# o# n- D
    3    Sophomore& m6 g7 M" n% b* b* K5 @
    4    Sophomore) {: s; O0 D, f% x8 L# ^  }
    Name: Grade, dtype: category
      ]7 [, ]- b4 x$ V4 [( c" mCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
    5 L0 ~# S* i  K5 N  r1, _/ M& L; c+ V/ W
    2
    $ ?. P) }2 ~4 T. D) u32 z3 c3 ~3 @3 V
    44 w' k) j. [! I) E/ J  d
    5+ V; _$ r- L3 m; B5 S9 y5 q0 d
    68 O1 d6 Z: W. P: S0 A
    7  K# H, v7 E+ N7 K
    8# N' a5 T) L; ?% u' {
    9
    2 t6 h1 y/ q! D" r* v+ M- n; Z10
    # N8 A. E3 U( O( j  U110 p5 ~. C" L" @. X8 h2 c
    12( `; f  R; I& R, A' ^
    13( h8 b. X1 r1 J# X
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
    4 x. J7 l' P% {+ l( ]
    # K% y2 G0 J1 R1 F4 |7 V* \" gs.cat
    . g0 ?9 l' N- F, S# E2 \/ I- ZOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>6 n! m* f! G8 d! I" H
    1* h8 @. u3 Q" u7 W
    2
    4 [* g2 m- ]* z; J$ f, N. ]1 ^cat的属性:, u* t+ `1 ~/ M$ C: n6 J* x

    # a; k0 F: z/ j' u' e& D) s+ N! jcat.categories:查看类别的本身,它以Index类型存储: V0 T: a, Z( ?. _
    cat.ordered:类别是否有序, W1 E! I) E) `1 C) d3 }9 n! h, C4 `
    cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序5 D' m/ {7 U2 h" P) C. x9 i
    s.cat.categories, j" i' ?% o: X( s% g
    Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')* x1 [9 H9 w( L1 u1 G( I/ w

    6 U* ?6 p' \, v6 ss.cat.ordered" A' o' g7 c4 ^% g' X
    Out[8]: False- e9 h. R$ ?. m
    7 H6 M2 p0 C, W
    s.cat.codes.head()+ b# \( B& V0 m- q: f
    Out[9]:
    3 b$ ^! E8 W1 L+ W. {$ l0    0
    # t& b. X; W! K& N% k1    00 P* a  o# U- A( T9 I/ }. |
    2    2
    ) ^' ?! G' q; C3 F3    3
    ( b( c! g5 v+ d4 [( Z+ V. C4    3
    2 W: Y" E( F" M! k4 f# Jdtype: int8
    % _( q  o9 C" O7 D! u1
    9 f/ H  T4 n1 h  c! t22 C' p  d- V  N
    37 b) _- v2 M0 w1 L5 I* P, ~
    4
    ' ~9 [8 _  Q4 ]0 z, J9 J% {/ n9 k: r2 u5+ G& w' M5 j) V% M! u1 _
    6; {5 v9 f, c" @8 j- J
    7* ~- l* C; i# x* G7 [
    8
    & c; r/ `# t6 r, O/ V- K/ l% t; F' d98 M. o. g; O1 X
    10
    & |6 F$ Y% B; v9 H* `+ [11
    / j) v9 g8 S. r) ?8 h% y12. B4 A- }4 y5 |& M
    137 M, ^& y; M- T0 U6 n
    14/ p3 ?0 [8 G7 b  ]9 e5 G' F# o4 Q
    9.1.2 类别的增加、删除和修改( j* k3 e6 `4 B
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?; r! ^5 a; O8 P0 U7 _; O) }0 A
    ) R2 C/ ^$ e9 p7 O
    【NOTE】类别不得直接修改" h- @% s: i; U8 l' A2 F; p' h
    在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。! P( f7 E1 {( T

    7 L( R7 }4 _. y9 a1 O' S! p  e/ O. H. Gadd_categories:增加类别6 j. m' ]+ D, T& v* ^
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别+ L- F  F0 v/ E% r* l
    s.cat.categories
    0 C" U  J  v6 \+ J: c( K. W
    9 p; K$ R) l6 R, n0 N( LIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')0 g* w7 m/ i: M" j& l3 p
    1
    3 d: ?8 @' @0 r9 \4 l2
    ! ?$ |2 w  x: Y1 X7 R9 s; a9 \3! X4 H% k% `' X% w, c! `: T
    42 ]( ~" v. S2 x: h; [8 c) ~. x
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
    ( K  z2 m9 ^8 {/ w( \$ ?9 x9 Os = s.cat.remove_categories('Freshman')
    - [! O* {) j; m0 o, Y2 z1 r, u9 a) k1 E, T! A4 F5 S1 q+ Q  _
    s.cat.categories
    7 i& @2 \$ x0 ]5 QOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')  ?' Z$ F9 G) m; N

    " D8 H3 x8 ~- ~3 b* Q& es.head()) ^6 t8 G7 k3 e2 [1 w) |5 W
    Out[14]: : \. e# j: a3 S; N7 D9 C2 H
    0          NaN3 e+ P5 @7 d: O% R" }
    1          NaN
    * \) ?' `* a4 S4 j7 h2       Senior+ f) b3 G/ k) d& E6 G
    3    Sophomore& n0 O# e- S1 l
    4    Sophomore1 S/ b7 J5 K8 A# _; Z
    Name: Grade, dtype: category: s8 @+ u' Y! @1 T1 Z! _
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    / D1 k/ n/ s1 ?$ z) n; p+ L1 ?1
    3 x5 [/ h5 |, b1 T2
    9 O+ I: b; D' r6 s, r32 O# @6 F, q) B1 `0 l
    46 C- N4 l9 d' U$ \1 d
    5( }9 ?5 G1 R. X" [# V
    6. V# F+ Y4 m7 c% }
    7
    / @; z% C. M" l; p4 {0 R80 j7 K4 \% i' Q7 `: F) ]
    9: e! k0 {( \6 @, r) T7 ^
    10
    - ^  Z" C) \; p/ |11& u9 ^6 u1 A' f1 N
    12# z8 x9 z& x, D; ]3 L0 i1 i2 ^
    13' B# t6 @+ u6 W3 \) N: p
    14( l' ~7 p9 }' P/ q& U! t3 A$ i
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
    # q5 P$ b* ]& y. q4 Fs = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士5 w+ t; Z2 _# H) g7 @
    s.cat.categories
    ( {9 u# G$ d; W- n5 _# |+ OOut[16]: Index(['Sophomore', 'PhD'], dtype='object')& c# Y1 @/ w" X5 z1 j: G% }

    - a. l" ^' L- N. Ps.head()
    8 ?6 _0 S5 M  mOut[17]: " |- V1 _3 Z4 r' q$ y, w9 `7 a
    0          NaN& \& ]. ^5 C& j# L" ?1 ^2 s
    1          NaN5 c3 [4 ~6 e3 p! V+ Y) [
    2          NaN
    1 p7 K. t- t* ?3    Sophomore% j- L) N5 b: H, C+ d; ?  w
    4    Sophomore& q& w% p7 g6 C' x8 `' }
    Name: Grade, dtype: category3 I$ g* b! R5 P% b, i1 ]5 R
    Categories (2, object): ['Sophomore', 'PhD']
    # l% ]2 f; d) k$ X) M: D/ M( s- _1& N  u/ g# D( K, E
    2) S2 ^) Y5 h$ q9 w% |
    3
    : h  l1 t, d( f3 E4. S5 b( P4 D( {. d/ J& I+ t4 T
    54 z4 D6 i0 k& w
    6& k" f- _5 y2 h( {$ V4 |
    7
    4 i$ X; X+ _. X' Z  y! q3 A8
    % j  g9 y- ^( s; I0 D9& \9 z7 K& Q$ w7 w+ A7 q+ N
    10" T! @; W9 G: P) F5 \5 c
    11
    2 h3 f" }# s0 l! ]( G3 U- e12& k0 J7 [& y$ I( _0 f  h
    13* a( t8 ^: U1 J1 ~
    remove_unused_categories:删除未出现在序列中的类别8 e7 P  P' Q# n. B, y
    s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    ) H) g3 j0 |' F6 |! n/ ?s.cat.categories8 I0 \; u* _. X$ F% l' N  D
    8 K. S7 ]2 h4 \! R1 c7 F
    Index(['Sophomore'], dtype='object')0 B& Y) \. U. Z! a0 X+ l
    1
    & E2 O9 y8 \+ _/ Y- O- Z2# s% p* r: r0 r, b& \& V
    36 @6 P& M( G' `# z
    4
    ! L. S2 F/ N* J9 P. arename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
    ) k5 [7 |$ \- [5 z2 ts = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
    3 E/ x. f0 s  ~. Rs.head()/ W# ]2 `) X- N: e: b) ?8 w
    9 f  ~9 D/ x* q0 |9 e7 G4 L# F
    0        NaN
    ; C$ `+ D) c) u# n; \( c" O1        NaN
    $ j8 d' L  w' X' H2        NaN% H  [, [( a$ g: m3 y+ h) o
    3    本科二年级学生
    4 U7 b0 e* T& s8 s4 p4    本科二年级学生
    : a) ]' G. O% r4 {* Y/ Z4 TName: Grade, dtype: category4 d9 p/ w/ I# r! T& l
    Categories (1, object): ['本科二年级学生']  v2 }+ l/ d8 u: k) e7 L/ N
    1
    4 |: F& i8 E( I2/ P" E+ N5 p3 w+ A$ D, j3 Q/ P
    3' J# m+ p3 i' f, i) v  W9 V* Z
    4
    4 `/ C6 T4 a$ K. {& J* I5! ~7 E; I3 g. Y7 p9 Z
    6& j# X2 ]6 O# K0 l7 C; b" g
    7- a9 e1 G' G4 c9 w% m4 G6 v
    8& |0 |* i5 o3 J9 ^
    9
    * p8 F& c8 P" M) n) I/ S/ ~10& l9 J, e. V' ]7 I7 r
    9.2 有序分类
    8 y7 i2 [, k% m' C. c9.2.1 序的建立
    4 h9 L& O4 f% B! M  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:* ]6 n& \3 E+ V) F& {5 b
    $ K8 u" w4 X  ^! E4 s
    s = df.Grade.astype('category')2 d# L* C" V, n$ C7 K, \
    s = s.cat.reorder_categories(['Freshman', 'Sophomore',
    1 H6 T1 _" ^7 i& b                              'Junior', 'Senior'],ordered=True)0 @$ r, g) j+ T* v. O3 }' U
    s.head()7 k1 j' J; Q: w( Q
    Out[24]:
    8 @/ E. E" f- a  V' q; x( B& ^) s0     Freshman
    ! L. H' p: s9 w1     Freshman2 O. C. D, G0 C" \) N
    2       Senior
    3 M4 W% G: J0 z% B! _3    Sophomore
    " k8 e1 c+ t5 q1 l, x# {6 B4    Sophomore
    * Q& t% r( u. {Name: Grade, dtype: category
    ! t0 H) R6 C; z" ?' rCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
    # p% ?( ~0 L# @$ R3 z# q  I9 o  w- d: S" p! W5 Z
    s.cat.as_unordered().head()
    5 X5 m7 A; \; o, \Out[25]:
    , K$ I+ A$ \: `& S0     Freshman
    - X9 N' i9 ]" J9 D1     Freshman) o) V/ Y1 M7 R5 B! d. M* G! r
    2       Senior
    8 h+ ~* e' t5 Q5 B' L. C1 _. g3    Sophomore8 U5 w9 H" B; G/ l' p
    4    Sophomore
    9 I6 p5 Y( k& U8 c% e4 lName: Grade, dtype: category" y6 w( s4 |$ S1 r( b
    Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
    ) Y9 ^6 v7 R* T4 u- K9 l% c+ e% D+ O
    17 s. u+ y& V4 ^6 A+ I9 P
    2
    6 A- l; n% N. I35 x" W6 M! T% L  q
    4
    3 f7 [: d* V. w1 e' K5' ~$ [5 Y6 r! C& |
    62 b* O* ~% u( C2 c1 o! g9 F
    7' M2 m& l; K; ^! L, N
    8
    0 ~& b. {2 q# v5 i! u+ o# h7 R91 f2 {. S' {5 s4 Y* S5 }
    10/ c  Q! r% `4 z5 c$ B
    11. [, g/ V3 x7 ~& B$ u
    12
    # j( i$ j+ D! H13
    # D- U2 x& ~* B0 T$ S, m14
    ; c& Q3 M. r+ p) G# b155 ]9 m* r. U, C# y/ i) t/ Y
    16
    $ ?  n2 f" g' x" N3 H; g2 _8 y; a171 j( v7 K, H' _7 `* A" B
    18
    6 k3 Q5 l4 K- D. p6 l19( K7 L: f! a! |2 Q
    20; S. {7 A% k, J3 V8 [
    21$ z9 f/ D, q6 v
    22$ Z! d% y8 \- }  i7 C
      如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。  _+ V+ y4 x4 Q* F8 ^: o

    7 S/ i# L9 B+ B  A0 E. j: x9.2.2 排序和比较3 P8 }. q  _6 d( I# w
    在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    " L$ X2 r, c, ]* c$ f7 `& |/ M9 @7 f" x  s
      分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
    2 C) _% g; v6 M3 w9 F3 O$ Z8 l
      i% }$ r+ {6 ]+ q7 g+ y1 l, j8 ddf.Grade = df.Grade.astype('category')( y" c6 y/ r' J; V+ u
    df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)7 F- D, g0 Z: J1 r2 v' M0 W
    df.sort_values('Grade').head() # 值排序, c0 h* p) q) }+ n# n
    Out[28]: # }8 J1 A* p' C* r5 O
            Grade           Name  Gender  Height  Weight8 l1 |$ n" d9 U9 ^' e8 [) q, Z
    0    Freshman   Gaopeng Yang  Female   158.9    46.09 ?- Y$ k/ `# u5 O$ C' o8 L. }# ]
    105  Freshman      Qiang Shi  Female   164.5    52.0: K$ D1 V+ C8 k
    96   Freshman  Changmei Feng  Female   163.8    56.0
    . g" _% d0 I. t  m88   Freshman   Xiaopeng Han  Female   164.1    53.0
    # A0 v& t: t5 g: Z2 j4 T6 y1 z* n81   Freshman    Yanli Zhang  Female   165.1    52.02 G* S5 G# ]9 j7 w0 i: Q3 T- L& |

    ; ?4 K2 U% C( v2 k! a! P* K( xdf.set_index('Grade').sort_index().head() # 索引排序
    ' C# g/ w0 z) H$ Q( o1 YOut[29]:
    ' t) R4 H# {: z) {                   Name  Gender  Height  Weight# O9 w1 B' S2 k. S
    Grade                                          ( M% ^. `$ v- O3 S
    Freshman   Gaopeng Yang  Female   158.9    46.0) y8 }. s& ]% U  ^5 g6 i
    Freshman      Qiang Shi  Female   164.5    52.0
    # E2 [# [% |* k6 P' ~' f6 s- vFreshman  Changmei Feng  Female   163.8    56.0
    - k" `% F8 L) \: W& a, cFreshman   Xiaopeng Han  Female   164.1    53.0
    ) z' Q  v9 D# r* D. ?Freshman    Yanli Zhang  Female   165.1    52.0- c& ]3 w8 [; b: j5 M# ^
    / T" w/ _5 N/ Z
    1
    1 G; D( p9 T3 w3 U/ ~3 K2
    ! D: d; X& `4 H  f/ j) M39 B& j) m( R# k6 F
    4
    0 [! w4 h/ ^9 s% x. O! r5; B; t  D) D) J) I1 \
    6
    0 C. y, Z; ^" s/ K' G/ R1 R7
    $ }1 P4 a& [1 ^) G84 G8 w' g( C! |: p  ^8 t) `& E
    9; }1 d) @$ f8 X
    100 M) J4 Q! @# M" ?1 g5 [. ?
    11' Y5 A4 e! J. r' P
    120 S2 S- U; i5 R! _' h
    13
    ) ?8 V5 x, E# K! f: V14- S( _6 ?; ?5 A9 V1 |
    15
    . I4 z2 t% w. P3 C- y0 B" x16
    8 A0 P7 l; s+ A% o171 l* [7 `  ~* G; _  O1 U
    18! K* e- X* h  r1 D
    19% O4 [4 ~2 T! ?. n
    20( Y" F0 q3 F; ?2 `3 [
      由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:5 r9 a" `7 z% l4 C% M0 A
    ( `: Y7 o9 @" ?8 x+ i; ?
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
    & x' s( \! Q+ \; V6 q  A% V6 h. c>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    $ O, h8 t$ F5 u& x& i# n+ p3 xres1 = df.Grade == 'Sophomore'+ V" t- V! b, [
    7 b4 @- D- \' y  W/ r, d; s0 f
    res1.head()
    * N3 a( l2 g, H* @Out[31]: + o8 r' Y$ P0 C" S& z, R. Z/ _
    0    False2 O7 K, O0 a  c# v
    1    False. C# ]. w/ B6 f! [* {; o
    2    False- ?6 y/ o0 I4 T$ o! E* z3 z! X" V7 |
    3     True
    " |' g1 i; @. D4     True
    9 e% Z( P4 H& p) [3 yName: Grade, dtype: bool
    : u% C' |9 E/ S4 P! d  {6 k0 E7 G1 \9 j& ?1 F3 p" t, N
    res2 = df.Grade == ['PhD']*df.shape[0]5 n$ I2 k; k8 @! j2 H9 L7 Z# d

      y- s% Q0 h; O- s! ^1 y3 ?( Sres2.head()
    # A! T5 B6 U* oOut[33]: / O( ~* P8 k' j4 M- {. `. k1 @2 ^
    0    False6 B( @$ v6 `( j5 o" q5 b
    1    False
    2 X; |; l9 e" s; F! w7 O: S3 a2    False9 ?$ Z9 N* E, h8 V
    3    False
    4 E* p) m- p% q# a2 G4    False& m7 \2 v2 j* O* B) [3 `5 h, b
    Name: Grade, dtype: bool
    9 s) p  D' j  ]* z, h7 ?
    $ [+ }6 P8 D( H: y$ G  Q; G8 Q' @res3 = df.Grade <= 'Sophomore'
    ' i8 o0 q: J( j# @# h3 e
    ! @: y& B! M1 ures3.head()
    - ?3 p0 x3 B: B$ C$ hOut[35]: 4 n! r+ J' e' B, l( c. w
    0     True
    & N0 l" l' o/ d* v, ?* T3 ^8 l1     True" x0 |. K2 n& e
    2    False
    ) ]0 h5 l* |( c( m/ }# x( ~3     True
    1 q1 {8 V1 P' T3 Y1 ~. o1 Y# P4     True
    5 S! E+ }7 o$ E% V8 U- J! m' W0 sName: Grade, dtype: bool" Y7 o! a; X# H) Z% _

    : ~; h5 ~# P8 ]: b# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。# y0 c% m' `2 a' F0 e) c
    res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
    0 Y  _* k6 [9 m, J# h/ {9 }
    6 R+ d' D! M% j' l7 h) ^% M9 \res4.head()
    ; [5 k9 u" E9 |; R8 Y8 p( FOut[37]: : L8 R2 Q" u0 K  H
    0     True
    ) F7 e& c5 N" r$ a/ |1     True
    8 Q7 M) l( s; \% N* u$ }2    False& b9 ?& p; j' p  ?
    3     True
    8 w8 S- R5 d: A6 \4     True5 z* g2 d4 _* c7 C: F7 j
    Name: Grade, dtype: bool8 m4 H$ F  D- W6 w: W8 q0 w
    8 O+ M8 M; F2 F, [' m
    1) Q% `" w! N) U: r) }/ ]0 O- r
    23 \9 @. s. S6 A! Z3 \
    3
    ( [" t1 [' p  N% d3 Z4
    2 T! [2 V( q, E0 b4 F  B' W59 g( y- a5 @: V: e
    6
    ' e9 [) s$ D) T7 ^7
    3 C, I: U0 c' u; m) q/ \  Z8- B2 T, J! j8 v& ]: r% U
    9) `: Z, P% k& }/ u
    10
    + [5 U+ H2 f/ K11
    - J0 J$ F! R0 t. d12
    # l7 n6 D) {, Y; g, p6 B- D! Z13
    5 A, f7 N1 }0 A- w! |7 o: s14
      q! {6 q& y& m156 S3 G' D# i7 G6 |/ Y& X
    166 c% D& O7 J/ P& ~
    17
    ; O5 w8 t6 U& T% }+ m, m18
    & F  \. _1 `; r( e7 }8 Z# i191 q2 d0 l; f% |, e
    20( b0 X; n: ~0 i! c
    21. t+ b* ]' {" \; v9 g
    22
    + P% A) }7 b; Q230 g" r: {* ^. u5 B
    24
    . \; t" _( l2 W+ E4 `/ n- m( i( s$ W25
    . v! D" E5 u( R2 Z26
    % ?5 U, U/ @5 B  A' c6 T273 F: p& N( T6 T) w$ ^# k
    28( `% t7 g6 |: Y% W: v2 E
    29$ n+ r' t8 \$ m% e* z4 `9 ~
    30
    1 S3 l8 |+ C- Z* y6 c2 X319 b: H: C- G. H1 X6 e. h
    32
    1 R0 ^- P# O( o( i/ f3 x9 _: Q33
    , k6 S% D" R. [& X34: Q" b* ?" Y9 T( |: C1 ^
    35
    8 O+ `$ ~! {' s% b3 Y1 U36
    : y, [  x# P6 Y' J6 M( z) I37
    # K: _3 M4 A4 g& U: E384 g/ G% O! v, N, U+ p! [4 i
    39
    ; Z/ e! j' [- t- j3 y40
    $ V& W  x3 B" U8 D6 M, e$ G41
    3 }9 b% ?& @4 P$ m& j6 M42
    ! O" r* ?! m' O( y  h( \& H7 ~1 Z43" L; l* B; Q" P' W
    44
    / B9 y( O7 a- a8 e9.3 区间类别
    " J4 ~2 P3 q# S: V$ Q1 B9.3.1 利用cut和qcut进行区间构造
    % V' E2 g: Q% E9 d  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。$ {4 f, x$ ]. C/ x1 Q
    : `( O7 u1 ^9 L
    cut函数常用参数有:
    - J9 Y4 O% u7 p) N) ebins:最重要的参数。
    4 L2 K, ~/ [. H$ |如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)! m, j! M( G: m1 W. L; y4 L
    也可以传入列表,表示按指定区间分割点分割。  B. Q" Y0 [$ J' S. b9 h6 W
      如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
    * y) {" b: x) g  J8 a+ i  如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
    $ @( s; t7 X3 q; g/ P
    & M, Q& s, W% t! W" us = pd.Series([1,2])
    # }4 n, r7 U, `# bin传入整数* Q6 r6 ?/ b' g' ]) n7 H
    ' ?% |% o1 y+ E
    pd.cut(s, bins=2)
    - I# V$ O9 S1 A  l( }  MOut[39]: 1 X" N/ ^+ l& v/ ^, T
    0    (0.999, 1.5]3 D" X7 j+ w! L  x- T0 r0 a
    1      (1.5, 2.0]
    $ C- ~( q8 H8 t$ Idtype: category+ x# m9 {5 B1 v- F
    Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    3 j  p# _7 K' G# e) ^) a8 r7 R8 U5 h0 R1 R& b
    pd.cut(s, bins=2, right=False)% M/ s9 q% i+ E
    Out[40]: ( v9 Z8 _5 |1 E3 }8 V6 \; y
    0      [1.0, 1.5)
    ; ?1 t! n/ U, G0 f) I: ^1    [1.5, 2.001)
    % K  J4 g' a- w& Tdtype: category
    * Y. s& L, v1 c/ b' nCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
    % o5 d4 E! b! P7 p6 X0 p% o; J
    " m! G; e) q* O; @1 x$ D" `9 Q  g8 t, Y$ j
    # bin传入分割点列表(使用`np.infty`可以表示无穷大):- o$ V2 z* f* s* l5 y) d
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
    ; _' N$ w& Y; t2 p# f5 y5 |+ ^( gOut[41]: # u6 V0 Q& i6 X7 R% p7 t
    0    (-inf, 1.2]
    5 E7 X& O) ]" w6 m) ]1 {1     (1.8, 2.2]* D* o6 k1 Q2 v; O
    dtype: category3 V% R: i0 x( L$ O# `
    Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]1 X- s+ Z5 f, b$ Y4 g) d& S' |
    0 a" [4 n% i$ t9 d' H: O8 j
    1& B8 z5 c, c% L$ u% j6 s3 j1 g7 |
    2
    ; {) N7 h9 L9 C% `  Q/ j  O. H6 _" z3
    6 @, x) k% N, R8 M7 l4* b6 S7 ]; _% D
    5$ f; B4 Z. J' L/ B
    6
    6 P. g6 s  B6 _8 P5 `8 M7! X* E1 T$ G; G* H# `
    8
    1 O) ^* A' j; s# _1 ~) S9
      x3 Z0 [6 `. N( ], E4 A10. Q$ l4 z' `7 }
    11& z; X% X0 z) K
    12
    ' b. |9 Q3 f/ |# c13
    ' f0 v$ n- d' l. ^14
    4 d# y4 ~& |! \* V7 J15
    3 M# h" t! b( q: L! t; W* U" r' @) p  v16
    0 s4 o# v+ H  w# a  `+ C17; |: l- j0 b& _% [, n" L7 p
    18
      i* W! |+ Q9 H5 e8 T* J197 _4 ^( L$ _" O' h+ Z7 F
    20% v2 F2 K/ @* g. g  o1 y
    21
    2 ?  f8 g# \( h  Y; \  G: Y+ d! u225 F1 S/ P0 x& }/ P0 P
    23% {" H9 a' a* a2 t) n
    246 |* |9 A- o2 F4 U/ k1 ~
    25; [6 r8 r9 z' F! j- b7 B* q
    labels:区间的名字/ L+ {! U. v/ [: x' B3 c
    retbins:是否返回分割点(默认不返回)
    & j& Y1 f( @& s* {9 w2 \& g) i5 S默认retbins=Flase时,返回每个元素所属区间的列表, u% L4 s$ W% a; A2 Z) ~% p/ S6 ^
    retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值+ ]- D, G5 K+ Y$ {2 F

    7 @# O  L0 T% M3 v* P" Os = df.Weight$ Z( l* ?1 L' c" r' n# @
    res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)# @8 h# B' V" Z
    res[0][:2]3 n2 \# g) ^  [& {

    ; k4 p9 i5 t; A. w5 `Out[44]: 1 o$ q" k* T# P7 F! ?
    0    small
    * T4 d6 V1 L+ l( w1      big* J/ q" }" x; l. v
    dtype: category- g- D, R8 g  Y3 s
    Categories (2, object): ['small' < 'big']7 Q$ n. k2 @( s9 g4 L

    ) H0 N% t  g8 Sres[1] # 该元素为返回的分割点
    $ n* w* L" L" s! W  g6 `! W" ~Out[45]: array([0.999, 1.5  , 2.   ])
    - Z" z- v# }4 k# U2 J1
    ' [& W+ f6 n) l: k% Y: S' F0 m" J25 `# b. g( E1 h) t+ w8 h
    3
    - I1 X3 v7 ]' i) E/ p; ^) T# G4
    ; ^/ `9 G9 d9 s! s5
    " Y& B/ J4 E  D6 t. }  d6
    7 F+ p, E6 s( @. N9 x" k7% I6 H! g1 J; c
    8% D! ^: |, @: p3 o) u/ y- p
    9% G3 n; Y( g4 _0 H) @
    10
    & I+ b" p, U3 E- a11
    $ O. k& `, `) h  c) O+ O126 V: h0 E7 e+ D3 O/ u. q
    qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。9 Y( Q) t2 r/ o' q
    q为整数n时,指按照n等分位数把数据分箱
    8 b  \& q: y; n. a" Vq为浮点列表时,表示相应的分位数分割点。  b! l% U! |- `$ v5 R
    s = df.Weight
    : V" W& x9 I5 o' v; ^' N" Q
    1 X* C; W6 v! v) F- q/ r- ]pd.qcut(s, q=3).head()
    1 ?/ n4 }9 T- `. @% DOut[47]: + E- f3 k0 L% y' V# E( M
    0    (33.999, 48.0]2 J. d) t8 @& b5 J3 B; p) U2 v
    1      (55.0, 89.0]
    . ]# z5 E' S" }( v8 N1 E* P5 D- x6 Y2      (55.0, 89.0]: C( B9 W; n/ t3 i" v
    3    (33.999, 48.0]
    % p( F( q$ F" B# Y2 a4      (55.0, 89.0], y% k' q2 s4 ]! P/ L
    Name: Weight, dtype: category& e( q* `6 k+ ]
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
    9 y: X  \8 h+ `3 s! B$ q8 l" E! T3 G+ g" s9 V, \
    pd.qcut(s, q=[0,0.2,0.8,1]).head()9 U/ }# z* `: y' F0 n$ B( ~
    Out[48]:
    4 v& b, i# ~* I' q2 i0      (44.0, 69.4]* q0 i8 g6 F9 f' ]
    1      (69.4, 89.0]
    % K7 L5 Q6 n7 A: q, y' {2      (69.4, 89.0]1 x- R) V5 R! X( K
    3    (33.999, 44.0]! G8 I( T' a, L0 F
    4      (69.4, 89.0]: s) b* d( k/ k% r  ^# P4 Z4 @
    Name: Weight, dtype: category: Y3 P. W0 B. t6 N0 l
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]: X9 A0 c1 c; z/ `! V+ k

    " z3 {7 _1 u9 U1
    5 K  n$ h9 R7 r) ]; m0 C9 P! p2: W) T5 S4 R6 c
    3& V/ M% n0 ~7 Z$ R: x2 S* Q1 `
    46 W4 n/ v! @1 q+ z
    5- |5 j$ |% j8 O" _' \
    6# i0 h$ |& U7 E: j) X1 V
    7( C5 q9 `! d2 ?" L
    8. [% U4 y: K. v
    9
    0 n+ D' P' S* m# k10
    3 _. j3 R0 R5 {- }- u, Z11/ T2 Z4 L( g. h2 J
    124 w/ p7 T$ ?8 d$ C* @7 D4 X
    13) U0 U! n2 |, A+ c, X, b& ?; U
    14" w0 P7 M; Y1 \$ Q
    15" A- p& Q3 @5 ^: X; A
    16
    6 I9 U. Z: i7 g" P17
    5 f. M) N& r; F8 `1 m" e/ ^3 v% T184 R, R: ]8 t4 q" h* s
    19
    ! v* s2 I+ [9 _1 B- w20* N- M- Z3 H5 H$ A9 g, k6 E) Y5 E! d
    21" |* ^$ ?: l- v# ?$ O* _
    9.3.2 一般区间的构造
    ) y: A4 h$ f" H7 n$ S7 U  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
    ( H8 [. A5 P: U' {/ L9 Y  [# d" e" K/ _7 v1 l8 J
    开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。( N4 }; C9 H" a5 X- Z( F
    my_interval = pd.Interval(0, 1, 'right')- a( e2 F- o$ B6 L3 k( e

    ) U+ N, `. w" B" J7 Amy_interval
      D$ \2 t4 |. ]1 D2 D% fOut[50]: Interval(0, 1, closed='right')
    1 T3 \+ j2 R+ w. _1/ ~6 M8 l$ L( ~5 I# l; |6 V/ r6 G
    2: g, B2 E" B1 B7 V
    3* B  m* V9 @0 P  c% ~: I+ J
    4& d9 |, y4 A# B0 s# u- _0 W2 D
    区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
    1 J, J3 ~( n+ r使用in可以判断元素是否属于区间
    - O$ W$ G$ b4 S. H2 _# a( r用overlaps可以判断两个区间是否有交集:
    , A- ?& h" B3 s' H# W& Y) Y0 i0.5 in my_interval' K6 D* Y; }# k1 g  z7 L
    # W; l" Q8 \) l& ^1 J
    True
    1 \: [! K' f- q9 y4 W- v; s+ h9 @1
    # {4 c) l! e+ V4 N' M, u. t+ E) [: \# K2
    0 q: h% G, k1 ]0 {# d4 r, e# {3* H5 _: f. M" w9 |8 Y
    my_interval_2 = pd.Interval(0.5, 1.5, 'left')
    6 Q5 Q; d$ k% r' c7 z. Emy_interval.overlaps(my_interval_2)% G2 i' ~) Y) S8 W! T6 q/ ]

    $ w, q: H- h, V. [4 gTrue
    0 e$ o+ V1 `' C1. b" f1 B8 L4 f, J5 X5 w. O1 O1 [; w
    2
    4 a. }' A! K& C2 Z3/ }' D% `% t" t1 {8 R
    4
    4 j' D  t1 z$ L" f, p1 `) N  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:& @# y3 B8 l& Y( }2 x* K4 N  H

      l# Y3 p' L# K& x0 _from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    ! ]' H) A1 _  n, Q' r" ^7 ypd.IntervalIndex.from_breaks([1,3,6,10], closed='both')  u7 p$ j, A" u* p6 F

    % l3 d4 a3 w: S6 wIntervalIndex([[1, 3], [3, 6], [6, 10]],
    % `/ t; ]% H/ e: P/ t& R8 i8 d               closed='both',
    0 J6 }- y! C3 `0 ^- d               dtype='interval[int64]')
      X- I% v9 d( H1
    % v& _3 C* E3 ]* ?2, ^0 |5 i( {% X% o5 y
    37 n( v: R3 r9 Z1 c2 i
    4# M& A- r# _6 D8 e: J$ }, p$ g8 G
    5* Z! R) x/ x$ }% Y( n
    from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
    ' U: ~& @! m" g8 }' j! Npd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')0 A) Z3 e* a7 q, Y5 l2 V
    4 W: R; D& }# \$ r$ {. m
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],# u+ l, z2 J' j* ?; |
                      closed='neither',( K* b5 |/ J$ y
                      dtype='interval[int64]')
    7 Z, X+ `) Y; ^% m: x4 a8 ^4 S1
    3 z3 ?% O: d$ o1 g3 I/ R! r2- B! `, C' K- V. r5 R! c: \
    3# w$ o7 R6 V1 B5 `, J' [3 p
    4* T; k& E& |% I, C, v8 s- u9 g
    5
      Z( F4 I, q% S% r" pfrom_tuples:传入起点和终点元组构成的列表:  x7 J* c, _: _( Y- W: P" X
    pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither'), V0 I) Z$ `8 i' U

    3 O/ {+ I2 o! P1 d# c+ AIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],- g) }9 X3 _+ r/ `  X! F
                  closed='neither',
      y4 p# h3 q8 }7 ^# G2 q- z8 l              dtype='interval[int64]')
    ; m6 [4 }. K+ P15 V! x, e! q) T( g* S
    28 G: C! z: B3 c. w" a! _2 D
    3
    ! f/ I- l% \& j) A) S% T- A3 Z4
    ) D: v0 I7 W+ \% W/ `. c5
    0 S& {5 j. X( B- @$ E, S  E6 D7 K) {interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
    9 e% U0 y! a( u: u' y) \3 U8 kpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数4 L( G2 U( |3 n8 F, R- N+ ?
    Out[57]: " I6 [) e3 H, S. J- J7 W- X! N
    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]],
    9 w* d4 g% \8 Z( m0 k              closed='right',# }$ N" v. K% a  k: ?3 _
                  dtype='interval[float64]')
    0 a0 b% S, V# a% J& n. ?1 |$ Y- O' t9 ?9 B6 Q! a1 u& ?% M
    pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度5 p$ n4 R/ q. n' e5 l2 Z6 d0 _/ g# J
    Out[58]: 4 O! r9 b$ G4 ]' [0 M$ D
    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]],1 g' U, A. f; y) Y; ^  }
                  closed='right',+ Z( j2 N1 L9 z: E- [" |1 R: ~
                  dtype='interval[float64]')
    $ g' \1 t5 b! Z( d7 o$ a1! d* }( l3 Q: m* z( |
    29 x3 l4 {3 g+ q- F
    3
    8 |, ~1 y/ e' E0 X4( m1 {6 C; \/ s7 E% t/ G
    5
    : W/ q, Z. Y7 ]  E2 u. `' f65 d3 {2 P; ]# a0 p2 d9 e& O3 P
    7
    / @" B( u6 J. `: [& |  O8
    ! R4 S4 w3 B0 O9 Y$ E9
    5 z2 _7 i/ l& \10
    + Y: m7 i- a+ U) @119 W# X) G, v' ^3 s; X, C; B7 V
    【练一练】- V7 T" |1 H4 Y0 y  _
      无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。9 X) Y3 ]9 H% A  j) C4 ^
    7 y# f' {8 o  |/ E( g  Q
      除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。; W& |2 z7 s! W* `% ^, N" l
    , \# K( b, S# l, j% C' ]/ R# F  a; B6 u
    my_interval
    / }6 n1 P! j9 D: ^7 tOut[59]: Interval(0, 1, closed='right')
    ' S$ E+ t! c3 O6 U; K+ U. X
      p7 [. J* o1 N1 U  x* {my_interval_23 B, X# k, P+ O. o
    Out[60]: Interval(0.5, 1.5, closed='left')) g( D; c( l7 f/ x! h

      D% i- D+ |  `1 n& C& Zpd.IntervalIndex([my_interval, my_interval_2], closed='left')
    * U8 [$ V) u4 zOut[61]: 1 J/ n+ X, U: m
    IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    7 @0 f9 Q' v2 ]& r              closed='left',
    - u' T. F7 }. B4 V2 B& H9 g$ F              dtype='interval[float64]')
    " L' W5 B2 h( j# q! o$ |: R1
    , Z8 F0 S% b* X8 L7 |, V" ~2
    4 P2 y. I+ X+ {$ t( t: T( O6 |3+ K* @4 O  V# I6 t
    48 V4 W5 x) @7 Y! X9 \$ ^
    5
    % ]1 g0 F& o6 C' ^  M$ O2 K- I& I65 A. w) N: Y0 C# l5 c9 u
    7
    # D# c4 z1 Y, |+ b8
    , I" k1 G) P6 ]& @* h- W9; @/ B2 w' v. g4 n, G7 e6 Z
    10' o: Q, N' R5 D* m
    11
    % [  b7 |! C0 C9 E8 _9 V. B9.3.3 区间的属性与方法
    & A( _0 c5 l- |; k' c$ t  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
      b# D: X& J2 e6 i5 a; d9 i% @! `
    3 y+ _, v3 F' W" u$ Ds=df.Weight
    " m0 ]" K# M1 s1 j: F/ R/ Mid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示3 y& H/ t1 f: K& h: G5 }0 \
    id_interval[:3]1 w& ~) P8 I! G7 K  @

    / R8 }# ~1 O7 N; j7 b% I, M! r7 t/ CIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
    % y" q  b2 a( d: F0 e, X                 closed='right',
    ' _. ~: {0 b) `1 o                 name='Weight',0 n6 [$ `( @! w2 L0 i% r$ S% D$ P
                     dtype='interval[float64]')
    8 |! z' \3 _0 Z- X/ G6 }' N! I1
    0 F( d- b- q" {$ g' E6 ~4 S28 ?% x( s5 v, N, w+ y! O
    3
    . o$ f  t- a3 l9 [" I( \4
    # }! K$ F, R+ k, K4 E  {! \5& M9 N. h8 R! J" J7 `% G9 K
    69 s! \: \4 M1 p7 d
    7
    / U; m: B( ]' c- I1 v8' A% s  u% o/ g/ r6 t
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。) D* A3 n) e% _
    id_demo = id_interval[:5] # 选出前5个展示* I% \: r! z3 C, i
    1 K- T" Y& L8 m. C* H
    id_demo
    0 }! h5 p" T/ B9 b8 a/ W- z- EOut[64]:
      a+ }1 ^4 H' c' z" i# M4 rIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    1 M1 I+ ^/ d% H% j( |6 W( Z              closed='right',' }7 E+ j7 R2 K- d# q3 A
                  name='Weight',
    $ @! @: Z2 o9 K* k- u( R3 w              dtype='interval[float64]')
    + l( i3 B% x7 l1 H8 N) w1 w5 s. g
    id_demo.left # 获取这五个区间的左端点3 |# A' H) @& f8 Y1 T& S2 y$ A
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    , M/ @/ j; |8 p6 n8 h, V4 p! ]8 y, d# B2 {( E  B3 r7 r6 E
    id_demo.right # 获取这五个区间的右端点  o$ u- D+ T! _3 _" Z
    Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
    3 t3 K4 Y1 a  n( I+ v, T
    ! F7 D$ Z( \7 z' @# y) vid_demo.mid' X* t+ |9 A+ i& \/ {8 S  B
    Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
    0 `4 Z: U% [# L+ A# t; L# o0 F  [! E) c2 ~! ?. E
    id_demo.length
    6 T8 H  V$ D; o3 _& L8 e+ _  O4 o; N9 rOut[68]: 5 g7 o: _, j: e3 f6 M& y3 e
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,
      Q1 z& B6 G. Y( |+ s* v. n, u              18.387999999999998, 18.333]," M: _2 M: ?; i! |7 q) F9 C
                 dtype='float64')
    + |! K- t# ^$ `3 }9 `, j, o8 N4 c. r# l. `7 E3 m# M0 D
    10 ?) b) F2 ^3 X) c, |) O* e- g
    2
    4 s" ^7 \6 L5 h* W! U8 m. ]: w5 Y3
    ( N6 ^# x- b. _- N+ i6 k6 u) e3 n4
    ) `/ M$ e3 b$ J5 s5 n5 g5
    0 D! h# f9 V4 {6 Q6  D' l7 e* w4 Q  C* A
    7" h1 {- R% W% f/ {2 l# m
    8
    $ G' J2 \4 V8 P) J6 b( q! h8 I% ~9
    5 V7 C) G- Q6 P10
    ( T, ^1 Z1 U8 u4 m; X6 I: o11/ z2 J6 ^4 v6 j" p; ?" x5 Y6 i( E
    12( c+ j; V' c. }: o
    13
    $ o6 @- b! X6 B% C& J& a. |) p14& `+ N; u7 Z2 L9 G7 v: \3 g
    15
    5 [+ N/ [4 \( p' v% K9 T6 n16
    0 M0 I. y  u& b4 B) x17! F* v% M/ W! L. a
    18
    1 G5 {. m9 B. M% Q* x: L0 Y& F192 O- k) R/ @! J, J- ~& J
    20
    " p; s+ |, V4 u21( q4 K, }0 @; B% V! @9 H
    22
    - y% K# i9 T( }! w( `. U5 t23
    7 h  }$ v0 {8 D8 KIntervalIndex还有两个常用方法:, a/ q+ j' X* y7 i9 [+ R! \7 g, V
    contains:逐个判断每个区间是否包含某元素& C! G7 s: ^) Q! I
    overlaps:是否和一个pd.Interval对象有交集。( T8 P. ]' |$ u. H
    id_demo.contains(50), \+ s( t: I( ^
    Out[69]: array([ True, False, False,  True, False])
    / s6 d( j$ G; t4 V" o( J" ^7 ^0 A. ^) y
    $ ?1 ^& }/ L" G5 }5 ^/ ~id_demo.overlaps(pd.Interval(40,60))
    + a& n$ o% g1 }6 }Out[70]: array([ True,  True, False,  True, False])" p% G7 x" O, R* a7 q; _9 E0 `
    1- Z7 f8 S. g7 E/ S# j# I
    2# Q* R& ?" ^# T6 V
    3
    ' n7 h' J- \) N47 [* K2 g  [- I
    5
    4 T* J! C- B8 l% J# n% D9.4 练习( e, V6 `* S9 i! u% \) M& k
    Ex1: 统计未出现的类别
    # H$ u3 J. Y* r  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:4 t, H2 Y+ P% G% n$ y7 A/ I
    : ]0 {: `1 k; x
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}). E4 F1 B9 U0 X: p4 N5 \
    pd.crosstab(df.A, df.B)
    . J: Y7 g5 u: J- X) e5 h
    0 O% {# \& A2 n% ^Out[72]: 2 S' @; M, s) ?& r  A9 u
    B  cat  dog/ _" c0 `# [# Z/ s+ i
    A         
    $ u# B" `  J' Z0 e! s+ P3 @0 Qa    2    0
    9 j6 g" g/ V' E: ]b    1    0
      m. c! }9 H  c! _c    0    1
    0 Y# [: S% k- A2 n! V: m  n1 W1; J$ W! O  g* W+ n1 d; d: @
    24 R2 y  f; \5 k& p2 r. _7 |
    3) R+ E- V. E1 m8 ?% d
    4
    : P2 b" l% e9 u% S. o5
    4 Y/ {# r: X9 X* j  @  K. D62 \( t1 q, C- d) `  P- s- g0 N
    7, F2 ]9 F& Q& m/ K
    8
    5 j0 l2 K) t$ W  r$ I' V# N9% l* `) A4 W, W: L* q+ U; V
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
    % _' n) E+ u' \6 K
    * n% v' R7 S9 L1 S+ Ydf.B = df.B.astype('category').cat.add_categories('sheep')
    / l9 g5 i* d0 }4 vpd.crosstab(df.A, df.B, dropna=False)
    7 @9 H& C+ C% X: F1 w& }7 t, j& O
      A0 I* n) |6 Z3 m" GOut[74]: / w' D5 F8 Y5 t
    B  cat  dog  sheep
    . Y8 U/ m- C8 z2 \% m# f+ C  gA                 + l5 X- N0 C9 h
    a    2    0      0
    ( o3 k: a0 J" B& U  pb    1    0      0+ d# z9 O+ w. G# @; @3 E
    c    0    1      08 C% z/ Y/ G% o( Q# l6 M6 Y
    1) h- W% ~0 g0 B) v+ @2 {$ m
    2
    # o! A) N& N- u: R) X3- W7 x; \; W4 j  X9 Z
    4
    * ?% z. G, t# p9 c# e. o- t54 s3 U* v6 o1 X5 A: W
    6- z' r1 A4 R; o5 s# J" X
    7
    ' K6 Y# y5 r, K  T2 Y8, t% h* W" ~  @- s/ t+ @* B/ Y
    9# ]( k' u$ h* \0 G) k( U
    请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。6 C+ k, W6 R7 V; v
    + K8 Z* U& I$ K1 w
    Ex2: 钻石数据集! b, d8 P5 a, c: p# G
      现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:! R  u4 b$ k+ g& O1 I
      n* V/ V0 N* W  k1 M  }
    df = pd.read_csv('../data/diamonds.csv')
    9 t- {. c4 a8 ?# Pdf.head(3)
    % Q; c! j% T7 K( K* [" M: p- Z7 G' _3 |( @, L
    Out[76]:
    - S6 R( Y) A0 K   carat      cut    clarity  price4 n- a' z; {( `/ `7 u5 a4 A+ D
    0   0.23     Ideal     SI2     326
    : W5 G" k  C" d' X7 h. }' J1 h1   0.21    Premium    SI1     326
    , z1 K- |. K4 a; ~' n: V; O+ y2   0.23     Good      VS1     327" L9 Z+ Q2 \7 S- L( ?+ Q5 d, P+ F% O- Q
    1" k3 P" ^( V2 _& G
    24 e0 B) X8 g$ V2 i; U0 L
    3* @4 k0 O8 F8 \' T9 g
    4
    $ e3 V" d. q. x$ ]& N, [. @9 `5
    1 P/ G2 c8 f( D- A+ f$ V" E60 l* r7 _( g" Q  ~
    79 o$ A( [. I5 V: W0 Y6 Y; w/ c! U' T6 v
    8( Y5 f' H9 q7 w4 C- H
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    5 U8 K0 I0 j2 f; b5 F钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。, y7 Z, |: ~+ m# l) l* q! B# E
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    ) t4 R2 W( Z7 M9 z. N  ?  O- B6 {对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。( ?/ [" {/ G$ \5 D  I& R
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。! y8 K6 T/ P+ D8 T( G
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    ) x9 C( _6 p9 @+ |先看看数据结构:+ @6 S" C* ~# j; H0 ~5 V! B3 s
    7 \# x- @0 a1 P; v* F4 C
    df.info(): A2 d2 y% F7 X
    Data columns (total 4 columns):* t! t& A6 K0 D+ \7 L
    #   Column   Non-Null Count  Dtype  
    : T* b/ z# k; |% F) n---  ------   --------------  -----  ! n% d1 ^9 {" K" b
    0   carat    53940 non-null  float64
    # I- ]3 f& ^* \* @+ s0 h- S 1   cut      53940 non-null  object . l; V. l: H9 z
    2   clarity  53940 non-null  object
    3 t# z% T/ G% S: U 3   price    53940 non-null  int64  
    0 q6 p) t, X6 edtypes: float64(1), int64(1), object(2)
    4 [+ s& E+ Z  M5 m1 I" r' O1
    ! L8 u" D& F; W7 U5 p$ X22 A2 ?% v2 e. M% W6 s9 f+ r# c* ]
    3* q6 c5 T2 d- W6 u& G6 o  M
    4
    0 @1 k* A# G( w! y5
    + V$ n1 i) D. E# m6- o. V! Z  W4 v3 P5 e% H
    7
      Z! X1 r* M* _) j3 r  p' P8/ F1 w7 M/ w# h8 H" Z
    9+ l& G' T# e& _5 g; l
    比较两种操作的性能
    : ?9 d4 d/ Z( a3 t+ P; A$ f%time df.cut.unique()
    + e5 C; ^# X0 k. @7 }: H" D# {" x, M7 Z1 \% N/ `) K" }9 I* t" I5 p
    Wall time: 5.98 ms
    3 p, ~; A# |& u' r" W0 harray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
    / A" _: ^& B1 r1
    ! h/ O4 `. `; s& a& ^' N9 V2
    6 x4 {6 O, b9 z- h, @, F0 k1 M' K3 l/ F3
    % E  t- {% E* U6 {$ `4. v8 q+ y: m2 @" K9 f
    %time df.cut.astype('category').unique()
    4 H/ F. t( ]" W' a3 \3 C3 N: q
    6 [2 I9 [+ |0 q+ y$ MWall time: 8.01 ms  # 转换类型加统计类别,一共8ms
    & p4 i5 M% J( i' a& ?['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']9 G& G- n& x) J6 k+ {# E
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']' Y6 S6 A. o5 d( e# B- F. P) [5 q
    12 Y; X. V, e$ l8 B0 w* U
    2
    / j4 h% d* T5 K( g% W3; C& U" H+ n) O  r  S/ z
    4
    0 ?; y8 p+ k% k1 u5# s3 I) m3 q1 {& i
    df.cut=df.cut.astype('category')# ?+ V; z' Z( e: G5 G$ h1 I3 \* Y
    %time df.cut.unique() # 类别属性统计,2ms
    % h+ C- F0 t# e$ S8 ?
    + y" r9 Z4 Q) h+ r( [/ LWall time: 2 ms
    6 b$ t  V- J! }+ Z# M['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    ' W7 c' r. p: ?( z: Y0 jCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    4 n( n' _9 a1 o1( @/ |: U  `5 n  Q0 s3 O
    2/ o# X' I. L6 E4 r7 ~
    3+ z& Q  ~7 |; B, r2 t- D0 u
    4
    # {* X; G- h- W; l' z5
    , m; Y8 y0 k; Q3 J  l60 D2 A" e- {! Q5 k' _/ w
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    + a: T! a+ v; p* c1 X% d& f) Z0 F, Xls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
    # ]: `. `' h$ ^! y8 A1 ols_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    & E- W4 j. j2 }7 v1 y8 x) P# n, Mdf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换. ]( B) r) L6 G! A  s
    df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)5 \  v3 c5 k% ^+ T
    * \7 g. B3 U1 c; l1 E) R/ {) H  e) r
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)0 R$ p9 {8 x8 o: `: I
    6 O" L. g3 j; M1 c$ `" u  N
            carat         cut        clarity        price/ s; ~7 N8 M( o
    315        0.96        Ideal          I1        28017 X& c9 ~2 L- C9 ]/ e' _
    535        0.96        Ideal          I1        28263 g5 U& h2 X) a
    551        0.97        Ideal          I1        2830& b: ?6 k6 H* a& l, b
    1
    $ z1 f* @; C0 `' _2& S) m+ h9 T, g, B+ k* |
    38 [  x$ ]* w' a* U1 [* ?# j; J
    4) a) a5 l6 J) c6 p/ ^& E
    5  l) J  C* i, y3 v
    6
    0 O1 x; ^5 m5 X0 k9 o7% s8 Z4 _- L# q
    8
    3 L$ T4 K* J8 n/ W" u6 I9
    * n' K5 a+ }( X10
    2 {5 h; |$ w3 h  K/ M- u11  ?, }& r' z3 Z$ `$ n9 |4 O
    分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。! b& \3 ^- x/ B8 G
    # 第一种是将类别重命名为整数$ X, Q( W' I8 K8 t  h
    dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))" J1 K- A' n! s1 ]6 T
    dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
    & P, Z* g. o% h& Z' ~. Q" z; `1 r) }
    df.cut=df.cut.cat.rename_categories(dict1)! w! V3 O8 \* J" t; C1 r
    df.clarity=df.clarity.cat.rename_categories(dict2)
    5 n! |% Y& c& a5 odf.head(3)
      l% H( _0 `) _( V3 T9 P+ g
    4 q! |; H4 s& t( ~, V        carat        cut        clarity        price7 t  k4 Z5 x% Y' m6 ~
    0        0.23        0          6                326
    ; M- B% C# x& ~4 ^: r1        0.21        1          5                326
    % z4 X* n1 E( W: a; e" b2        0.23        3          3                327! v7 @# E; b3 r& I% w) ^+ e" ~
    1  m, G2 T) k' P- M1 d
    2
    & v1 i2 _: F6 a2 T9 s3 ]3 Y* R/ X3
      B/ O) N- P( O. w( f+ X5 l4, x: K6 f- B1 C7 D9 }6 Y
    5, {% c1 P* _" @- v
    6
    ! o! N2 ~0 G& {4 }) p7, n* B0 L8 U: ~! n3 U; Y! H9 M
    8( O) i/ u6 @& l
    9
    / l/ Y# p% v" g+ F  w10
    $ l! a/ \% O1 x) M1 F  @" L110 l+ E6 \" l) V. y7 X  `
    12
      L0 B7 s" L" V! ]# 第二种应该是报错object属性,然后直接进行替换% y: [# ]8 z6 b# @4 R/ H
    df = pd.read_csv('data/diamonds.csv')
    # M# [* }, ?4 P' }& [for i,j in enumerate(ls_cut[::-1]):
    0 l* K4 E5 M. E& `5 c: F    df.loc[df.cut==j,'cut']=i
    ! o2 D7 l& W/ z( _8 U) J$ M1 N2 {, g( h" P
    for k,l in enumerate(ls_clarity[::-1]):
    1 O+ I% \" h! x* c. U% Z; s/ \6 W    df.loc[df.clarity==l,'clarity']=k& g" W, j' ]1 G1 ]. W2 z5 M% Q
    df.head(3)8 p1 ]& Q2 V2 Y# ~
    3 \7 J/ m1 y( |/ z
            carat        cut        clarity        price
    / }3 ?# y& E4 N0        0.23        0          6                326
    0 O+ H, [2 _; k+ B) z) |& J( `+ z/ x1        0.21        1          5                326& j7 A* o* i0 |, a  W2 |. B4 u
    2        0.23        3          3                327: \0 q# R- c+ f% j5 @) l
    1
    * D7 W4 g% p, ^# q/ M8 v( B: L2
    " e# g* m1 X, M; Z3
    9 J1 x* A: C$ n' G' J5 l, v) }4
    2 `0 |/ `( ^9 p. _0 x. r! ~55 a9 Y- {; B( N; }- [
    6
    2 C$ o2 {5 z, |7 P6 Z( e: J& c7
    * w0 u# _) i$ a9 w0 P  p# [8
    5 ^: d% U4 D1 R& \8 q9+ N4 o; J- l) [* K+ v; r) m
    10
    , P7 m/ K, N( i5 `. k' Y11
    * S+ C9 c$ n5 S; w/ i- M2 t12" U# `  j" |2 y0 m
    13# N( V" ^3 H' a
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。8 Q4 S5 t1 a$ I
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
    8 J1 l5 t' L  g% ?( j; \9 _avg=df.price/df.carat
    " [6 v7 d7 f/ P% K7 L% u( b
    ' s5 w" R( \7 Y3 Q! O* R7 bdf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    ( F" f5 f! L7 q" E" z2 Z$ U                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    ; R) R, I2 `8 o, ~( r& i0 n
    ! W0 m' Z& G* ?df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],( a0 y# U, z/ a9 T( o! u
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    % ]4 [5 S: o0 w  A  `; u0 ~( \$ ldf.head()0 }4 s& }6 D6 O7 s
    , d! A) L$ E& t/ j* u4 ?1 G9 p
            carat        cut         clarity        price        price_quantile        price_list0 j) z" a, d( {0 y  I1 l3 U
    0        0.23        0                6                326                        Very Low                Low
    2 ^) J5 A2 w+ l: U% B1        0.21        1                5                326                        Very Low                Low
    : L- \2 C. \& v0 |4 B( N- F2        0.23        3                3                327                        Very Low                Low  P- L" D* L; Y3 k& |
    3        0.29        1                4                334                        Very Low                Low
    9 m$ \8 ?4 _- |  n- M4        0.31        3                6                335                        Very Low                Low                                       / _1 ?8 F5 v: C( I2 c- ?
    & n- v2 B# ?1 W2 l; L! |: i* z
    1: m; p/ f5 ~2 Y! N$ e3 y  j
    2( D8 t% ]$ \$ h% Z
    3
    ! j) @% W# ~# O& N! z+ |4
    3 c5 U  k" P# J5
    . K: F* m4 \9 d' w; e6 z! }6
    ; s" r# k* H5 a- I7
    7 {) A+ r  q+ {0 K, W3 u8
    9 X# n' j' t* c/ [5 d1 S' V- p9
    & F% ?, C3 n* y+ m. r/ }! |10
    3 V0 c, Q$ C* w; ]$ s2 }* X11# [7 l$ w2 u% x7 B/ W3 S" j
    12
    ) k; j) w, g2 Q9 e13
    2 e3 v) L9 z% s* O$ Z14
    0 \9 _' b. |) S( s15- u  V4 D7 T! e" U+ Y2 q4 X* W
    16
    : e( l$ M' [5 [; O5 b0 ?分割点分别是:5 o3 F! l- T  `4 O% m4 Y

    - ]3 m* E4 Y/ n  Darray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])+ m% ~0 P" U  |/ ?0 y
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])+ O) d6 k3 I, c! ]: O+ C3 d& a
    15 f: G( v5 m* J
    2% Z" L; {, A0 B" V: i: T& E+ \
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。1 C" e2 V' b; b$ J5 n
    df['price_list'].cat.categories # 原先设定的类别数
    ; ^+ F4 \. p/ A+ {Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')7 C' b" k, R- _+ W! A6 d1 X& V5 w
    7 H) p9 o) f( E* h6 M0 W( }
    df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别
    $ l$ B' G! i: D+ s6 [  dIndex(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    % q' U) n* J+ N7 b1
    % \' N1 ^1 i+ m8 Y2. l' l% `: P9 f
    31 m' h3 p" e# U$ X( \4 G% h
    4
    $ ?1 i" }1 G; f1 k- c5 g5
    + m- }. m* l1 b' X) Savg.sort_values() # 可见首尾区间确实是没有的5 m) M& H( m, j3 ?: h( t
    31962     1051.162791
    ! G- N; ~2 g+ _2 h6 T( E( U& `( F15        1078.125000
    8 \+ N* K, u" E" ]. }4         1080.645161/ w9 L+ }* O  B( ~$ H$ x" i! w7 B7 `
    28285     1109.090909
    - F9 |4 w$ n  W* t13        1109.677419, H6 N5 ?& o$ A8 \0 H
                 ...     ; H$ o+ P/ M9 A6 `5 [
    26998    16764.7058824 [( j; T# K6 T2 M" Y1 U( c
    27457    16928.971963% F/ K! W) H" \( l
    27226    17077.6699037 q! \* s! ]/ c& {& L4 e
    27530    17083.1775709 J- C3 F  B/ e* L
    27635    17828.8461547 I8 W) n2 Q+ b! Y! [3 \
    1' ~4 t+ ~/ c$ n1 ~7 o8 T5 U
    2
    0 ]& [: E! d' M% c& P  e3 N3
    8 Z6 \% u3 A7 C5 |5 R$ |+ M5 M+ [4
    8 k3 K( r8 V4 ~) V" G5
    # X; k% `1 g4 z( w$ M  Z+ `; E& f6
    . ]/ v9 z7 c, z, e% l( h6 B5 h7
    # a/ D2 D0 ~, ?! Z' _6 [# }88 d6 K& h8 _; u( [" {+ Z, S0 g
    9
    ! j+ q! |+ Y2 j' Y+ k+ q' ]( H10
      S0 k3 |* L; X; P( T! W11
    & V3 Z9 H) s0 b' ^! I6 R  }7 P/ L127 L0 b/ \8 ?1 W; f% s5 b+ j2 o
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    9 N% W, ~5 M! p! S# 分割时区间不能有命名,否则字符串传入错误。/ K( ^' R+ ]" F. W& V) G- H* X
    id_interval=pd.IntervalIndex(& l- ^( Z; O6 f+ C6 s$ {: N
        pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]2 Z! [, B, d' l7 E& \$ q
                                )
    ' s& f7 {/ W+ h! b0 i" F" o% C  l+ ~) iid_interval.left
    ' m: K( ?' L6 p) K1 @id_interval.right
    1 [1 l/ G  |7 D6 v6 L1 k% F4 ^id_interval.length                           
    6 H3 _+ p) w, `1 F  L1
    3 V4 s, @1 n+ s* S( N7 n1 b' M5 o- S2
    5 `3 m  p9 {+ i) |35 G' c+ A% K( I4 o: b& R- _
    41 o& l% @9 x9 J# d0 V
    5
      N, `2 ~" p5 }6" L; B2 @+ x+ M7 C: L# C
    7
    4 L  z# ?7 }# f( H# y, c$ w第十章 时序数据$ [- L' C% J% r
    import numpy as np
    * r( Z: ~2 o# H  Z0 c: Gimport pandas as pd+ p" |/ @" P4 t* `0 _) H( A
    1
    ( }, Z- V) R8 ?2 J2( q0 X7 ?: r# }7 X

    0 i" l  w2 }" \* B* J! L4 f4 S4 c$ w; Q6 u
    10.1 时序中的基本对象9 m8 G2 H* u* B$ S2 l
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
    & @! Y5 h9 l( c
    + @/ T% T8 p6 i. h; T会出现时间戳(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的简写。. a* V9 ?# j4 I7 L

    : [  s, e3 j7 [. G会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    # ^) R* N$ J. s8 b! n
    / Y/ ^( E) s' H会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
    # r* K" e" s7 k6 L6 Q5 G2 e3 F: I2 y% r5 p$ ]
    会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    ; X2 A, u, j3 N3 z3 `8 g1 o: L, S: \
    ) S7 M3 i# w& {! J5 P# `7 ~  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:& b2 K2 t+ D$ S: l5 j5 k+ u5 T
    ) u% a9 M8 v/ F6 c2 L) c8 z
    概念        单元素类型        数组类型        pandas数据类型7 v. {, Q  Q7 ?) [# P1 z/ y' B9 I
    Date times        Timestamp        DatetimeIndex        datetime64[ns]& `6 h2 y6 T, p; n& Q9 A
    Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    8 X7 ~# r6 X& |' b# N0 Z* bTime spans        Period        PeriodIndex        period[freq]4 e: F8 A, j* m; A# U1 C
    Date offsets        DateOffset        None        None% M+ L& _  V' ~( W6 ]* f6 {* A
      由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。" x0 s: L2 l0 x4 m

    % I: s" }- W) X5 n: C9 b10.2 时间戳
    9 s7 f* Y- [" b10.2.1 Timestamp的构造与属性
    # c% u2 }0 H0 L单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:& c6 S9 d( ~% |2 R4 u3 F
    ) H) R5 a8 s& N: S! V4 n) X
    ts = pd.Timestamp('2020/1/1'); Y8 O; F5 A3 l4 J1 O

    / s6 ^; @( O( L5 d. xts8 `8 t- V3 W" q+ ~' m. F
    Out[4]: Timestamp('2020-01-01 00:00:00')# [( a; A4 C# ~! s

    3 j/ ~' w5 {( }2 K# P9 J) dts = pd.Timestamp('2020-1-1 08:10:30')
    . a2 L) k! G. z8 `( M9 d4 B+ Z
    % F& }. u9 l0 ?7 k. p1 s# V2 f9 U8 h4 Xts
    # [& e( |6 O4 J7 _Out[6]: Timestamp('2020-01-01 08:10:30')+ n4 `0 ^5 ?. c- w4 F$ Z3 @  B5 N! c
    1* G! _: Q+ X. N+ P$ x
    2! g% r7 s7 j, T  m* K, f6 S8 G2 H
    30 m4 C, c# J( w2 {) c: ?7 C
    4
    1 m2 ^8 c2 v  U$ i5
    4 c& k, t/ |. n) h62 Z' L) X6 j* Y$ x. o. t! o  T
    7
    4 P$ i8 V6 o( H; a8$ M" Q0 I8 X3 k2 k3 o* `/ _
    90 I; O1 ]! f3 x) f  d* p2 `
    通过year, month, day, hour, min, second可以获取具体的数值:
    ( y7 u$ T3 k; B) d. f; ^8 ~# L
    2 `+ P; t) P! S- X: {, @ts.year
    2 Y4 L6 h" J/ J% H( W+ J" tOut[7]: 2020
    - b0 V" K+ @8 X# ^" n4 A6 i8 B$ t! p3 B
    ts.month' }3 o8 _; Z- R" Y( n
    Out[8]: 1) A; y+ V$ v! _3 Z. _$ L% ^/ e
      Y6 C5 C0 k6 r7 h0 ?- U* z
    ts.day" q* o, @. h% \0 t
    Out[9]: 1, U8 }  {7 E* n$ h# b
    8 y6 H7 c( K5 l/ w# `7 H; u* r
    ts.hour
    4 ~. D# r4 v% q8 Y, }& v) gOut[10]: 8( a% x: l6 u) g
    ; n! _! [+ Q+ D( e( }, K
    ts.minute
    ; f1 r' `# _5 b8 O4 f  ?8 FOut[11]: 10
    - l* i. O9 O& g* P, r, [( ^. i6 k! |! i
    ts.second
    * E0 F) W* a+ Y4 E5 ZOut[12]: 30
    9 e3 O' v% ?: h8 S2 i8 ^2 d+ Q& J$ L7 a% \0 A( j! N, D+ o' l
    1, j0 B$ _7 Y5 p5 F# d
    2! o! q" f% D4 i3 {5 T3 a
    3- W4 v# z- ]9 B/ k/ ?1 R# d
    4
    : ~: m: R) e+ }+ _% i& y: o/ A, [2 R5; x' {( s: O5 f4 N+ p$ u/ Z  K7 b
    6
    2 J8 K$ |# i6 e+ U7
    3 M' m8 E2 s9 b. D1 R3 j8( t  L" b- l. Y; g
    9
    # Q3 \/ \- J* K( Q% [# t4 O10- @: Q3 e6 {9 a; ?: O) B
    11
    + o( p7 L7 i  C& d( Z12
    0 ^1 c) {  v+ \# T139 E' J  {! _5 O) I3 r
    14" _' Z* L$ e) ?! u. k) R
    15
    . z# E7 U: n% i8 @16
    ) G1 \! x0 F1 V: q# Z17
    9 q( O1 }! [4 K* Z+ ~+ M# 获取当前时间- u* q# k, ?3 h
    now=pd.Timestamp.now()
    + \8 n: j! ]. h" ^: A1 G( s& Q1' b% F; b) E( A
    2+ X/ c2 S" l! f6 Y" `5 k
    在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    3 \  c1 z7 i, h* x7 pT 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)2 X# d" j3 x; I, W9 \
    TimeRange=
    + {" `; E# l. Y- J3 o10
    1 I- {5 s; O2 C- v! @95 a' W, f; H- h9 T% _% j7 I
    ×60×60×24×365& g0 x& e( ?2 H, H9 O* i- {
    2
    " ~! j/ s# q: H3 p. h' z! o641 s& ~2 j! s6 d0 K0 \6 A4 H

    - P5 H( x9 y7 c* Q; q& O
    ' U$ X; ~  R- _ ≈585(Years)
    ( r$ Q9 {# @% Z1 L$ X! h( N* f6 Q% n1 R# \1 b6 [" d2 ~  V0 ^, ^' S
    通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:. l6 W( Z6 z5 ]: K
    & e: }" ]0 N) w8 C4 B  o9 _
    pd.Timestamp.max& i* |9 f7 b4 J; f: a7 G1 K9 y
    Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
    & d+ o2 }8 \- m( Y5 r) h# N
    & R4 E5 f- g1 q. X, H3 Gpd.Timestamp.min
    4 o+ g. a: Q6 AOut[14]: Timestamp('1677-09-21 00:12:43.145225')
    ' ]; N9 b- n7 u' w1 j% `, C5 F
    5 S! _( K1 d0 Dpd.Timestamp.max.year - pd.Timestamp.min.year: [/ t5 M* I0 x/ K
    Out[15]: 585
    # g- F1 Z2 H* ?8 v0 L1  j1 X% w) Y% l' ~5 ^5 K# C
    2: s" v8 u+ T5 E5 U: v' P
    35 k; \) A% C7 x
    4
    2 w1 d: L+ Q6 S7 o& U9 p50 J" x3 S( a: A
    6
    " E( c5 W) \6 B8 E) h  _# j3 |7, I, x" u  S' T% E
    8
    4 ?: s# v2 s+ @% u# S: U10.2.2 Datetime序列的生成
    8 W% Z: m1 Z5 B3 v% m% }% a5 ppandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,& g, W2 X' L( t3 i( M
                                      exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)# W3 s) `7 j5 o0 V
    1
    + I# O9 x% v7 f% r. r4 Y8 r. }$ u2
    2 \. R5 |4 q6 }( _pandas.to_datetime将arg转换为日期时间。
    2 A9 }9 a; D+ v6 v+ X
    2 g0 M) w1 c& O8 f) Farg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
      Y) j! a* u- X2 ]) V3 X+ Ierrors:
    , V, t& b% C9 n  [- ‘raise’:默认值,无效解析将引发异常3 p% n/ J* `$ T- d9 t( r
    - ‘raise’:无效解析将返回输入
    + M# h8 R; @" v5 e  Y6 B2 I- ‘coerce’:无效解析将被设置为NaT* W6 Q; l! m: A: X' h3 ^
    dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
    , N; z  `5 L# E% K/ @yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
    8 v/ w  A# J5 _! ]7 Yutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
    9 `$ @, A7 p' n: ]. I' n6 Kformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。0 w1 c" g' v( r% P7 Z" h( T! Q
    unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
    & Y) D9 U. U5 I1 x1 N. M2 Kto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
    / R9 z4 e+ G/ F; \; Dpd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])1 N1 q" D: G4 ?9 ]& z" S

    : M  d6 B1 R6 `: {. t5 IDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
    9 ?6 V# r& }; u: ^; {9 F8 c, g1
    / j3 ]; j0 f$ s4 p2
    ' d$ c& x& V9 ?' f+ g  [3
    : V& p( i) D( i3 ^在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:5 J4 `4 p. P( D# \4 w4 t1 ]" ~5 U. C7 a

    1 J% o% z5 w& z* }temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')- H7 J8 L9 ?( e
    temp* R! M, `( T! \' `  {6 c( n) Z8 n$ O

    / O6 h. }7 E# x; [% h- BDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    ' g/ U9 ?$ D. g2 I# _" I9 s6 {: G11 M7 y" o; F& \9 v- F- ~& m
    2! m+ v: v8 V: {3 A# v
    3
    ' I) E8 M9 X/ M4) Q2 T+ g! {! X+ s" f4 `
      注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:% F+ O& Y8 I& x3 l) y* m/ t/ H

    2 Z6 i$ k- f- y# `; c4 Vpd.Series(temp).head()5 s7 \9 K9 X$ \) u0 T4 B

    $ H) r; {# C* e: Q* J# A0   2020-01-01
    / @' Q0 U3 u2 B+ c1   2020-01-03# J& n! L  p9 [% ?4 ?2 L- K
    dtype: datetime64[ns]
    0 C7 t2 b+ H& i. q% j1
    4 i3 }* e  i/ t& s2* l. }; \! a; b9 U1 j& G; D
    36 B/ ]6 K  [0 E' ?: |) j
    4
    8 `/ l' Y* I; H6 P5
    ' A% i9 z! H# T9 l* t下面的序列本身就是Series,所以不需要再转化。) K# G- @* b+ K2 m. [

    2 M( i3 {+ |! ^# X6 |df = pd.read_csv('../data/learn_pandas.csv')* E2 h  W& _7 d1 U. d1 X
    s = pd.to_datetime(df.Test_Date): s, L! w) a' v$ p- b9 y
    s.head()
    1 P7 V2 ~! k$ M
    " g+ `' j6 F; a' s6 M0 J$ D0   2019-10-05
    ; @3 G7 S: d$ p1 O6 g) S1   2019-09-04/ q. a. `' S  O
    2   2019-09-12
    ' M4 ?$ D& @( Y3   2020-01-03, O+ v9 N1 M4 ~. U$ G
    4   2019-11-06
    ! d0 e6 m9 [6 ~+ F5 i0 A, D8 F& e, TName: Test_Date, dtype: datetime64[ns]
    ) i$ X9 m. q; n/ Y& X; |2 p( M11 F( L. w8 N2 G; R
    2
    & }* F8 {3 ?/ Q& P2 v; x+ j7 u/ T3
    ) b7 w. [! B" j. d/ Y, U9 }4
    , g+ w9 q$ ?0 o) u5
    2 `3 H  D# i% {$ W6 Y61 O5 s5 Y% h8 J5 V, Q  O4 u
    73 V& }9 j% I" X
    8
    & A; E# h# T' s+ C* q# O9' F" K7 ^0 q# x# E7 t" N
    10; r) P7 D5 f3 E8 B; U' u
    把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
    - ^* z) D" d# S; Odf_date_cols = pd.DataFrame({'year': [2020, 2020],) W9 D1 P+ {# m3 c! N
                                 'month': [1, 1],
    1 |4 V# j2 Y7 K4 u8 `1 q% C                             'day': [1, 2],0 n) g! S0 T1 i  g  k  i% Z
                                 'hour': [10, 20],
    ( O$ _% k5 X. Z9 c4 U$ K                             'minute': [30, 50],* f& N3 s0 W! M5 p* A! Z+ D& |
                                 'second': [20, 40]})) M4 O# @) \. ]4 p
    pd.to_datetime(df_date_cols)
    ( i4 }& E  N- g: F8 Y
    $ C( q4 X# R$ e  ^; ]0   2020-01-01 10:30:20
    1 a: w* b+ h$ I6 i1 O1   2020-01-02 20:50:40: f5 q# k/ {5 t  L% X. e
    dtype: datetime64[ns]' k* H/ o. K1 k! M# `. M
    1( k+ |+ D, I& q% ~. l; U8 _
    2
    7 `- ~8 q. f8 s' n1 h; q7 B3
    " N4 B. ^! F1 Z* E4* D- m" i9 K: G5 Z6 `) u; h
    5
    $ ^- c+ D7 t- N0 \6$ j/ n- t' a# v* d3 @% R
    7" m* @, A' Z9 F3 a8 A
    8
    ) B) q& F1 F/ b: b6 _* |9% [3 R* B8 m# i; c$ E/ A: \3 c* A
    108 e! e9 M- H, d$ B" u) g
    11
    % I* t/ q+ o; x: l! ?date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    ; `/ j' w6 t, p6 {. ypd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    9 B* e' M" `1 P3 @( N, QOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    ! F/ n8 u, [9 Z/ b/ ]% }2 C/ W. m0 G1 K. V, `
    pd.date_range('2020-1-1','2020-2-28', freq='10D'), c; a7 g) T& Q: t( \& W
    Out[26]:
      |+ y  Q8 e) C. p- K/ C0 cDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',5 h% z9 m- v. P
                   '2020-02-10', '2020-02-20'],6 ]; v% O2 e$ y/ q
                  dtype='datetime64[ns]', freq='10D')
    - H5 U+ ^) I) T: d9 ?2 E, m3 K
    6 |2 M5 i! z) t' y5 p. [. q$ V- v+ bpd.date_range('2020-1-1',  _7 |; C; y" B1 s% W/ e
                  '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天! a4 r, ]; Z, C/ [

    ! k" J% z1 l' k- m. ~- mOut[27]:   s- w( [$ c5 O/ _/ G& V
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
    1 [% z3 x4 q1 J9 ]5 f4 b               '2020-01-24 04:48:00', '2020-02-04 19:12:00',
    % |, Q/ ^0 S6 ~# v) K- G* d- E               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],5 }, Z# Z8 R8 R/ S
                  dtype='datetime64[ns]', freq=None)
    5 L+ w, |2 K+ V5 V* ~# ^( V- z1 O9 R; A; w7 a# r7 F
    14 B: Y; D  Z, x
    2
    7 u) M2 d- J! [& A4 X* M/ k3# O) Y$ P# `* I/ f  T. N* ?; `) |* d  M
    4: [4 W, B5 B! T0 c- l6 c
    5
    0 E6 N# v" W) v8 J" m/ {8 r9 q61 \- ]- r4 y. R+ ?4 P: i% W" Z
    7# w" @+ d: X: I. C
    8
    9 ~6 C% }/ k7 j- G9
    + z' Y7 o! E* F/ {+ Z$ m102 `+ P3 m0 X6 T
    11) R# A7 f. H  F$ n# d4 m
    12* t! y. `4 @/ _. r% i
    13& B9 [7 M( z& f7 q# @8 t1 ~
    14+ D2 j/ T; P$ ^+ b6 o' y( K6 c
    15
    & [3 Z( b& V/ F& e6 c4 A3 C166 x" M6 m3 d8 v1 n4 ]9 b
    17
    , B$ `7 [8 P8 ]! I" n) a这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。! R7 U7 P, a+ d  X1 V
    . \+ s! F# {/ K
    【练一练】9 D/ N% Y& h# \9 `4 y: D* b
    Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
    2 Q2 Y+ g0 M' h8 y& C% w$ y. L1 p0 U( b, A: T  a
    ls=['2020-01-01','2020-02-20']% `0 s7 P- V# r2 M: ~0 h
    def dates(ls,n):6 S+ D: C; V7 {. ^  w& ?
        min=pd.Timestamp(ls[0]).value/10**9' Z* |, y' h: B, d  }2 ?
        max=pd.Timestamp(ls[1]).value/10**96 M$ J) R% g* c% o% H. G- o
        times=np.random.randint(min,max+1,n)9 J0 T4 p9 W  W6 h4 g  Q
        return  pd.to_datetime(times,unit='s')
    % C5 P  p0 u$ I# N: |$ o2 k7 H8 qdates(ls,10) 6 x: U* a& M8 {+ ?' ^

    - R& C- Z! w/ n" h4 r2 Z3 mDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
    & o! l; W) }2 y               '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    ( [/ w/ K0 a  t0 x               '2020-02-15 00:18:33', '2020-02-11 02:18:07',
    , o5 N" B1 E1 T6 ~5 G4 C- l               '2020-01-12 21:48:59', '2020-01-12 00:39:24',$ e: g9 i" E3 e
                   '2020-02-14 20:55:20', '2020-01-26 15:44:13'],/ v& Z. p3 [) ^9 b
                  dtype='datetime64[ns]', freq=None)
    + v4 l  b- q1 ?2 p$ V+ U, D4 V1
    9 a) S, ^, l+ ?3 @3 g' S: d7 N6 ?2" K5 d8 W+ M& W: F
    3
    & }' v8 ~" c: o7 ], f: P4
    ! B" J" C5 O+ A$ o( H: ^5) [+ C4 s1 |2 Q' v$ y
    6
    + p# a& c% @9 P8 _74 s9 [- h% \) P& x& n
    80 O, @; w" K2 G1 `8 n% Z7 E0 @1 i
    9
    , ?; Z2 a5 ~8 j0 T' Y) Z5 h10
    ' i6 J& D) i6 }+ t  g8 V1 ^3 c11
    9 i0 z7 I; [% F( v) [; s12) `, G& ^6 x; S0 x, N
    13
    7 J: K3 l, x% j7 z1 n; U14) W4 ~5 c  Q0 y- j9 x
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:$ ?, X6 r/ J5 R& D% H0 |
    s = pd.Series(np.random.rand(5),9 O: f: H4 y0 z! y1 Z% I
                index=pd.to_datetime([
    - u% u0 [; T5 u! @. F) l! T                '2020-1-%d'%i for i in range(1,10,2)]))
    7 L0 r. U0 h+ }% J; f. r, s. R+ R2 a
    5 F% n4 J$ D% x8 J( i0 _* M: h' A2 d& @* r" P5 q
    s.head()( E1 \7 v( A( ?# u- D7 h
    Out[29]:
    + j# T& T' I: v  E+ ^3 ]+ t) V2020-01-01    0.836578
    * v* O+ ~6 e% I6 R" ?6 t, b2020-01-03    0.6784192 E. y/ n. H; i
    2020-01-05    0.7118979 y& ]3 K: z; K+ j( w
    2020-01-07    0.487429; _" `* ]' |6 ]% \5 c* @
    2020-01-09    0.604705$ Y  l( z; ^4 o; c6 l
    dtype: float642 a: Q) z. ~, C; J

    ; G! z, ^. x, \% d1 K  K/ h5 ~' Bs.asfreq('D').head()# {- q2 T  O! L, D0 P8 {: d
    Out[30]: ' J8 Z/ Z$ U+ u  j' ^
    2020-01-01    0.836578
    9 r' I, m. j8 W' |2020-01-02         NaN- N+ O! i  l' ?7 d! K, I" z
    2020-01-03    0.6784194 v) p- a) m& b+ \
    2020-01-04         NaN! |5 z) R: o7 K' W- t6 j: j$ `( u/ ~
    2020-01-05    0.7118972 k4 u3 {$ }7 L: e3 ]' \( x
    Freq: D, dtype: float645 K% ~# G- q. R& F  l- j# o
    * O9 }/ ^9 {' Y, _& a3 K( R$ ?
    s.asfreq('12H').head()
    , p2 Y1 H' G' QOut[31]: 6 \3 e# b7 W+ \7 [
    2020-01-01 00:00:00    0.8365786 `- k# J6 L$ a0 {0 T3 h! \# [+ ^* b
    2020-01-01 12:00:00         NaN
    % p5 D6 S) _! b# q! T7 x3 f) Q( s2020-01-02 00:00:00         NaN
    ) Y/ B  A4 b2 \$ i( |3 j! O2020-01-02 12:00:00         NaN
    0 f. F! x( O9 v2 Z, a9 S5 s1 e* d2020-01-03 00:00:00    0.678419
    / W9 Z! @7 ^% {3 G8 sFreq: 12H, dtype: float64
    2 J* a* k2 o1 Y  {1 k; `$ ~1 e! G
    : o3 S4 {! s) H: ~5 D- |19 H3 Y  K+ U+ A  b
    2
    ( Y) z8 d: f8 Z( s30 Z/ \' r9 \. Y7 G) E* q. l
    4
    ( B8 ^4 Q2 F0 z( k8 Z. J5
    7 q& B( M/ J& Y0 f. y6  T! O. S- A% k) Z2 O% L
    7
    ( C. g* d1 S) ?! Y7 i+ p1 T8
    * P2 k3 w) {2 K$ m0 X6 @9
    5 p  Y+ n8 J# \10
    9 q* n! g& T6 r, L# r% h11* V7 t7 Z' d, \- \
    123 v( ^# O' [. q0 {: s
    13& c# b3 l! i8 T: J
    142 h5 ?5 T. R& R1 \; o
    15( `" n' W8 P  S+ h
    16$ X: O5 W1 m6 H- @9 e2 ?
    17( ?3 S% v5 L4 o! g3 n3 S
    18
    / k+ M8 s& r. p% C# \19
    6 h& E3 r& ^$ T; G) E7 t9 y; i1 W# z20
    & r* E: b7 u: p) o21
    * |$ N0 m1 w5 @- `1 T2 e) |22, [4 M9 V0 J: d# J" C! h
    23
    3 Y0 X  n- s/ t0 L) H; ~' ^% X24
    2 o6 F8 r' T* f2 H25
    # ~6 g( S0 H7 H' M* D26
    " u5 L3 {# L4 ~9 b* ?6 L270 `4 v/ {# p" s; E3 y; g
    28
    " |8 s& r" B1 @( V9 v291 J) k8 G' m7 @! V- v. i" c! i
    30& a" n, D3 M4 }+ H
    31" b6 M0 F0 d* u
    【NOTE】datetime64[ns] 序列的极值与均值( C% }7 y" P- y# m" g
      前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
    $ o* u, ^0 N/ r* r- Y
    ( ]3 Y. I7 T4 z% l& p10.2.3 dt对象, N: y$ x! ^; |
      如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。) ~( n: K& W. e* f% Z& ^, l( I
    6 {0 D3 l9 O* R4 b6 Q$ x' z) N% x
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。; e! d2 \4 o4 m1 M, f
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D')), F% A7 J2 v. c

    ) |# F& }' R; d7 N2 C* ]% o9 c: Rs.dt.date, i5 J* b7 ~# s2 R# n: B$ P
    Out[33]: 3 |/ B3 I3 a) R$ v& X
    0    2020-01-01
    % d# @2 l9 F$ `6 @  P1    2020-01-02
    5 m; a, F, [8 n& {% ~% J2    2020-01-03
    0 b, }% o4 |4 [% }( ydtype: object5 j7 K. ^# F) [; {

    6 ]7 ^% ?5 I# k8 k; J( o# @s.dt.time
    3 y9 L* \( \% [$ {* r% e6 m$ kOut[34]:
    . a/ G- n" U6 P7 `4 M0    00:00:002 }/ X2 O' S; X+ |
    1    00:00:00
    , z, S- h' {; l3 s. w8 U6 r2    00:00:00
    & q, O  Q/ i' a: p1 Gdtype: object+ ]5 m% P+ l8 O. D
    . x/ ^- A. D2 A! i
    s.dt.day
    ) _+ x8 H; A7 l* JOut[35]:
    ' B9 b* `8 k& ~/ E0    1* l) ?4 m) h& e% P) I' s$ F+ G
    1    2
    8 n* x! _" }( m+ }# h2    31 l& I; A* k2 J
    dtype: int64
    * w$ P% O6 a7 O! {6 _: m1 {5 L  ^$ p  P5 R' U9 A6 ]$ U8 s
    s.dt.daysinmonth+ v9 k# s3 n2 H9 A& E7 P& p' A4 l
    Out[36]: + ]/ W+ [" u# v) x
    0    31
    / ^1 ~1 B9 I! D5 s& d1 b) E: X1    31
    2 J8 k2 e$ G9 L0 P' P2    314 E+ z3 [# s( P, d, Z
    dtype: int64
    " O5 Q5 f6 w( n% x* c" n+ c8 z" T3 u3 r
    1
    + a+ a( y4 T  Q; W+ R, @2
    & x, L% z0 E: Y3 l3! x: b3 K1 t! N1 M' a: y$ S
    4
    9 t+ r  l: j+ B7 G0 [+ R% h, ~5
    7 }6 Q$ T) z: c4 Y, X6: l) U  d  m; o9 q- V# Y& x
    75 p# _/ {8 W1 Z3 q. u
    8
    7 b' Q  H; q. M2 G0 P6 `9; m3 Y0 i$ T6 G7 n* B# S* w5 e% f
    10
    & w$ d0 C; S: i3 r3 l4 S115 N( _/ l* p3 U* H( k& _1 ^
    12
    2 Y2 G) l; o5 a: ^139 j  f- g  x- h6 Q
    14/ T; i( Q3 P2 [! s- a
    15
    ! }# J0 p2 B& u* Z4 O" `16
    % R. A, L2 O" E( I1 _. k9 y- e173 L! Y% D  U) C; y9 i
    185 n) D6 i$ v4 Q4 ~
    19
    - `6 ^7 x/ }, v9 I: s2 _  U20
    * ~/ ?6 \* B: |* P8 x4 Q21
    & c& _4 b3 o5 x, F+ }0 N$ [& X226 G3 C, A/ h) {% r
    23
    6 M! n$ r0 @  J6 j2 v5 P* L1 H5 W; Q% u24
    * U6 f+ x( z0 _25
    + [5 b% q9 b& }7 s26
    $ S7 _( C% h7 g. H1 T27
    4 \4 O4 s4 r. L0 b0 W286 c/ O; u+ o2 d4 f& u: H
    292 n5 q6 i. n" K" }& Y8 o1 h
      在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
    ! T9 g9 e' h  b5 z7 G: _% k+ y' j" Y7 Y% g$ P$ N
    s.dt.dayofweek
    ; T% ^0 j4 F, lOut[37]:
    + d4 |. x, @* b- x; S' h0    2
    : T4 K' U8 J/ b8 E1    3
    ! G( w. {# a2 b( z2    4
    ! P3 Y0 H) a8 j9 ~9 ^4 Gdtype: int64
      m0 L0 v! ~  T: E& ~8 ]( ?( M6 |
    6 r! b9 |+ o. |; h( W% H& Ns.dt.month_name(), H/ ^8 h* q$ w9 |8 h
    Out[38]: . _. y0 F; {6 |4 N) A+ |" ?
    0    January& S( E( v. u% [% i- e+ O
    1    January
    1 }" ^9 z3 _$ `3 e# G2    January5 s$ k- R1 }, M$ b  [4 c
    dtype: object. d* g: A" n: ?. e
    7 n) |+ T7 C. B! X. C, E: p2 Y
    s.dt.day_name()
    % c6 ~+ h* W8 Y5 tOut[39]: ' {& U& ?3 l$ p
    0    Wednesday
    ) G4 S9 Y& F8 s: B5 T- N' d4 Q6 K1     Thursday
    % M  o( \3 y! J! g% {1 `* J" l2       Friday
    2 ]/ t' p# @9 U% U7 wdtype: object
    . X# n$ v$ {( [* Y. j
    " }# d: ^5 d5 Z, U; |$ \1
    , a5 J9 n) W/ g$ t- o/ d- g3 V2 @& G2# M6 F) w. z. w2 h
    31 D8 [. J, ~, e% e; u9 Y
    4, e7 C( z; Z2 X! E
    5( S7 C4 i1 I. W0 U$ m: }( }
    6
    + Y. F/ a/ O3 `. F. M74 [$ b" O2 Q1 h" e
    8! {& T; Z9 M3 }
    9
    ) i4 l& f. V# U) {# n+ x109 b/ \, K0 ~1 m6 W  X5 I  V/ Y$ B
    11& Z2 b! O, {8 W6 v- b+ K
    12
    9 x' T, s! p- z  A  z$ B13
    , _4 [2 L: V0 C' V% I14
    * F. ?; p. o; r& g0 \* M) O15
    / d8 Z! X! s. d0 n. o167 v$ ^1 T! l% f4 f
    17" V/ l3 O5 q, E/ ?2 X  |5 u: \
    18% L6 q. Z& n: v! e# S
    19
    0 ]5 R" u7 ~) d" G; T20
    1 ~! H* i- Z4 l: ?2 t第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
    & x' G, |7 t! u. e  ]$ vs.dt.is_year_start # 还可选 is_quarter/month_start
    4 }/ c- a7 [6 U5 dOut[40]:
    . V0 R: f5 M' n0     True
    . Y( A& m" M+ T2 t1    False5 e2 p3 s) S' f
    2    False
    : ~, e' q1 N- a- ]dtype: bool( R5 D! H: g  D0 D% I8 K
    ' y0 D: y4 `# C: d
    s.dt.is_year_end # 还可选 is_quarter/month_end
      f+ G# z' G6 J& G/ F  W) WOut[41]:
    ' I8 [, y4 q5 L& F. Y) w  o0    False
    5 G5 N" f. q' e1 R1    False
    ' L# K; k4 L% B" R- D& X6 G2    False
    ) A5 O# T0 `- g" g! `) Y1 R7 j: Z0 ydtype: bool5 h- f. w7 a+ @8 \# K# f6 S( R
    1
    + g9 a- ?3 I! U2% g0 v" \6 \0 q
    3
    4 q) }6 c5 U- g; }41 g0 n& Y4 Y/ V) y9 X& z
    5& u: x9 S' ?1 C/ o' `5 k
    6& }9 H, C. f1 n0 I! P" f1 F
    7+ S& b2 r4 n/ F) T7 o
    8
    # H: F% U1 Y: i# [9) j; @3 ]" K' F' |+ j- _0 v
    10
    & h+ I/ U' ~# {+ [9 o% x6 Y1 U9 [  D11
    ; |, `( g6 x2 l12, \! T( t- b' h
    131 W5 o  F9 A# G, s$ V6 h
    第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
    $ Y% d1 [3 V6 o( Ns = pd.Series(pd.date_range('2020-1-1 20:35:00',; w2 y  T! j: ~6 K% l
                                '2020-1-1 22:35:00',+ `! M: b) R% @2 s( D# P; g6 r6 x
                                freq='45min'))1 Y3 S' C2 [- ?1 c% V# L: d) x9 D
    * G- d1 [8 k0 j1 {4 L* K7 s

    - B6 t1 g0 M( W/ `' cs
    / z9 O  U" D7 r. M$ FOut[43]:
    # f4 f2 p7 v2 F" @8 p0   2020-01-01 20:35:00' }* T& u. B  F1 u& @
    1   2020-01-01 21:20:00
    3 z' h/ E  ^8 T* @- A7 {& y& [2   2020-01-01 22:05:00
    # H- k$ o% {: Y" F# C7 I% Qdtype: datetime64[ns]6 k) L; W$ b8 h- L" E' E

    4 K/ Y7 G% F5 f! Vs.dt.round('1H')
    1 ^2 R5 Q9 t# w7 ?Out[44]:
    % V* d# b, l( T; {1 e0   2020-01-01 21:00:00% x# \! ]$ V5 e; A  i4 F; K
    1   2020-01-01 21:00:006 n' n# V6 R9 N1 d) H: y* U/ R& ?
    2   2020-01-01 22:00:009 f: a( [. Z4 A& @1 R7 A
    dtype: datetime64[ns]
    , L; m0 U, L9 O2 q. G! @& e
    , l/ j6 i0 W; as.dt.ceil('1H')3 I4 h& s8 [0 x( r5 _+ F5 c- |8 `
    Out[45]: & r) W3 T; z: |' U
    0   2020-01-01 21:00:00
    % ~6 b1 C. b7 l) f' b1   2020-01-01 22:00:00. w* N/ ^9 B0 i3 D+ G
    2   2020-01-01 23:00:006 v& c" @, s" A. @* C1 [* y( G
    dtype: datetime64[ns]
    & w6 _" B9 D' r" t# c+ o7 D
    9 _; |. Y% Z/ Ns.dt.floor('1H')& ?# N. s* B! g7 R
    Out[46]:
    * Z5 W  X/ u' \# K1 Q$ R0   2020-01-01 20:00:00
    ' l2 m" v/ k5 l8 g) |/ A1   2020-01-01 21:00:003 j. h7 _$ T3 f3 p- ?2 D2 C! Y- ^* g5 [
    2   2020-01-01 22:00:00% L- c& @* I- E% c* K) P4 w; s! J
    dtype: datetime64[ns]2 d8 b% ^3 G, O- ]( ?# b
    ; f5 ?- ~$ f! a5 h
    1
    # [' d* }: T) X4 ]! C2 M2
    : j9 r; O$ P# _  T3
    : u& N, I& G2 V4# |, O0 c+ i2 y4 I9 y. R- [0 U1 d
    5
    % M- w! N; h. s% j. ~9 i6
    : }2 P7 G+ Z$ b; i. U7
    . ]8 P4 T- p) j: B) f( z8: s* t# h1 P; A2 S0 f& R& K
    9
    - C: a; d2 I. {6 O3 P4 L2 J10+ P) @7 y  D5 s& Z# P2 k
    11- f, x8 @; R8 K8 W, A" I% J2 X
    12
    7 d& Y* I: x# V9 C13& k( |3 e. U( c  A! }
    14- m4 B  i( o/ s/ c, P+ t$ ~
    15
    5 ~# G% B9 b: U2 _$ g16
    : i- h1 y3 A: @17
    ) {) p5 c' N3 f18# N' I* e5 ~4 N  d7 s/ M; X/ c0 q, K
    19
    8 W( V% e  \9 y  h; B5 ]20
    ) g) U( F7 o3 F$ \0 h5 C. E21/ H# w! k& ]9 |1 t8 b% C1 |5 b
    222 d" O/ ~" F. |+ j8 X$ O6 n2 g
    232 f5 X0 B# e. D& v, ]1 ]7 I2 m
    24
    - S5 {+ N7 _2 I: ?- g: h25
    2 L9 ~; C- ?- I! K* i, u; P26
    " H  q) i. @) k7 X27
    ' G5 c& V9 `# N" G4 d28/ U3 R7 n8 X" H% s
    293 a: A* V- s1 D
    30
    8 ^+ @, [: E4 [# ]0 u) u$ U& j31
    " o8 }9 l) L* e* _' ^. {4 T% L. G32' Y% b; N- `9 n
    10.2.4 时间戳的切片与索引; B2 A! W  Z, u; Y( _
      一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
    . Y6 @) f: L7 M- X: U9 O. }( q5 K: k" o) C
    利用dt对象和布尔条件联合使用' W9 s% Q4 |2 F
    利用切片,后者常用于连续时间戳。6 C  G5 T3 T1 y1 R/ G
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
    5 T8 w/ w: g" ~idx = pd.Series(s.index).dt: I0 E9 @$ E. n  k5 J
    s.head()
    # N  i! `8 R  e- l# G2 s1 j2 N. @( G1 I$ L3 m+ G
    2020-01-01    0- k' o1 `5 [: Q$ O2 i9 x9 Z
    2020-01-02    1/ d( Y3 ^# e' `0 T0 H1 d
    2020-01-03    1
    - L0 w3 Z9 W0 w2020-01-04    05 f6 y8 f2 W; X+ z3 J
    2020-01-05    00 v) Y/ ?+ |( S4 F' v; v
    Freq: D, dtype: int32
    ) ~- D( V- G0 Z5 j* F) S1
    * _7 |  {+ \' I8 R2 \9 l7 k28 x. z, J; b# a* @- y3 Y9 W7 S
    34 U' ^( V9 {' L
    4
    ! [# Q9 ^. B6 p# P6 a- z! Q- F5
    ) X: C0 H' S' D# H% g1 D6! z' Z$ ?8 B! m
    7
    , Z; J6 F  ~& d- [7 d+ H8
    + l: f. H- s- p* y3 L+ ?% k+ ~# L4 B9
    8 _2 K  l% ]" c# P" W( w4 W$ t! F10
    - @( @3 B+ {2 y  p1 K- Q0 eExample1:每月的第一天或者最后一天
    : \. T. u3 H2 C7 |# M2 y* q! W) x& R' t$ V
    s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
    3 I: }+ X; h4 d) ~0 a" S7 @# A2 SOut[50]:
    % Y) A- U; r8 M* F: [2020-01-01    1
    ( R/ O( [6 z6 i& Z% }) W3 |1 z2020-01-31    0$ J, D$ ^$ O/ A) M
    2020-02-01    1" y$ w4 B. ~4 [( S7 k4 A* i- W
    2020-02-29    1' P  f  U$ E: r6 i) k1 r/ d
    2020-03-01    08 ~% `" L1 K9 {
    dtype: int32
    " J! l& k9 O8 c+ ?+ g% n# g1  X$ @; v- m' \4 m
    2
    7 i+ j# @: \0 \9 S3
    ( r- i- K0 G2 s( r4  e' P9 I  l1 \& F2 G5 ?/ L, r6 A
    5
    : D& J* ?3 {2 H* p1 q& h! k" z69 a$ {/ W. l0 H
    7. ]7 d% r7 V: A. x0 b* m, z$ u
    82 f  P+ t5 ~: n( ]) S
    Example2:双休日
    0 s1 I' {8 k9 Q; o9 d6 J4 E' p6 _+ E" J5 q5 N  E
    s[idx.dayofweek.isin([5,6]).values].head()
    / A/ M1 m* Z: p! [( `" F$ yOut[51]: # S8 Z$ g. w& J' D& @2 Q
    2020-01-04    1
    / n) t$ K& c% X4 z; W- T3 I2020-01-05    0. T0 S$ B# O4 C7 h$ i' J# u
    2020-01-11    0( b. u' \2 W4 |6 f6 p
    2020-01-12    1
    6 x& ?' |8 S- I) L' [2020-01-18    1
    * j1 s: L8 C# @2 I0 o) ~2 Cdtype: int32+ b8 N. S+ f, c& b
    1( C2 {2 @+ q' n" D# ]# v! b' R' }
    22 e5 F2 O! L, f- ^" t- U
    36 h7 P5 H* ]* K$ j# U2 G7 d) i
    40 u( [7 J0 P2 H( s* U) A
    5
      e: R  ?% B3 U+ b/ u$ i+ N7 n6! V; x( |7 f8 J/ L1 m
    70 @; g9 z- J/ R4 |" w
    8
    * p. M$ u4 b( y( Z5 |  [- eExample3:取出单日值
    8 H% w' p; x. n& K
    # p7 ^6 W, R! @0 Es['2020-01-01']
    ( `, d! _9 p$ L( B$ [7 A! WOut[52]: 1
    : M( c; W) E2 ]( q" W. F. k# {$ T6 U% c4 N5 T' b
    s['20200101'] # 自动转换标准格式1 l& R" I  _+ ^
    Out[53]: 1
    # P4 Q" \4 G8 S- T+ e1: A# x9 l7 r, }5 x  N/ `& z
    2
    7 h7 i& x6 [# A* W. f35 J/ n3 O* E, O$ s% L  _* F
    4. A# _  M) b- n
    5
    . P2 d6 g" h* XExample4:取出七月
      i. @1 |' K+ W4 R, [" C( r+ L7 N" G& G- c
    s['2020-07'].head()+ U6 ~, P7 K# G+ B* i
    Out[54]:
    + r# W! S( F4 o( X) y0 l2020-07-01    06 g7 _* t. u, P/ b7 O
    2020-07-02    1
    2 K  B9 o0 [( _4 R) o2 B3 K2020-07-03    01 r0 C/ A9 q' M& K
    2020-07-04    0
    - g3 X, a# y9 b) ~, I2020-07-05    0
    & e  H6 N% t. J7 E% I) |. {7 dFreq: D, dtype: int32
    . K: H; E8 n1 R# B+ n5 m1 {" R1
    . S% A7 U0 C6 g. i/ R23 \/ X+ t! v8 {# Q
    3
    4 u9 P, E# R1 x+ g. f) n' a41 C% u+ t, U7 ^, e1 Q  @" D& B- ^
    54 B: i1 H# b9 I7 W! n
    6
    % J& a# @( s1 Y; Z  N: a7 C73 U  ]3 {! d: j6 X, l% Q) E  `
    8
    5 w5 _1 I) A/ W7 q/ E# t$ F" XExample5:取出5月初至7月15日
    8 q& P$ M# t) Y4 i( @" ^+ J( _3 C+ y0 B# h- o! s, f* B
    s['2020-05':'2020-7-15'].head()
    ! x% X6 V( [1 R/ y+ T; _Out[55]: 7 \& U" h! N% s# K7 v7 W8 @
    2020-05-01    0& }$ O( O- o% w, A1 d: m
    2020-05-02    1
    + B4 \4 I/ [  R# s$ _2020-05-03    0& k- m; H6 o# z( p
    2020-05-04    1
    8 X, u1 R3 C3 H  w: Z* s2020-05-05    1
    ; n) S" I& e  l- kFreq: D, dtype: int32
    2 k3 L$ }0 p) u; e' M  R  d7 D1 ]1 }3 w% o  I; @
    s['2020-05':'2020-7-15'].tail()5 t7 u7 ]: G: q7 @) ~0 ], Z
    Out[56]:
    2 A8 }0 b( f$ J) C8 ]) c2020-07-11    0; J& O# d" K/ O! N  Z" k
    2020-07-12    08 Q; u6 U$ E! R0 l- f8 W
    2020-07-13    1
    : o: b# m) z9 H1 y. ]3 J* j1 P2020-07-14    00 N" y7 W3 N5 t7 ]" |
    2020-07-15    1
    2 g1 Y5 O( y6 z( b0 j9 U$ mFreq: D, dtype: int32& i( \/ X# v8 m) b) W" f$ ~6 H

    3 t  P9 z4 b7 E6 ]$ ?, B1 L1$ Z2 Y+ v& o2 \7 `; M$ n* F+ d
    2
    " E) q5 E( L3 m1 Z3
    1 j6 q  s/ A- d5 x$ T0 m" v0 f41 x3 n) p1 ?$ E
    5
    $ O! ]  {1 Y6 v0 M+ z6, T( t% \: \) L/ G. o8 @4 F
    7
    2 ?/ o4 n% a- P8
    ' }# @# G! J, |4 P/ x3 Y9
    - F7 x  w* @+ v! ^9 x$ P- s10
    ) _0 g, `3 H" i" N5 F2 y11% a0 \7 x8 W$ A' z4 a6 `9 f! m6 s
    126 K" y/ x3 C( a
    13( e  |* k0 q7 s) {' A
    14
    7 j+ `2 n  C. w/ L$ S7 J+ Q) G8 ~0 ~( h15
    6 N! N0 i6 o3 p( I9 d' G' @& C* M% f16) t9 u; D" W! _7 l3 F8 R' N3 C: J2 j
    170 ^' n$ s. h8 C* H; C7 k# k3 B
    10.3 时间差0 e- [2 H1 s: H" u
    10.3.1 Timedelta的生成
    ! X+ X9 v% ?. @pandas.Timedelta(value=<object object>, unit=None, **kwargs)8 j9 L& Q8 l5 w! H
      unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。4 T' u4 p+ h, F+ Z& V
      可能的值有:" b/ o0 T2 c, N' E; t
    ) A5 u/ D5 j9 [) I% K4 ~' i
    ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’4 Z* R# J$ Y% Z
    ‘days’ or ‘day’% Z, ?2 j6 o& g! X4 V! \
    ‘hours’, ‘hour’, ‘hr’, or ‘h’
    & {' X7 I4 w6 X3 N# A3 K‘minutes’, ‘minute’, ‘min’, or ‘m’
    # S2 X0 N/ ?1 y, V# |‘seconds’, ‘second’, or ‘sec’+ L1 J1 v' s# \" i$ s
    毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
    : u# e) {# ?1 O/ Y微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’3 |, ^  o% b- S( a% ]# ]. g5 n
    纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
    : L: k8 e- Z! w3 v时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:( k6 o* p: q  ~9 C1 E+ p' f8 W
    pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
    ! ], P4 n4 H3 U+ J) b8 P8 }# eOut[57]: Timedelta('1 days 00:25:00')
    ! X5 G7 _# V1 o$ L8 `2 O, a: ]" X) f* b
    pd.Timedelta(days=1, minutes=25) # 需要注意加s
    8 B+ [9 x4 _6 w$ f$ KOut[58]: Timedelta('1 days 00:25:00')5 C# A: x9 Z% ~: A* M

    # G' Y7 [6 N7 g( r6 Spd.Timedelta('1 days 25 minutes') # 字符串生成. |3 V  Z/ O2 L# \) H4 P
    Out[59]: Timedelta('1 days 00:25:00')9 ^; Q! S" @5 D5 x9 A+ B

    8 b3 I. D: u( A3 ^+ Apd.Timedelta(1, "d")* m2 n7 J9 P* O7 ]
    Out[58]: Timedelta('1 days 00:00:00')
    & |5 G5 l( H6 r7 I0 Y$ L4 o1; {9 c# |6 B- n4 c- ]! {
    29 D5 }4 \: O1 d/ X9 _( m
    31 u' [  b- i9 S/ H( S4 R, E: ^
    4
    ! U( G1 [( C/ k9 v, x- y2 X0 c9 r  c5; o6 d( d) y7 Q+ O. [; j, r
    6
    5 T- a' q# D* I( R  i( `5 ~/ B7 V5 N7. \% i/ E/ n, B" H% U) K. e
    8
    ; f( r: P+ P9 p* L& G4 E9
    . e8 g( \# w. Z$ E5 m( {100 }: T5 h, U: d0 e* H
    11
    2 `! N0 k9 `$ a生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    6 x( _1 t0 @7 G& [/ X8 p2 u) S8 W3 gs = pd.to_timedelta(df.Time_Record)9 S) g! m2 Q; C- w/ d1 N& ^

    $ i" G$ M7 m9 I9 b. l+ Y8 ds.head()2 {. s2 \4 o% M: w5 ~
    Out[61]:
    3 n$ g6 {3 G& C% @: J0   0 days 00:04:34. W' w. j7 e2 \3 P4 e! a
    1   0 days 00:04:20' v3 T6 y- ^  `; U  c) z! t+ R
    2   0 days 00:05:22/ u: g- X) e3 P: ?4 V' V" E) p
    3   0 days 00:04:08
    : V5 d0 L1 ^: u; p1 R4   0 days 00:05:22! E. N1 A. D. T5 ~" z4 l: h
    Name: Time_Record, dtype: timedelta64[ns]
    6 ~- s- j" L+ ?* K, X1
    0 l/ l0 b, m6 b) x0 i25 [4 {( L2 c# m& C
    3
    7 m8 V: k. _; y0 K" \2 I" E# d4
    4 Q+ r$ p1 _" ^' b6 p57 }& V; Y7 K0 D. a9 m9 D& J# R
    68 \& U; H- \2 ~: V
    7# H* A- I, z; P/ T/ _1 p6 c
    8
    / `0 A8 }8 o& }4 V7 |9* f" F$ z* X) t% M& Y8 P5 R, O$ n
    109 |1 ~% a! \9 Z
    与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
    + P! N5 c% e. `& Vpd.timedelta_range('0s', '1000s', freq='6min')9 v$ A5 F6 H. s  q8 ]  J$ q6 D5 v
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
    5 [+ C. h  Y4 p/ H8 C9 {( R$ `/ C  [* u# O- k2 G- L! D5 f3 E# S
    pd.timedelta_range('0s', '1000s', periods=3)
    0 v3 f) A7 y7 s; B& LOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    % Y  S# ^! J/ q. X: ^: W7 D# ]1/ @2 T  ^( H& F! _
    2
    6 D6 E; `5 Z( R( o3
    % v# n2 s. x1 Q4
    2 \6 A& r6 g2 ?& m( ]  H$ e% s50 G  \# L; Z/ \- Z) a' P$ P
    对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:2 m* i* k/ t2 }# M# N
    s.dt.seconds.head()' h* P* Y. r& a8 f9 Y4 B1 G4 n
    Out[64]:
    % r) [! m* Y8 K% P$ L7 ]. \$ E0    274
    % f  j, t% o$ @# O: I1    260
    5 D. f+ @# c7 R' @2    322
    % U, C: l5 _  s& t, F/ A4 I3    248+ S: Q# X  \. {4 ]* x7 q4 i
    4    322
      `2 X$ ~+ M! O- Q  pName: Time_Record, dtype: int64. J) `, ?0 Z# K  {) v* V" ]% ~
    1
    # U3 G  |9 f5 p8 [6 P2 z21 ?4 Z2 Y8 ]2 i6 s% v- h4 Z- D
    3
    6 A3 B6 V5 Z- Z: m( Z: T5 H+ v4
    2 Z) F0 l' c4 T+ S9 x0 e5
    $ p; _2 t. x0 q6
    * @" R& S: z; y6 }, d7
    ; t4 R1 E* L- _4 \85 L) m7 p+ H' [) e9 T  n
    如果不想对天数取余而直接对应秒数,可以使用total_seconds# _( j' T% t5 a

    - A% ]: g7 f1 J' \4 f3 T; ds.dt.total_seconds().head()
    1 K! n, ^; Y( yOut[65]: 2 l6 X; ]: f# }& B
    0    274.0. K% a, _/ V  z3 E
    1    260.0
    & K/ v5 ?7 F  v+ ~6 y2    322.0
    9 o, ~3 n9 o2 q1 Y7 G. t) ^6 Z3    248.0
    4 z' h4 q9 H( B* t5 m5 q4    322.0
    , h- U5 B& m, F1 KName: Time_Record, dtype: float64
    , ~3 u* L4 Q+ l! M% l1
    ( u% `/ T4 ?  Z7 T" O$ u2' G4 T  P* ?+ z" P1 z
    3
    5 ~2 e: {7 n  n5 [) p4
    + j" r% m! j, q5- T& x) @; D5 Y1 {6 k
    6
    ' W3 b0 h6 t  @) j7 b* ?+ y& \7/ b1 b3 |! }" P* D3 s8 @. J: m" }
    8
    ; _3 d9 h# s6 ?; Q2 ?2 w9 H与时间戳序列类似,取整函数也是可以在dt对象上使用的:
    3 p  C& R4 ~  \
    0 Y  R  n. H  Mpd.to_timedelta(df.Time_Record).dt.round('min').head()
    % P- l6 f  Z* @; v+ F2 E0 XOut[66]:
    ! \2 L" J$ g9 l* s/ m  E; g0   0 days 00:05:00
    " E7 t- H: L! a# L+ \1   0 days 00:04:00
    3 m: _1 q5 o! A8 L: W* F0 _2   0 days 00:05:00
    ! p, B' K9 K9 h4 F( \) m, @- |3   0 days 00:04:003 a9 Y- |; D. @$ ]1 M( M% W" T' I9 u
    4   0 days 00:05:00. a! n' W% q; l$ M8 h2 @
    Name: Time_Record, dtype: timedelta64[ns]
    / j9 t) m% K8 f1 b8 s# a- Q0 j( D1
    & k9 y* l. T& F2
    ! F$ C0 L4 n- M3
    , a7 A& H: `% ?: w4
    ! ]  i4 g  h: U. `5 ^5
    ; D6 d* {" g$ o1 R6 ^% w6
    8 F, Y- |& P# q# ^" R0 z7( R3 \6 I& D1 B$ {7 m* G0 g
    8! h4 y; a3 [& Q+ j6 e1 w
    10.2.2 Timedelta的运算5 k+ i2 F& C& @3 M( s
    单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
    + P# i  L" {+ S  H* wtd1 = pd.Timedelta(days=1)3 K! L7 g, `; s* d: Q
    td2 = pd.Timedelta(days=3)
    5 N. x* \) M  Y+ o! \ts = pd.Timestamp('20200101')
    $ r6 P! y* I8 ]: }5 C
    4 z' F* ~7 Y  \& B% J# gtd1 * 2
    5 V) a' q# c% ]& S5 [- oOut[70]: Timedelta('2 days 00:00:00')
    , A7 B8 O$ ^$ n9 S6 ^' t: v" Y. [- h* X. K) p0 T8 l
    td2 - td11 d4 k( d: Y# _
    Out[71]: Timedelta('2 days 00:00:00')% Y5 k8 [3 f/ O
    * ^: I* H! @  C1 w9 d' Z" K
    ts + td1& ]1 u7 ^; s" X( U3 h
    Out[72]: Timestamp('2020-01-02 00:00:00')
    . I4 N& _  F: G7 S, B
    ; S. a" n# r, r* Bts - td1, ~" S' c5 I% Q$ I: n; s! b
    Out[73]: Timestamp('2019-12-31 00:00:00')
    $ m5 E* i. a, Q& Y4 x19 @  X/ c: l3 @- G+ D
    2+ l; F) F& v! z( v; h) q
    3$ C& |5 G0 e, V3 k) r
    49 {: n, _! _% w* N
    5
    - ^5 H1 u& A. u6 T, U62 A% P3 r( ]+ ^& E
    7
    6 @; v( ?/ t  C0 ?  X, q, A0 S$ K8
    " R4 V' {5 x% H" N/ B8 L/ A6 {9: m0 @, J/ i) \  q2 \' |
    100 o3 Z2 i! W) y3 Q( ]% M8 f- b
    11
    # B* A8 C8 H" X7 B2 n12
    1 K) D' p: F" ^' l5 `! T13+ j; U$ s9 x/ K. y4 q: K
    14* p* E; j1 B$ [
    15
    % r- X9 d' J0 |  e时间差的序列的运算,和上面方法相同:$ p- m$ L) Z; m( {8 \$ U
    td1 = pd.timedelta_range(start='1 days', periods=5)5 g% b8 k& O& @! z6 L) P! b  h
    td2 = pd.timedelta_range(start='12 hours',3 h$ _7 s. H8 F
                             freq='2H',
    # T( i3 P- o2 [# N                         periods=5)  D4 Y5 \+ v) O5 U; b: \% Q
    ts = pd.date_range('20200101', '20200105')
    ; ]6 {4 B8 J5 C/ V3 Ltd1,td2,ts
    ) t6 N4 O0 t  u+ I* ^8 _6 a
    1 U+ W4 A/ v# H0 @9 s1 WTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
    2 H8 z0 {* V# _9 i5 H) JTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
    ! P" ^1 y' w" C) r, G6 ?                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
    5 S/ f" G0 M: X3 D' nDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',+ T# z$ l+ U7 l$ ]; N( V$ |
                   '2020-01-05'],* U' C8 [) i6 q
                  dtype='datetime64[ns]', freq='D')
    4 `4 w/ `( E' u1
    5 v9 q7 N) ]9 O" V' M2
    ( z" B2 C8 d3 m' p7 l3
    ' ~1 b2 v4 d, W4$ p3 C0 N* @9 q; A/ Y" U, k9 w
    5
    - g0 U2 v  [4 |6 @6
      A( W% t( j; X/ I; v7
    9 I6 I0 F/ H  [7 [3 Z- R3 Y8
    9 F- N$ q* e: Y; ~92 j$ e# L( N# S! m0 l
    10# Z  t! E1 Q( u1 W8 i
    11' z5 [  F# o! s& `
    12
    ) \' j; o2 O( ~0 t; x) Q! k: i- c13
    2 _4 s$ Y) W. W! B. I2 ?8 v2 N# Xtd1 * 5
    3 H0 o# h) D7 C5 q! ZOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')! U% W- c+ l- k; |3 S2 j

    ; e5 d* i+ y& [. b9 ltd1 * pd.Series(list(range(5))) # 逐个相乘5 x9 B8 C' e( F# Z1 R
    Out[78]: 3 ^/ r' u8 \, u0 `  [' T
    0    0 days; F) v0 G$ _# e& A1 |
    1    2 days1 j# h: ^' s& e( j
    2    6 days
    . y7 ]: b" t$ i: c3   12 days8 j1 ]9 r' e$ P9 Z: y/ i* x0 G: d
    4   20 days! n. \- r4 x, q& e* p# t3 @
    dtype: timedelta64[ns]5 U/ r0 U" ~  T( r0 u( a

    , Y6 M6 ^: b6 c/ ktd1 - td2" d3 Y3 X1 K' T0 r  f
    Out[79]: / n4 c5 h) y, m, l9 M
    TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
    * l& @; N2 u" I! m. H$ l                '3 days 06:00:00', '4 days 04:00:00'],  t- }: r+ ^0 h3 |3 m3 a
                   dtype='timedelta64[ns]', freq=None)
    7 d. ~* y# ]; ^0 H9 ?! w8 _/ z( n5 E# e
    td1 + pd.Timestamp('20200101')
    0 W5 G! F' }* L' r, T  G3 }$ {2 UOut[80]: / y1 X/ V: w) g. D) g
    DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',! t1 f' X0 j, _6 i7 ^9 k  s( Y- \
                   '2020-01-06'],dtype='datetime64[ns]', freq='D')
    . ?7 Z3 D! ]0 L9 h2 J/ K  F$ `
    ' Z9 G, F% ~! t' `7 Ftd1 + ts # 逐个相加
    * F. w9 x0 X, m. gOut[81]:
    . S& ^/ s! {. L: aDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',( ]4 F. A8 K4 w% f  l# x
                   '2020-01-10'],
    # W% k% n* f* k9 a+ m8 {7 Q; H              dtype='datetime64[ns]', freq=None)8 f6 t" h& O, p
    + O9 E& g. A. n* w
    1
    & Z4 a2 u; ]8 K2 J: d2# |# G+ ]6 [8 S0 @
    3# f+ K! ?/ i0 h; a
    48 }3 v. h) S: P8 `
    5% y" m* O5 x  ?7 g
    6- ^: @% `9 o: u7 E8 Y, [
    75 j0 f1 C- e/ v0 g6 j
    8
    ' D0 b( U- Q; p  V3 U9- f) T( D% D- [4 ~' b
    107 m; V$ y& r$ K7 i( p
    11
    2 z! m" c2 |* R: J1 Z0 d12
    : X5 e0 W3 E/ y3 w3 _+ u0 p( r13
    # r9 \8 X. `' B" }14( v; o2 _6 z& N9 o8 j- F
    15; u5 V/ t/ v: o% f# Z; z
    16# |( l1 O: T2 Y: e. @6 J
    17
    + i& B4 Y, Q9 G. D5 [* Q181 `+ C9 f" A) z& i  @
    19
    7 H  v0 d7 `- y5 L! |- }203 u8 z% L% Q# C/ u1 j
    21
    * F; e2 w9 @* f/ o22
    5 t1 y5 @; _" F1 a23
    : b0 B6 r& Z% w" ~: B: X24
    ) G4 x6 {* z: `1 d2 V25
    / r8 d: n9 c! s( G26
    ' e1 V6 ?9 P1 H' H" e: s27. ~) q1 S& r% x/ k4 w" G
    28/ L6 F" L2 u( s- D
    10.4 日期偏置
    ' }1 O7 p- A$ f: {$ M$ N( B10.4.1 Offset对象/ ~+ E5 K$ p5 [
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。+ K# A6 i/ t8 W
    1 h/ J8 v. p& I4 T# g& ?) n9 o
    DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:: {% T1 K+ V. [9 I3 W+ U. m

    9 y- g' D  E" h# f& bs.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
    - t9 M8 K2 _& T/ F0 [( Fs.kwds:{‘week’: 0, ‘weekday’: 0}
    7 k0 z8 u. B) P2 Os.wek/s.weekday:顾名思义
    / ^8 e4 ]; V* {7 j有14个方法,包括:
    5 ]' w: x- P$ L; \" z- N1 ?, N3 ?5 W! Z" v: H& J6 a* H
    DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    - ?+ S4 V# W* M+ C  |+ i; X+ mpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。% S& ~) e2 G, ^0 [
    ' h2 ]- l/ [; p. `
    有两个参数:4 V# J) Z6 R7 x1 K, S. T
    week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
    ; f6 X  i  q2 H% p1 w6 b! E8 Sweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一): O1 _! P# X! B+ r# w
    pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    ' {) n, [' F# j& i  I) Q+ k" _
    5 X1 _5 y! H  N% S3 D. H0 bpd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
    ) T$ r% p6 w7 y' f& l+ fOut[82]: Timestamp('2020-09-07 00:00:00')) S# I' e+ \1 _  I2 h

      r8 J9 L9 z* S/ t% L- Epd.Timestamp('20200907') + pd.offsets.BDay(30)
    9 `) C9 o$ y- X- P, Y2 `9 S, `6 a) dOut[83]: Timestamp('2020-10-19 00:00:00'), L7 u6 |+ F" d; _# Y
    1
    : ~, }2 T+ m( ]+ s5 s22 F( x. H% r& T4 ]5 K4 L- t# R
    3% t/ f: ^& C  K+ P2 m
    42 S* K4 x- c6 ^% z: g- \/ V
    5, s2 |- M& f2 x& ]5 D& z
      从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:# I, q- `6 U" L' R+ W
    9 q( ?# |6 x" U5 L& m
    pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)8 R  X4 T& x3 r! D
    Out[84]: Timestamp('2020-08-03 00:00:00')
    % l0 n8 a' U* G7 H; r! X9 I
    3 C# L; ?: T5 T5 |8 s) ?pd.Timestamp('20200907') - pd.offsets.BDay(30)7 `. L( v8 j: ?( {( V0 r
    Out[85]: Timestamp('2020-07-27 00:00:00')# j" R1 r. T6 v8 Z# @9 G: p
    6 G2 i$ L. f4 Q
    pd.Timestamp('20200907') + pd.offsets.MonthEnd()6 j$ |7 g9 Y6 ^0 z( Y) s4 c' T! s$ f( ]6 D
    Out[86]: Timestamp('2020-09-30 00:00:00')! |0 A4 V7 q; j4 ]' _
    18 L6 g8 d/ V+ U& c7 A* p
    2
    / ^( s* A8 n& ^* A4 y% @+ ~3
    3 j4 O- r9 R$ F+ V5 f2 {/ C% G$ c4
    - e9 c$ ^5 j: r5; I% c) f. `% R, T- G' d) v# {
    6$ j+ _( @8 [# `
    7
    3 B/ T0 A( u$ g" v8
    0 X; }8 J6 b( C# t/ |4 p  常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
    & Q4 z9 H+ ]1 y2 K  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:  [* B$ H+ }  R) e0 Q8 X) V7 o

    4 ^& l2 g! ~) |8 _my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    6 _: K2 M* L: F( X2 @% pdr = pd.date_range('20200108', '20200111')
    ) @  Q# h# B) @3 i8 H6 d1 e' ]/ C( Z3 W. {8 d
    dr.to_series().dt.dayofweek# s3 }8 j/ ]7 @, i, C6 R# M* i# m
    Out[89]: & W7 v$ q+ p5 G: l+ m, V% s% Q* u
    2020-01-08    2
    & k2 U* F8 k' S& I' O$ n8 S+ M9 |2020-01-09    35 j  E/ j1 s. P, V7 t8 f: N
    2020-01-10    4
    5 }2 }' n' C% r( I& m& Z5 P& k2020-01-11    59 w: P) h0 ], G$ S4 B
    Freq: D, dtype: int640 F7 ~" `2 r+ `! @( c) ?. D
    ) d- S& W) [0 _1 k" s
    [i + my_filter for i in dr]. _# I# W, o0 a% n, h
    Out[90]: 4 w4 T, `$ U; r) L# s& t8 M
    [Timestamp('2020-01-10 00:00:00'),! i& E) F* `5 _2 }0 w5 G
    Timestamp('2020-01-10 00:00:00')," D& h4 `- \( M. y# [
    Timestamp('2020-01-15 00:00:00'),, E* L2 I% E4 i
    Timestamp('2020-01-15 00:00:00')]
    6 \" z& [$ V$ e# W! V
    & S1 _) N, v& v) U" U9 u4 Y18 E" P( K9 W; `; J
    2% T) q1 ]% D" r: m1 |4 X
    35 t4 K# B5 P0 R9 r! [2 N
    4
    ! H+ v6 }: N8 ~; V5& Q! c" s6 a! O0 V3 @
    6
    % _! W( ]$ D5 t4 P( J7
    1 y6 Y" r* {' T* L( n! `, @0 z1 M8, C4 k0 u! i+ |% `0 Q
    9
    % I# U0 w- F* A& V106 G; S+ h8 Z$ _! U- [% G
    11
    6 H6 t7 T+ m$ J12) B1 P  X: h' E6 p$ n0 J7 a% }
    13
    $ m; n! }# s9 r1 b8 Y* U14
    0 j; W% d! `; j: @15
    / C' ?0 p0 P. v0 d16
    $ ]2 e: e7 Z+ z, u+ F170 g+ Y7 A8 D; u
      上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
    , N7 y) B+ i; [0 o$ z1 ~; @& ^) [$ m5 Y
    【CAUTION】不要使用部分Offset
    , n3 ^1 x1 k1 p在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。8 s; Q+ [9 @9 ?7 C. q
    7 k  |7 B- F* ~+ p* t2 R
    10.4.2 偏置字符串
    ! c! x( }5 o/ e4 i! X5 ~  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。6 m7 y9 o7 w, C) i* U

    + b% {) r" S: x' ^  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
    ; _1 w/ C3 I9 U2 h) u/ t. P# h! o! P7 i" l/ {9 B1 ]4 A: P
    pd.date_range('20200101','20200331', freq='MS') # 月初, F: E' X9 h. h; M; j
    Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    7 l% r; s2 ^9 ^; }6 F8 B
    7 h/ u3 L; h9 r& C7 hpd.date_range('20200101','20200331', freq='M') # 月末/ {9 V/ E" e) G) _' ^5 \6 E& V/ C
    Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    7 ]- F3 J) w2 G9 f9 y
    4 ?4 M8 M! Z! r& z+ X) u* cpd.date_range('20200101','20200110', freq='B') # 工作日
    & G- O$ {- x. f, f5 `' j- `Out[93]:
      h& n) P$ C& A$ ODatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',& k; R$ b8 m9 J/ `$ d+ d5 f
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    ) s3 U2 C; V0 A; G2 U& z              dtype='datetime64[ns]', freq='B')
    - ?1 O, ]$ G7 z7 |0 y& I" ^  t% A* T. C8 U+ T+ Y
    pd.date_range('20200101','20200201', freq='W-MON') # 周一
    # k  l$ p8 e3 m3 t( u$ ~Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')( G" T- }" k9 p2 C6 o, K
    - M; e4 p9 ]7 Z) ?
    pd.date_range('20200101','20200201',' J2 A  l0 {3 p. U0 E
                  freq='WOM-1MON') # 每月第一个周一
    & V% E5 }" @: V* S* O: T+ T- f! z
    Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')  p0 c' u# h2 Z8 W$ ^
    * f0 r0 H6 ^3 D8 q2 h% p. R
    11 y- i' ^  H1 Z
    2
    7 {. w7 ~4 G. e" y. H( L. q3
    9 f% X. A& v8 e  u& j+ y9 M4
    7 q+ Z* a% K( Z+ S1 g0 a" X, U5
    ) b) v+ m3 m, ?63 |# t9 c8 Y3 C
    76 A# l' X  v! O# Q# x3 e" y, e
    8
    / g4 N/ ~% G2 Z97 ]* |! ?* s: m5 _7 b. P
    10
    , l, v# d6 T* p) D8 @! S( G11
      W5 N7 S, g; M9 d3 p12
    3 m0 V" d' G6 Y  o2 G3 d% [7 f139 D8 `' P0 }0 [' `0 W6 h
    14
    / q! u1 X/ t% z5 |1 g+ j' P158 i0 z3 O$ @' a7 I5 J
    16
    8 X+ A) L  L7 K; F2 x, O* ^( R9 f; q17
    & F, m& l# c% A4 m18
    + q" o% c+ X6 U19& u: X1 Z5 f9 }" z! A. a- o
    上面的这些字符串,等价于使用如下的 Offset 对象:% ]" S; C: q4 v9 L6 b4 h

    $ O7 c' s& a6 I0 P# G( Zpd.date_range('20200101','20200331',
    * F& d( Y+ O' [0 ^, L; L              freq=pd.offsets.MonthBegin())
    & R  U7 ], o. y% P% X0 _& r: x7 a  v2 D9 [
    Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')/ E; I1 j+ H- u1 z9 P1 V

    # J5 h% }2 @# }. N+ ]+ Y& e: i% ?pd.date_range('20200101','20200331',* _3 J4 u3 s+ T$ f5 s) y
                  freq=pd.offsets.MonthEnd())( _9 n% r" B, Q1 F# {8 |3 M

    / j$ A7 w! s& f- m& H+ {Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')6 {- R" G4 t" w1 z, ^" W
    " D- c- j: f: N
    pd.date_range('20200101','20200110', freq=pd.offsets.BDay()); N; L9 |( B% P' J6 w1 T
    Out[98]: 0 S( z' L, u( }4 J; |- \+ H9 F- w
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',( U/ `: _1 \4 j! F! h7 l& i# t, u
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    6 X! \- s" T- j8 M6 x. ~! `/ V/ `              dtype='datetime64[ns]', freq='B')
    . d: A: N5 z! p8 W8 A& A7 k
    ; ~# l$ |/ u. Z3 F% P) Npd.date_range('20200101','20200201'," p! T- n& s& [$ p
                  freq=pd.offsets.CDay(weekmask='Mon'))
    9 k+ Z" @! c4 E9 Y. c8 Q. e+ ~0 H5 q: H+ W1 Y, H
    Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    % `: Q5 k1 b- Z% X4 o! S& A. C' e" h" l* N7 P& B5 N% T; e# w* D
    pd.date_range('20200101','20200201',' ]; L' C# j3 Z! r
                  freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
    8 M( X! W6 Z: Q1 \$ k0 w- i! m0 U" e5 ^  h$ S( R" y
    Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    * R. h- F/ A8 _1 i1 N
    " ?+ T% R# K% |" e0 f1
    $ f+ Q* y0 H. a$ {; o2
    + h/ y- w$ j( G8 D8 P3
    ! A2 o! R0 Y" j3 Y3 \4
    - a, C9 c/ H# v) T3 I5
    6 @6 ?/ R3 w5 _( X* b63 m$ @' |8 k; @6 v
    7* u! D4 l; M  R5 ~3 v+ ^5 a) B+ U% Y
    8  [, D- m0 ]; ~
    9
    $ O6 L$ H6 p# K2 K6 p( ?10
    ( M7 S( m% G- \3 w11; Z/ o5 i1 A" [3 Y$ C" ~$ h. ~
    12, v- Y$ x' d: T/ f( t" t
    13: ^( v+ b/ q1 N! S' r
    14
    9 Q* v) v# ]  L- A6 J8 T15# \, v- c$ [9 c6 K3 ^  F
    16& x/ x  j! W- k7 ?4 j- `
    17
    / Y0 r; Y  X0 e4 j18# Y* n; E% T$ u4 v, e: Y" U. m
    193 b& g+ w4 |% E; F
    20
    ) Q  E0 Y1 y3 R* k& Q+ R7 C. [21& ?9 x! B1 G1 [; K0 V7 @% s
    22
    2 |; P0 j8 M% a  C( b- n/ _23
    8 R4 m/ c" @2 ^0 B; }5 `& p( E24# Z7 i# ?$ w" @, d) l
    25
    / V+ ]& ?( u' k; x# w. o【CAUTION】关于时区问题的说明
    ; ^% m8 ^4 z8 w* u. W  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。4 w! _9 k4 X# u' |6 E) y! [0 v

    4 k- X: [; o  u& C- ^. F* C1 `10.5、时序中的滑窗与分组; b' @3 k# j( k( x6 u  s- b( s
    10.5.1 滑动窗口1 \2 T" c7 P' Z% U& _2 T
      所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:! m1 s) R1 d! `8 W; G6 H

    : h! i9 S1 B2 C" {import matplotlib.pyplot as plt) |1 V3 y; j8 H/ j. A% j) T4 @
    idx = pd.date_range('20200101', '20201231', freq='B')
    / i& Q9 t4 M2 s* Hnp.random.seed(2020); w" x& e4 \) K; K1 f% z0 K2 [4 p

    4 W" g! ^; w5 [, [" }data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
    1 u" a- A% u: o: N* cs = pd.Series(data,index=idx)& e0 \/ c6 K( _- h5 ]9 N
    s.head()& L- I3 k2 w  r* l) f+ j9 y
    Out[106]: ! M2 M% k& S, e, @: H. |& m
    2020-01-01   -1# J$ o+ r+ J/ i6 v% C
    2020-01-02   -25 |6 l2 r& C. G; |: H9 X0 Q5 w& l3 k
    2020-01-03   -16 w) Q/ h& s" D3 @; N/ l
    2020-01-06   -1- P0 u& j- E4 \; q, G0 u
    2020-01-07   -25 K- r4 ]# x: {
    Freq: B, dtype: int32& s$ k/ C. U3 J- m3 z) U. W& A
    r = s.rolling('30D')# rolling可以指定freq或者offset对象3 x- N+ q" J- S# ~0 G
    ( l! \0 s8 ?4 x$ d. E
    plt.plot(s) # 蓝色线7 \% w" }* _7 C. a3 h
    Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]7 E4 a0 C7 ?( c" V
    plt.title('BOLL LINES')
    # F# o# t$ K! F/ R" yOut[109]: Text(0.5, 1.0, 'BOLL LINES')" g: O8 w8 q/ z& ?' v% r

    : A( U: s/ V* \4 t: ~( E4 U4 d! A& ]% mplt.plot(r.mean()) #橙色线$ \" p- M% N: k  v3 }' U2 o
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
    5 h: ~2 A2 A: n. D& v: q: K
    * {4 n- v9 F6 e1 G: @plt.plot(r.mean()+r.std()*2) # 绿色线1 n8 T0 D- y2 M% y  s2 x% S
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>], u1 o1 I7 T& y* \9 p1 b

    9 K4 G, m6 _9 E! L+ Pplt.plot(r.mean()-r.std()*2) # 红色线
    7 k% a6 e0 f( q% V/ i# b, POut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
    # W, E6 T2 {' Z6 \% S
      h- {6 x7 g9 |1 a9 d6 [: o+ s( ]1+ m) @7 ^" S. u, ?/ v7 l# }
    2- m! w4 J2 m( b
    3
    $ q2 [1 U( k6 w4
    / w! u5 B+ b' u# r9 F! ^* Y5& K( Y& B+ J1 b& L! L3 \; C
    6
      L/ u1 T7 s( L7 o. ?6 p70 a6 j# E9 h0 m' G
    8# k! i+ V; q# x" ?( Y( q
    9$ l" e7 h3 `7 Q! ^( W
    10
    * @. m) f2 v1 r  j$ R118 Y! |) r7 j) D
    12
    ) F' e  T) f, f: L13# Z; l. W0 L& W4 f, k3 t
    14- K! f1 V6 [. u3 ~# s" r
    15
    $ L+ U* H" U6 p9 y- E7 x16
    ! w) R0 G, \/ Y  b17& ~6 [  _' [* _5 i3 p" r5 z5 ?9 N
    18: k5 L0 y/ Q( A1 V- a: C2 E: w% _4 w
    19
    ' X5 z* _' a3 P( A7 N* i20
    * q1 X6 p! \( H+ d215 b1 h$ t9 E" ^$ ^6 M. C7 @6 @# n
    22
      ], e3 Q. o) v' r1 y9 C4 w23
    , l& F! L, M9 ^/ j* H, J% ]6 e24  R" g! s0 M2 g5 l+ I% ?" F0 R3 m
    25
    + k2 [" h# u5 F+ J4 s9 S4 L4 _26- v5 K7 n% X: H; l: l
    279 c3 M, ?1 H' T6 c- v
    28
    5 Y  ]$ b0 o2 N& c29" m0 @; H1 Z" c' b- B+ [4 R

    4 Q9 q+ [" T" a# [   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
    8 }& |1 l# N8 n   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
      W5 y- Z1 {2 u9 `/ H
    3 `) @+ a% M! |1 Fselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]! D# @3 P, H  |- l( h9 `
    bday_sum=select_bday.rolling(7,min_periods=1).sum()
    2 n! D: J( R( v2 D& z! zresult=bday_sum.reindex().ffill()! H$ j) _' r/ u1 }! C' S
    result, k# v" m* k# W/ A
    ) l6 y$ x+ A' K. W3 p: n; ]8 S8 D; }
    2020-01-01     -1.05 j% d- X0 Q* {& g- H6 |$ v1 |
    2020-01-02     -3.0
    % N1 W8 C9 F' L/ G  z4 |2020-01-03     -4.0
    ( K% |/ t8 o7 [$ s6 b2020-01-06     -5.0
    3 X7 [, J3 q1 a5 J2020-01-07     -7.0
    - u0 [* R" l/ u% w/ d% W1 a              ...  4 H9 k1 Z+ U- x  h% I. ]; K  B
    2020-12-25    136.0. n1 V. t! r* p# }# H+ W
    2020-12-28    133.0
      Y  f( H6 x4 [2 E4 `4 ]- H7 |2020-12-29    131.0
    ( t9 S+ M* m- j4 `) L, h4 j. p% @2020-12-30    130.0( A3 m3 J+ Q  }$ K
    2020-12-31    128.09 S, P5 X  @3 m' E+ W3 {
    Freq: B, Length: 262, dtype: float64
    : _) e2 E: U: `
    8 V% X; i/ {9 u1) q, M9 u( q, D( t
    27 i+ S2 P, z* v. d. Y# @% P# ?6 b# B
    3
      M# W2 A( B4 ^5 Y5 o3 l4( S. q, d$ k" g& U5 Z* X6 @+ w
    5
    # W& U8 A$ }5 f6
    , C: B! Y. s8 i6 ?& p7& ~5 z3 I# _: `9 G9 R
    8' D3 p5 ~! E5 X% W8 ?; Z# h
    9
    & b4 f. O; A2 ?1 {: J  V. |10$ W9 o) |% f2 ]
    115 \0 G$ @! e- h2 j9 h
    12
    & p: V0 J/ c' w( ]& d139 R9 x1 L1 Z/ e. v
    148 l/ l* m5 a& G6 \1 e' D( U
    15
      u5 J. {  L9 y, \. X16
    - i. Z+ q6 y# W, s, L17* i* T# r) {5 z0 t- I
      shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
    % n+ V, n. J0 J# ?& q5 L4 Q6 i: e8 I
    8 g4 k5 K. h" w$ ?5 O9 Q+ V  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    0 g3 @( `) {4 P0 W8 v
    - h& e/ R7 m! f, Zs.shift(freq='50D').head()' }' L- b# c5 G2 t: s1 P
    Out[113]:
    ! B' }- p( m" G2 P' @8 K4 g1 x* ~2020-02-20   -1& d8 R0 _$ U9 h  @
    2020-02-21   -2
    ; }6 K) [; \2 H. r" N2020-02-22   -14 Z9 t. G/ t, R
    2020-02-25   -13 h& h# O) X% P" P
    2020-02-26   -2
    # G& w3 N0 A- C# ldtype: int329 y/ l/ W; j, ~
    1
    7 i" s1 q  g: Y2 ?. f% ^2
    . a3 j& W6 a. Y7 {3
    : E, n7 x2 O$ \0 H4
    2 x% R' f% I% T0 K3 F' l5
    . \* p0 Y' ~, k6
    . i: L7 E3 D: R5 u7
    2 c/ _. q% S5 ~3 g2 A( D82 c2 {) j) u  s9 V$ I  y: `
      另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    ' z. w, d1 y* p
    0 }  l4 m- b& @* @* jmy_series = pd.Series(s.index)7 k/ }. g6 h: [* @
    my_series.head()
    * O% N3 B( S6 l+ {' vOut[115]: % e+ s. B( V7 Q8 x
    0   2020-01-018 v2 {, a+ H! m
    1   2020-01-02: |$ V6 {; e/ G: l- G3 z
    2   2020-01-03
    ' P6 B* o5 G6 v4 ]1 F1 F3   2020-01-06
    8 `) ^2 G$ E5 J2 }! V; n5 G4   2020-01-07
    & @0 p0 L; i2 wdtype: datetime64[ns]
    ( F7 p7 C% W& f6 P$ h; B" u0 f% z: n+ E  d2 g, h2 c; L
    my_series.diff(1).head(). |! c1 x* [: H. Q+ Y. x' w
    Out[116]:
    / s! Z6 S) `: w. n0      NaT
    0 w, n% p. _, A) K- Q- b0 ^1   1 days! m9 U! ?) a2 ~: m% d
    2   1 days0 c# t8 j- [( J4 O' u' [
    3   3 days8 J; I9 m" Y. G9 I- g6 g: ?; {
    4   1 days
    2 j# `; d; P4 X( T4 Jdtype: timedelta64[ns]
    , @& e8 _/ Q2 Y, o3 ?7 c! e, L: m( o8 U/ ?) @
    15 K- O8 S2 m( K
    2
    5 p" ]3 S. l- x* A7 D3- \% N8 C" y5 a
    4
    : E1 \, v. A- E5 l- Q5
    $ }' ^# W. \' R# F  G( r% a6
    7 @1 s0 n5 a+ \0 Z* b1 c, K7
    ( v% p! V- e- M; a5 L$ t1 B9 Z- |8
    9 e8 ?8 u* X/ g5 v. j9
    - ?9 K2 D6 E& ?" N- }10
    0 q7 J2 V4 S4 F( [4 y$ d7 q11
    $ I5 `" `# E0 {, h129 y! ]/ g: r: C% j4 b
    137 C. O+ H, D; D8 n
    14& h9 y  z! }: ^  c2 F( H
    15& H: p3 m3 W, T/ z$ M* G  p8 v
    16
    0 r) k1 m! f/ H9 `. O' J3 q17
    , @  f8 M) y/ w9 W0 L" v18
    9 a; x6 M! d% F: I3 N- u5 x; S10.5.2 重采样
      V. f$ {. e8 M1 T' [  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)- ~' J! i8 O2 b
    常用参数有:
    $ ~( v' T  E: s8 o. s% h: B1 Q" t" h+ j( t& F
    rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象/ ?  U3 }8 K: o7 b# _6 A
    axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样6 W5 c5 t) L$ |% s
    closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    , ?# O! Z2 @$ Mlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。( [- J  i0 c' q+ V
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
    8 b1 V8 F, |9 _6 ]on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
    / d3 R# t3 B/ D- Plevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。" O4 O" y7 T  z) s1 C! Z
    origin参数有5种取值:  ^+ n5 r, _% @# H2 o
    ‘epoch’:从 1970-01-01开始算起# `9 {4 E; B5 r+ Y  t
    ‘start’:原点是时间序列的第一个值
    * m- h" Y6 W) d‘start_day’:默认值,表示原点是时间序列第一天的午夜。7 r0 b% X+ q6 t  \; n- d* _
    'end':原点是时间序列的最后一个值(1.3.0版本才有)( z4 W, Q2 o- I3 H
    ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
    4 M! f) y1 U' Z0 |3 foffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
    % c8 z  l9 U9 r  closed和计算有关,label和显示有关,closed才有开闭。4 N3 T! x" M0 @1 A
      label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。* C0 Y# }1 Q  k& p% u
    9 a4 l7 @+ w# F  ~
    重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:9 ?& t: s2 ^$ {6 z% Z
    s.resample('10D').mean().head()
    ; S5 R( P$ I& q9 C( TOut[117]: * L: T, i! P5 y6 U7 X! y
    2020-01-01   -2.000000
    % M; U4 l: i% F0 \( q2020-01-11   -3.166667
    7 v, y! m" y: G* x2 \, R2020-01-21   -3.625000
    ) X) L2 S$ S$ h; \' ]  k2020-01-31   -4.0000002 p% G7 e/ v' w6 X4 ~& E3 H1 H
    2020-02-10   -0.375000
    8 w) ?" z" v* U* u2 JFreq: 10D, dtype: float64
    ) B9 A3 C4 A* p5 v- ~, @/ L1# z; N- P7 u% C6 C- i: u" s( ~
    20 c. k5 R! Q& N  Y( |3 {
    3/ x* s  S( l9 s5 [0 v
    44 o; Q' Q7 f! \* X* i2 @
    5, L' K" f7 y' X8 X- J# t
    6
    * y. T2 M5 s3 {9 I7
    ) |" o- E7 |" @. D5 L& _8
    9 w4 D3 p, ^% \6 q) _' x可以通过apply方法自定义处理函数:3 v9 d8 h9 `- H4 J7 L5 K$ s& n& x
    s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
    . K9 p4 t6 i) t+ Q( h/ a) c4 |
    3 c; D5 |+ Q# cOut[118]: 9 u, u  W* N$ U. l8 I5 c. v2 x
    2020-01-01    3" c0 p+ A* D; i8 f6 D. j
    2020-01-11    4
    ' B+ M8 O$ t/ q2 y2020-01-21    41 E' ]1 o; t( L4 N7 ?
    2020-01-31    2" H: J; t1 v4 }; `. y
    2020-02-10    4
    0 i( G% {1 r/ X7 VFreq: 10D, dtype: int32
    3 p* x' w1 [. h( x6 K/ a0 |0 [2 p' h& P1
    7 o6 Z6 w5 R0 S: d8 ~) H7 _) Z2
    0 x& ^: `/ m4 Y9 ]3# f% h0 }" Z6 r* a8 ^: Q8 e
    4
    ' N" k1 r3 D6 O; e5
    5 z8 Q0 R! A9 r8 o' U/ }6
    0 e0 R( `9 N6 V/ e0 U( n& ?* H9 _7
    $ m; K4 j' v6 j. ?8
    % ]7 a9 s  @$ U5 d+ K% d, z98 q& `- v; C* E( O: x
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
    + u' u* E2 |2 ]2 j5 m/ o9 @
    ; c7 |; V" p- Fidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')4 M! G9 n: Y$ r5 v
    data = np.random.randint(-1,2,len(idx)).cumsum()
    . i: }' Q. C9 x4 L  f  }7 d& Ns = pd.Series(data,index=idx)2 A. \& n8 ~* j% ]
    s.head()
    * F; z* C  ^8 A
    1 J/ z9 `( E  y0 iOut[122]: & F6 Y6 {1 F  K. K
    2020-01-01 08:26:35   -1. ?5 |& c" e  Y
    2020-01-01 08:27:52   -1
    3 M1 q0 n2 I* H: r+ p9 J2020-01-01 08:29:09   -2# [9 b" p+ e, B$ W& K* {- x" E
    2020-01-01 08:30:26   -3& o- A( _4 H; K+ u" f
    2020-01-01 08:31:43   -40 {6 a, c7 s, G; v
    Freq: 77S, dtype: int326 O4 {9 R" m0 @0 m, N7 m
    13 G' y5 ^2 o& i/ h
    2
    % z  w, w2 q% m- b! A6 G& O6 G3 f0 D5 b3' x$ h' d; N" b0 n# b$ A; x
    4' D$ @2 Z, l% R% @+ i
    5; \4 l3 a" O: l" P* V7 S- |9 W, J
    6
    ) {- n" O! j4 M, d7
    : U) ?7 f' k# _% |/ A8
    % f9 Q$ A* {2 Z) f0 a4 R9
    ) n  p  z0 E+ J- M; t* P10
    9 C! Y0 ?( X9 J5 n11
    8 }1 c8 @8 h. q8 D$ @' r9 i120 v: J1 z" s9 J0 `+ I! ?
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:2 ^- u) p; u2 p

    " i9 R7 f- {4 o: S* \s.resample('7min').mean().head()1 r: T" K1 h2 ]' ~2 s9 x" }
    Out[123]: / s; v1 \8 q" t- U& S% V' D6 d$ b
    2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值/ V+ t. ~$ \5 f5 Q  S; e1 R* O
    2020-01-01 08:31:00   -2.600000
    0 Y6 w0 ?- b0 Y0 X7 ]+ E2020-01-01 08:38:00   -2.166667, T& N% N1 N" N% R) m+ I
    2020-01-01 08:45:00    0.200000
    % }: I4 T2 t1 R' `+ e2020-01-01 08:52:00    2.833333
    & @7 Z- q- Q. t% uFreq: 7T, dtype: float649 F5 y" ?/ d3 E; g! Z3 p
    1
    : a8 r! H7 ?$ L2  J( i6 n: y, ?4 y4 H' d0 s& M1 w- M! h
    3! L# M( d/ v  ?3 l# Q0 p
    4
    3 x) g$ C4 d  M9 X- y0 O" ?52 v2 X& `% J8 B
    6  x9 s0 l- [7 @% N. t& w# S
    7
    ) A5 _7 ]! A% ^1 ^% i8. z4 {$ r) m$ O" w; q
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
    ( C' ~: K& }" w
    ; @0 ^3 ?! a* `8 B3 |s.resample('7min', origin='start').mean().head()
    4 p2 r9 P/ D; K( yOut[124]:
    + [% z" j7 D- V, V3 o3 ]% @) Y2020-01-01 08:26:35   -2.333333
    % d+ k- @: g& H. ~0 Q2020-01-01 08:33:35   -2.400000
    9 }) ?, y, K3 A2 q2020-01-01 08:40:35   -1.333333
    8 e  r, {0 N: k% u2020-01-01 08:47:35    1.2000001 Y* n& \6 X! i9 j- @0 O" K6 Z
    2020-01-01 08:54:35    3.1666673 z# Y) x( H+ I* F1 H% x6 _
    Freq: 7T, dtype: float64
    & \* F+ m: ?9 e& `# f. v! Z# P+ {0 R& D1
    1 x8 h: J8 ]2 F5 H' s2) ^" W# r  h/ `
    30 m; |7 U+ j  H# G
    4
    4 Y( [) L# }# n2 b4 W) D( R9 e5
    $ \2 l, _/ l9 R0 y9 m1 S1 _. ^6
    ! i9 X8 t( `8 M1 @/ G/ ]7
    2 M$ C) a2 m  \7 Z% I, _8
    ) m; g2 n, y! B% r  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
    - r; ~* i& v4 F5 P* D/ R, T: C! h0 P* |$ C: i
    s = pd.Series(np.random.randint(2,size=366),  D$ Z+ h7 A& R+ `
                  index=pd.date_range('2020-01-01',
    . g! ?7 T0 g& L# U# I/ X( [                                  '2020-12-31'))  x3 {0 y, k6 {1 D6 f
    $ b3 D* P* f$ m

    * B! o( ]: ^# J( c) e2 z# X. P; n- zs.resample('M').mean().head()
    ' G6 ^- d* T8 \  J! c1 V# I" lOut[126]: & `+ O2 \/ y9 J
    2020-01-31    0.451613
    7 o% b7 I& l9 c7 X2020-02-29    0.448276
    5 p; @  g* k+ i7 {2 Q2020-03-31    0.5161290 u) v$ P1 ?$ Y1 ~% Q% n6 R
    2020-04-30    0.5666673 ?) T  O# v) n+ D) o
    2020-05-31    0.4516132 J# o: j  h! |2 \3 k  {
    Freq: M, dtype: float64- o6 R7 d: U  H% m% E- ^( u7 b

    . n4 R8 H! l% k7 \( ps.resample('MS').mean().head() # 结果一样,但索引是跟正常一样; a, i) L' q& }: d4 ]' s
    Out[127]: , H2 c/ x3 k, H) }  `' M. V
    2020-01-01    0.451613/ {+ G7 \+ h; s8 V- R
    2020-02-01    0.4482763 z' \7 f+ ]2 e' l* u
    2020-03-01    0.5161297 p+ U, n; l7 K* p
    2020-04-01    0.566667/ T5 E: D$ Y( Y
    2020-05-01    0.451613
    : t0 j$ Q. `8 w) x' ]( [, B  QFreq: MS, dtype: float646 ^7 {6 J0 j7 X' ^8 E' S& m0 N
    4 s4 _+ N/ U9 `% h
    1' k7 q* Q( C( `9 H
    2' {% `" `( t2 i# U
    3
    & E2 J' `" `, T# c) q& }. A+ L5 S42 u7 \6 G- z: ?1 u  [* G4 T, {
    5
    # A! ~1 S3 b, g0 e: Q6 ?2 \0 X! M5 c6
    ) B7 Z3 v- [* _6 W/ j& a7! ]( L' |% `; Y, E
    8
    0 Y/ c  u% d" Z; S4 L7 n7 Z4 ~9
    : v8 a; C" E6 A; Z10
    . {. A/ H& t3 k$ j/ t114 ?5 D( ~& a( h9 p! g' ?3 k6 \; `; W
    12$ e, C, f, X$ @8 ?! E- g- W+ X
    138 N! e& X2 v" T
    14
    4 e& B" ~0 H( L; I; {' ?15
    6 x( D4 w9 @+ K3 {$ J: O' i( r16
    $ {1 f+ c3 |' m# K2 Y8 W7 b17
    " A# H' b4 _7 s: x2 C  Q1 W7 @18
    1 f9 s# I3 s- e/ Z3 a  Z19; v' [; @- O. B, o
    20
    8 F9 G/ t3 c4 f% B" R21
    $ P% W3 D- Z! N22+ V# S4 g0 j; t# o
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:. D- o, \7 d0 [
    d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    # }. n# g" X4 r     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    # L& i" n2 z" A5 [1 u1 Tdf = pd.DataFrame(d)
    ! L3 A* O  w' i# R& B6 udf['week_starting'] = pd.date_range('01/01/2018',
    ! X& B+ r/ a; {/ g/ v" A  N3 [; n                                    periods=8,  V8 R. g! n) y# t# K
                                        freq='W')( t' H( m$ j; o' O; M! t
    df
    ( F3 @" _7 _  X! p   price  volume week_starting4 J7 Z+ l( N4 U) v6 w. R5 g3 ~1 ]
    0     10      50    2018-01-07
    ) c1 x# H; I5 I$ _1     11      60    2018-01-14
    9 f) W  w) C2 y  g" D4 [2      9      40    2018-01-21
    + K9 O, E9 Q8 C5 I% z3     13     100    2018-01-28
    # a) B. }3 q) j# o, f4     14      50    2018-02-04
    3 y# K+ c# B, x% c5 l, {$ s5     18     100    2018-02-11: G3 N, E1 r" v" a; u# [
    6     17      40    2018-02-18) i( m- u- o5 P! J
    7     19      50    2018-02-25
    ( P$ l1 U3 \. j3 n( xdf.resample('M', on='week_starting').mean()
    + F& L# W2 D% K& w" o' f               price  volume
    : N8 f: M4 F/ g2 V7 K. p: \" \week_starting
    & w- R3 @( x) N& h5 m4 x2018-01-31     10.75    62.5/ c* P7 V9 r* Z% W# W' G
    2018-02-28     17.00    60.0* g% G) \8 m9 s) j; w, d

    * z8 n! L* h# r/ a6 ?19 @* s. ]  e( ~
    2
    ) V0 u( X* N8 d( Q  H39 r9 T5 l3 E' l" r
    4
    7 i+ h% w0 W: ?1 L/ Q7 z, x) A# g5: [, M" D8 D3 P
    6
    7 N- u# i$ H6 ~. j0 l74 Z! `' A& Q3 i/ d* l# J
    8
    / {: y' A) `4 f( X9 P  C7 d9
    ; X4 T8 c+ v+ k( u' z10
    ( ~! s$ [( M! E" K11
    & W- e, w6 p; s8 J* F12
    " `9 ~, f( h/ v% G" u. d/ A9 ]2 a' C13
    * ^% m: ]) k0 ?3 R! N6 j3 Y14! i/ t6 ?; B& `$ }+ U
    15
    ! ]" F9 ~, v9 X1 \' f. _6 J16
    0 a- a7 O6 t3 L6 \+ `5 D17' j  @& V$ z/ [9 ~5 \9 }4 ?
    18* Y' ?& B6 f: s- o, D) N6 l/ {! X
    19
    9 V% y+ z4 U! [$ b  L8 e# S. m% R20
    9 L: c* ~8 g; F8 K$ n7 h. u21
    % ~4 Y7 M. `2 u' |# K: I对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。! {  m( v5 d; I' e( N# @, N
    days = pd.date_range('1/1/2000', periods=4, freq='D')4 R1 Y- \- w3 M# O: a
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],8 j6 T- N5 W$ d3 P
          'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    , K1 a, w$ q+ u$ Q9 p- R% T. tdf2 = pd.DataFrame(
    2 T& g6 B+ I7 P3 R! v* q, P7 R    d2,
    : |' O  `8 b% W    index=pd.MultiIndex.from_product(
    / P# r4 ?$ ]1 l# N7 z1 n7 b        [days, ['morning', 'afternoon']]( |8 a3 l4 h& ~( [7 ]; ^; F
        )
    # Z3 P: C' k/ b! ]# I: M) s)2 b& F8 g0 m$ m" |0 c* o
    df2+ d) @  f6 N/ s; c6 L9 z/ J* e
                          price  volume
    ' q1 {, K- \! A/ K1 \2000-01-01 morning       10      50
    $ A& g0 U- {, s$ A6 F1 o5 s           afternoon     11      602 z0 {1 m* \0 D' Q  b4 [) C
    2000-01-02 morning        9      40: T: K8 i; p7 ]/ ~
               afternoon     13     100
    6 r- T& F) ]& C& P2000-01-03 morning       14      502 u  {8 [. w. h9 l
               afternoon     18     100
    * L1 `* V! y0 L: Z" u2000-01-04 morning       17      40
    - V( u1 n! Y. I, u           afternoon     19      504 ^+ `: g- ?0 O( r. x
    df2.resample('D', level=0).sum()! H3 o8 N, C* h2 f
                price  volume3 }% H+ U8 y7 J- B- e0 g8 u- w
    2000-01-01     21     110
    % A9 p3 @" G: Z, |% h2000-01-02     22     1404 x) P/ G" S  K; b" c' D
    2000-01-03     32     150
    ( k' |4 ~7 Z  h2000-01-04     36      90
    3 a% s- w) x! m
    # a9 h% |# e% T5 A! S6 C7 D1 m1
    5 v. M$ X& @8 n+ u& u9 d2 I2# q( R: a7 k% R$ |  g
    3
    1 Q5 Q/ m& i2 J, b2 ]5 g! [4
    ; o9 Z3 n! ]2 L6 C, p! U56 |5 Y/ p% w- U- A7 s3 U6 p( @
    65 l) ]: v) i. W1 p' `( u7 g
    74 n1 S0 c) v8 c4 [8 z
    80 `4 N% w9 [0 X
    9% {0 C  U# o; T8 K! L
    10
    2 Z/ P/ @7 K7 X4 S: q: F11; N+ z$ D! \0 G& H6 e6 D$ |2 o
    128 p6 b2 Z2 \5 b" S- q2 v9 \% z& ]; [
    13
    / g' G9 V2 z& e; x* f4 j$ V; s149 E* A, m. o: }) L- P
    15
      U. B1 o/ K6 |7 k16
    ! V1 A. N: A! _6 B17
    % v5 D! K. \* H8 A) K" t7 {5 B4 A181 j; @2 L, L& z& Z0 ^
    19
    ; e4 \. R, X$ L, G20, P5 p6 B. }9 S: L& n
    218 K. L% d: B5 |7 S
    22! a+ I3 A& I* W3 @" G$ n! L
    23
    ( q) M; B; p! H' N9 S0 G$ u24
    % h+ T6 b2 D$ Z+ A4 F25
    " a# {/ {, y3 {- d: W根据固定时间戳调整 bin 的开始:
    5 Y" W' d3 {. O! R, l6 ^4 Xstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
    : }% j2 S  L# M, F1 j+ Orng = pd.date_range(start, end, freq='7min')
    8 N& I) A) m* z, P9 j6 M6 O  u3 Fts = pd.Series(np.arange(len(rng)) * 3, index=rng)2 b* e& [) N/ W' q  o8 J
    ts
    4 O& ~, J6 N0 l% [% i; A( q2000-10-01 23:30:00     0
    ) y9 j+ _& i8 D( w2000-10-01 23:37:00     38 }$ [! \: K- T3 b, @& E
    2000-10-01 23:44:00     6" d) F! R  {/ }; r. N! j2 K
    2000-10-01 23:51:00     93 I. Z& K: p( p& b0 z1 M
    2000-10-01 23:58:00    12
    " O* w1 A) r% g2000-10-02 00:05:00    15
    ; ~, ~9 T( }' l3 [% M$ w/ e2000-10-02 00:12:00    18
    $ V& R2 Z, l* @- L9 \2000-10-02 00:19:00    21
    / h0 W6 o4 V0 j1 g% ~& P) i2000-10-02 00:26:00    24) |! N; Y& L: T" I( p
    Freq: 7T, dtype: int64
    . W3 q/ d  Q% I; v6 j3 u5 y2 t# o; ?: b% J, Z; r# `+ c6 S4 ]
    ts.resample('17min').sum()
    - _. s& a7 X6 ^* x! J" K) @2000-10-01 23:14:00     0# b8 _# F1 G& a1 H5 P$ G+ ]4 f
    2000-10-01 23:31:00     92 d" w2 x8 M7 _' ?
    2000-10-01 23:48:00    21% K- I' U: F" `9 {( @/ D  Q% Q  O
    2000-10-02 00:05:00    54' ], B: D& T0 R! q  m+ y& u
    2000-10-02 00:22:00    242 ~+ }6 p" K' K/ i
    Freq: 17T, dtype: int64, j3 i6 R( r- p$ e+ }# E

    5 k- t) s: D% Hts.resample('17min', origin='epoch').sum()
    : u# [( N/ j* O3 d# w$ G( Y2000-10-01 23:18:00     00 z& z2 P) w3 O( g' n* r
    2000-10-01 23:35:00    18
    2 {& L! _  d1 u2000-10-01 23:52:00    27
    9 a8 Z) Z. ~- q2000-10-02 00:09:00    39$ u8 [) l" V* Y# X" e
    2000-10-02 00:26:00    24
    & o5 Z1 d8 {3 `1 `, |- M% JFreq: 17T, dtype: int641 m3 }( W5 O) v$ v# V& M
    5 R6 W6 `% Q0 [' @; O& Y  ]
    ts.resample('17min', origin='2000-01-01').sum()  L! I+ |' L9 g' b- K
    2000-10-01 23:24:00     3
    4 N  O9 c2 _' ]/ Y: R; s4 Z+ W7 G2000-10-01 23:41:00    154 @/ e) d9 }5 V- C  _! U! _, D
    2000-10-01 23:58:00    454 u; _3 ]! \6 o& ~- o
    2000-10-02 00:15:00    459 E7 b3 m6 H: d; g; B, d- R+ ]
    Freq: 17T, dtype: int64
    1 q5 K' T) S( i% {( |  O' E% n) g
    1 l6 v& {  `9 L- g4 ]6 ^/ p6 }' b1# i. m% V7 o$ s6 J- t- |( M# h0 a
    2* {% F7 C! P( ~/ y+ d( Q
    3" F! x/ p, {: E5 i8 }3 j
    42 N7 W9 @- @3 v+ D, a7 n( @
    52 {3 o& f( s4 s
    6: j3 a+ G3 X4 s% G! ?0 H6 W) b
    79 I) n2 o1 C: ~4 I3 p$ C. ]
    8
    1 j: z' ~0 K$ C, N& Y; H% ]# a. ~9/ f, K5 N- O' i/ L1 Y
    103 l: c! Z' G$ o# O
    11# q8 r7 G8 q3 [
    12& b; M+ ~, ~9 d) Y2 g9 g
    13
    % ~( P- i2 D% f0 `% ^14. X4 A4 t; T( Y$ n: [4 R/ O
    159 D7 I$ M% Y# t2 y; i  F4 S! n, V; `8 S- L
    16
    " v- R1 j7 \! r9 [17  l) c- L5 `+ v( _% @1 k
    18
    6 Z0 l- O! H! l* r7 N( t0 m19
    $ m# `+ @6 E, A2 i0 J20' F9 B! J4 S4 p  W$ C& E
    21' g5 S& T5 ^* r0 R( {: O& n  D5 d6 H
    22
    9 D  C$ R$ ]1 P* l  U23! T  s  f( N4 X, b
    24
    / v# A$ b  n6 v5 z25
    6 M  L. E& M/ W6 {2 l: v26
    & d& T; ~. d/ ]# K2 t( G& z27
    0 J( }. S  o8 N3 o- e28. ^$ W) L/ g; o! B. l4 h: f
    29- k, u; f2 M6 G# A3 i8 `2 o
    30& d- j( v2 `" Q' R& x/ v2 I! A
    31
    5 T: O( g6 B2 Q% h! ]32
    8 e) W, q' c, q336 e. x6 P( w5 u. X& g) y
    34. v2 [  Q0 F0 q1 |
    35. q$ ^* p# u: p8 ]6 j0 z
    36
    & P; ]+ H" A6 U1 P- ~; O0 K* h, {37
    . I/ @; W" `2 O8 a0 w; \如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
    8 k3 h' T1 q& N" w; V0 K: J9 ets.resample('17min', origin='start').sum()3 g+ \3 a: ]. ?" g6 a3 q
    ts.resample('17min', offset='23h30min').sum()
    & B; d) g1 I) X3 m# `/ }2000-10-01 23:30:00     9  R1 i) R/ Y, H. V% |/ A* h
    2000-10-01 23:47:00    21; [+ ^( N. Y; }: l, z- B1 K
    2000-10-02 00:04:00    542 \, ^. w5 Z6 i+ w6 c
    2000-10-02 00:21:00    240 Z( T. f/ h5 G9 [
    Freq: 17T, dtype: int64
    : F: f( F9 n% D: L$ ]! P# W: z$ S17 V  m' y- V1 g2 r7 m
    2
    - D$ D; I5 u" f7 Z& G$ i3
    1 `5 _; U6 m$ K. M" V4
    ) _/ T8 T4 @) u0 c- _5 K5
    " }$ }0 B, Z0 ?: B: X6
    : L& {" C" L. T! j" l7" n2 v) U7 \; {, _% _
    10.6 练习1 a" P4 `9 o, G8 U+ v: B
    Ex1:太阳辐射数据集$ Q. C2 ^- v& ^6 ?* o
    现有一份关于太阳辐射的数据集:5 u2 l$ r! h  L. Q
    8 R2 R9 X4 l/ e( G7 x4 N% i( e
    df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
    ! n, ?) }6 m* J. W& Y* Mdf.head(3)/ f8 i  I/ f7 O

    # n$ W9 r! B1 w$ [" E1 m( mOut[129]:
    3 ?! U$ M% E7 Y( D6 E# d                    Data      Time  Radiation  Temperature
    . |  H: l0 a1 \7 w2 D0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
    * a. u' {, z& i* w' }1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    2 t) F/ T* {# j" ^2 {4 ^2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
    - Y% D7 z( b; d9 }: y+ k; |18 ]0 f) e2 w+ s3 f2 Z
    29 p! E! Q8 d3 a
    3
    ) j6 V$ k4 M, l- _6 r6 \% K  `4
    ) @: F$ j0 M, }6 z4 X: z3 o5
    * C' I! i9 g5 |9 I. m6  ^& `5 e4 Y/ s+ x# r4 X
    79 c% ]# [# T& Z/ l4 [0 k+ [
    8
    % N+ M, I1 ~/ h+ |4 s将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    - o/ o' M/ q$ y. f* S6 |. T每条记录时间的间隔显然并不一致,请解决如下问题:$ C) y" ^- E" Q
    找出间隔时间的前三个最大值所对应的三组时间戳。7 ~$ m& s7 R0 `1 T: |* F
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    8 {  t+ S4 I! o$ N3 {, \) p求如下指标对应的Series:
    3 o$ M' C  T! G) r$ y; [& T) I温度与辐射量的6小时滑动相关系数
    9 i  ~, g, s; b; j9 u( {4 |以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    - C% |* t* i3 b! v0 w& T' @每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)8 [& M( x' X7 C: y- l
    import numpy as np) S) a. Y# i5 l, N1 p* a
    import pandas as pd
    - L6 b' e- N! R1
    8 c+ N, W' l& ~- A" @" j2. I1 Q7 G) O- s
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    : g/ h2 G* C: Z; w  d- \data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列" q: \9 b" O% R
    times=pd.to_timedelta(df.Time)
    4 T0 ?4 {( b9 E' H4 g6 R# h& Udf.Data=data+times. ]- ?' X7 X# h; v4 D0 n3 ~
    del df['Time']; F4 W, D1 a* y  R+ q- w( e( z6 }/ T
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
    0 ]! k+ o1 z: w2 ]; Ndf% G9 }. b4 T* G  O
                                            Radiation        Temperature3 @" U2 `7 `, u  U* ^
    Data               
    - l( Z0 k9 {8 f! Q& m# }) k1 K2016-09-01 00:00:08                2.58                51
    # D0 ?- V; {8 N2016-09-01 00:05:10                2.83                51
    8 e( h$ y" w+ z" N2016-09-01 00:20:06                2.16                51
    ; y7 G( i" ]) S6 A2 M0 v2016-09-01 00:25:05                2.21                51
    & C; J' ]3 ^' |+ f2016-09-01 00:30:09                2.25                51& ^$ \: U6 c2 k# ?" E+ J
    ...        ...        ..." q' I+ a, h5 t" c; q. e8 t
    2016-12-31 23:35:02                1.22                416 K6 o( h% c+ g
    2016-12-31 23:40:01                1.21                41' W& N2 {( Z0 I
    2016-12-31 23:45:04                1.21                428 w& n% P% L6 _2 m
    2016-12-31 23:50:03                1.19                41
    5 y3 F& b0 z9 d4 x2016-12-31 23:55:01                1.21                41
    ' k0 S9 P$ i- `8 i
    # q7 Z) n. B0 V  f) u+ t0 j18 k1 Q& g% l7 L
    2
    ' q& U" f- [# j" h# w9 |4 d6 N3 J' f3& X; T5 w% o& Z- z5 x# I4 z
    4
    / e) c: ^8 q2 p& I6 `58 B6 P( c' _2 H) V5 ?+ ?
    6% [" O  C0 f; a6 N  J8 L9 }
    7
    : B4 `5 f8 q+ b) _3 c8! B: }2 [+ B5 r' L1 X  P. U: l
    93 A: l  J+ a7 @9 [/ Q. Z
    10; g9 F- f/ T, o2 N9 A) ~5 v
    11
    " q2 I; `4 m- x6 b; z* X1 A; E0 `12
    4 t' u, q5 K$ F" }+ i13
    * N- C- @9 J. C14! v& c1 [+ O( k! ^
    15
    / T: F& p2 m/ w) F' V1 A162 g/ _6 j' m; |8 t) f; A4 p
    17
    . M7 w% A8 S% Z. B. O( }( y- \18
    ! E1 U2 V/ h, J19# a# ]5 a/ S' }4 Q! i4 B
    每条记录时间的间隔显然并不一致,请解决如下问题:1 Z( x) L5 c+ e1 c7 d$ D
    找出间隔时间的前三个最大值所对应的三组时间戳。
    * g7 f% y' ?4 P1 O( _2 q; P9 F# 第一次做错了,不是找三组时间戳
    . s& v. S7 A$ I$ Didxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]( g: ^4 O( C3 n% k2 }: W1 S
    df.reset_index().Data[idxmax3,idxmax3-1]
    3 w) M! T# O$ i6 k" i" L0 y3 }; S( F1 B0 k
    25923   2016-12-08 11:10:42+ i0 }* k- q( q8 P  e5 D9 ]
    24522   2016-12-01 00:00:022 w8 H3 K. B9 a# `) x. R1 Q
    7417    2016-10-01 00:00:193 t: T+ ?4 Z/ `3 B
    Name: Data, dtype: datetime64[ns]3 i$ C/ H2 |5 R8 T
    14 `" X1 h1 [0 v& ~6 |' Y
    21 e: f+ {  W/ T3 Z9 X, E6 y
    36 H6 y7 i: G8 v  ?
    4- G& L9 H0 @" U+ c7 P* c- I6 C
    5
    6 [8 f$ i! l* J' [9 H' T) k$ f! W% J6  q3 F' R1 h+ m3 j2 H6 h/ }
    7
    8 Z2 e+ S! ^2 t+ W8 R! a8: y! F( ~$ o$ p; F- Z  r" S2 z( r; ~
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    , E, V" m' O0 A8 ^, [list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
    9 i- ?4 j2 l+ Y: S+ U7 t+ \, q, R2 \( ~) A0 a* P% a  n
    [(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
    . _) U" C0 X" n) {* G$ f5 ^ (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),2 S5 X% A. Z% q; r' N
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]. |1 E) }0 q+ _1 E  R( r
    1
    $ P/ c# p: e' ?8 ?2
    . _+ l, h% j; U5 u7 C3
    ) ^9 G: C& H  J3 Y6 W4
    - {' D, G. o) _/ ?2 K2 K5
    3 _+ F+ c! V% M% v# {& A# g" l1 i4 K8 M60 \* V- B8 A0 i/ i  l1 Q6 T: P; z
    参考答案:
    : r: k9 u: O' Y, A5 P
    ! e& ?( e6 U9 f$ }- t, Us = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds(); I6 h' h: B+ h; b4 `' A
    max_3 = s.nlargest(3).index
    5 r' c7 X7 C; ~  hdf.index[max_3.union(max_3-1)]
    ( S% P! `  _( v% ~- U# I* U  n8 C0 t
    Out[215]: * m3 l9 c1 ^, u6 D
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
    / f. j0 R3 W  D& J! m; p               '2016-11-29 19:05:02', '2016-12-01 00:00:02',4 m' p1 b' K% A! M0 o
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],! ?3 M* n4 k$ |- z3 n
                  dtype='datetime64[ns]', name='Datetime', freq=None)
    6 k5 ~, p- E" Y9 o9 o1 B* W1
    / O5 r' N% N$ d: w; Y( v2+ d) m1 ?/ a! `( ~/ b4 i
    3
    " D' j5 b- h% s5 c, `44 }  M: ~6 f" w& Y0 P
    5
    9 t5 E9 s5 o3 h4 c- J  u% g) g61 q: j1 S3 z  h" E, E7 S
    7
    4 A9 G+ r$ X2 |0 X" Z8$ X' H. w% }: [6 W* M7 t" }
    98 Y7 C% ]) a+ E4 \
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    $ P" ~9 A8 e* p' q4 m: b* I7 G# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间$ q6 s4 F9 K( p( `3 @
    s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    4 Z( r7 `% x% g/ a: h6 ns.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
    : e8 c1 j; l0 K; o3 e
    5 b/ u: Z! ~4 L+ J9 G  t- x(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)1 U3 \, o, u- @
    1! R1 z1 t! I' ]# o+ ]0 a9 |( F" w3 a
    2; R+ Y; t0 e9 _! u! j0 q
    3
    / M+ H, N; v, I2 l- m4
    7 W1 i$ {$ u2 R% _, x2 L4 g, {) F1 _3 h5
    ( t! D( p2 A0 W9 `1 P%pylab inline/ w; L0 c6 i# r7 W: v6 r
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    . R& Z* b9 l& e" g5 x- vplt.xlabel(' Timedelta'); [# G) ^7 X# W1 `) p
    plt.title(" Timedelta of solar")
    - ?0 d4 Y: {7 a, ?0 p) R8 m; b1+ S' E' _- q* H; a4 }
    2" K" E6 k, h2 @) d! N) F3 W
    3' W# k: H* I# {$ Y6 i& [6 A
    4
    6 u1 c8 ]$ _% |# E+ H) _$ R# e( p1 i* C

    9 W* |5 ^  O# o( [( O2 O$ M( T求如下指标对应的Series:
    - I( _" ]2 {3 B4 Y8 E. B温度与辐射量的6小时滑动相关系数
    2 O7 I3 T& U0 g( E, L以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列  R# q& j/ |  H" h$ c/ c
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    + H7 g" w/ `. a0 N6 x7 C" Ydf.Radiation.rolling('6H').corr(df.Temperature).tail()/ m9 ?) _$ f6 d8 ~- N

      u& g8 {5 Y/ y  D% GData
    6 k! Y( F0 Z3 |$ X& `& p0 B8 N' T2016-12-31 23:35:02    0.416187' A4 }+ n/ Y- E5 `
    2016-12-31 23:40:01    0.416565& k, |6 a5 d2 O1 E" I' F
    2016-12-31 23:45:04    0.328574
    + }- T9 \5 R9 S4 J  x# k' Y2016-12-31 23:50:03    0.261883
    5 L+ g3 z: R0 X; `$ x5 Y  T, D2016-12-31 23:55:01    0.262406
    + ~/ g8 }' a% r; odtype: float64
    ' `/ ]7 a2 j, i5 G6 `' p4 Y1" V# a  v* v$ x% h8 [
    24 l% Y5 C8 \' x+ V* F* k
    37 m+ d- f9 e0 n& X
    4+ i$ M3 ]& r" X- o9 ], R+ ]' s
    5
    ( R  x( O/ f9 L/ f6
    0 g2 C( Z! y9 ?; }$ I2 z7
    1 X: i- o$ e. ?9 h5 J% A8
    ! N. _. Y4 S- ]0 h9* v, X* u& W7 C& T: d& z
    df['Temperature'].resample('6H',offset='3H').mean().head()- b, `( t4 n' b9 ^7 X
    * M% t8 p% x( m: q0 @, q- v, s
    Data0 _' E$ h3 n! ^6 U, q
    2016-08-31 21:00:00    51.2187502 }' {8 S" O$ E: G& |1 _) Q0 b: m8 S
    2016-09-01 03:00:00    50.033333$ U# u5 s+ `$ b5 l& ^- j
    2016-09-01 09:00:00    59.3793103 K3 @- ?& k" h- ~% x
    2016-09-01 15:00:00    57.984375& |7 Y  a3 P( k' a4 t
    2016-09-01 21:00:00    51.393939- G7 n) _% E( {0 l' w. j+ w
    Freq: 6H, Name: Temperature, dtype: float647 m2 @1 u9 T, J+ \
    15 r( y2 I# U1 Z8 I" l  t) s
    2/ R, e1 T6 q" j9 b8 l& ], n
    35 J' z1 e6 ~1 b* p! @7 B' ^
    4
    5 W& W$ I/ ?6 n) Y: X, j5. ?. p/ T' G- h9 _7 [3 y
    6
    ' b8 i# {1 f9 Y. ^# p& I7
    1 z1 E; \( |% E5 U8$ b, F9 z/ c- _0 S7 @9 R
    9  D: _5 ~, o5 l9 L' X: s" o" [0 w
    最后一题参考答案:3 o# o; D5 @, d, w' r" ?

    3 Q3 u$ Z8 w) Q) W# 非常慢
    ( \4 W/ i/ X# ymy_dt = df.index.shift(freq='-6H')5 L6 S4 `6 g% [: \# W& u
    int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]! v5 a6 q; F6 i- E! ^1 V. p6 |* Q+ B
    int_loc = np.array(int_loc).reshape(-1)
    : H, M2 N2 Q3 U7 v+ Eres = df.Radiation.iloc[int_loc]. t3 o! p$ n, E/ O
    res.index = df.index1 ^1 w0 w- {1 y  n- y7 t
    res.tail(3)' r' z2 j0 i8 A/ Y( W
    1
    2 B6 b  ^% t( s2
    * r1 J; a, F6 h* Y% y3% i  K' w* z  G1 b
    4
    9 S& ~2 q% l+ K1 h% }& h59 N8 ~$ \# _8 c$ J! J" A* V
    6
    ; d# r  I6 e! D% \7# T! d; C% K5 W
    # 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    : h( L3 F7 d, M* I& w) H; Ytarget = pd.DataFrame(4 H# k, P' L/ X* K5 n% y5 a
        {
    8 U1 K: N# {! z4 ~5 h& [        "Time": df.index.shift(freq='-6H'),& Z5 ~0 w5 p0 j' W8 S% n
            "Datetime": df.index,5 V, E" X- |) |# i0 u8 K
        }% n' m+ {& @& q/ U; {
    )
    7 P3 h6 k7 Q+ b4 P/ y5 n3 Q- C! M; U7 l+ q
    res = pd.merge_asof(; |" W1 ^8 ]4 _
        target,' t+ d: v3 a2 F% I/ I3 v2 a" r5 O+ ^! k
        df.reset_index().rename(columns={"Datetime": "Time"}),
    ; r$ }' w4 y) D" o    left_on="Time",( i9 y' b9 f3 |- Z! X, s. y
        right_on="Time",
    & a" }- S) n  f2 b# {$ P; ^% X% I# I" P    direction="nearest"* I- F  U: x0 b! I
    ).set_index("Datetime").Radiation" k' w6 Z( b& m/ Z* T

    ) F5 Z2 i. D  v* U% K4 ~- Tres.tail(3)
    / s; h* \. S' R" Z; b0 O2 i2 M7 \6 a: fOut[224]: / b7 y3 r' c+ g6 @& l' D
    Datetime
    # k) N# v" L% m6 |- i* x5 q' ]" Z2016-12-31 23:45:04    9.33
    ' G1 J+ P9 H" ^. q2016-12-31 23:50:03    8.49
    + {' B$ Z$ V) n7 W" ~) D2016-12-31 23:55:01    5.84
    9 ?# K& h: \8 w3 zName: Radiation, dtype: float64
    0 S2 Y7 @) q7 Y
    6 n% P4 i7 K# }2 N' {1
    # X( e& z7 U0 k" u( S- d9 Z2
    5 F" d$ k' _6 j4 v. W8 a! Q5 f3
    . ^9 D- X, S* F! c4, P7 Z" o7 {: O) w3 g
    5
    ( F7 M- s  s* d' M8 s; ]+ [9 q6- y" J3 K; A) y4 {1 l: ~6 g, @0 _. [
    7
    & ]+ r& t$ b0 A8$ {$ m4 D4 F: z/ G* [, a- ]# c; x
    9
    ; p, K7 I' D. ?8 W% Q# j1 q0 \; j10
    . d' {' o3 }; Y3 A" j11" ~+ `' t, }5 ?2 A" `$ ?* o
    12
    7 s8 ?& m7 d8 r13" [7 m5 Z% H4 O; c# l
    14: ]0 k& ]6 R+ e/ y, Q4 N; R0 |
    15
    6 {2 @( e5 S$ M0 }; k% D% ]% H16( ~7 K1 A4 T) A% Q6 u
    174 {( }/ O0 P% k0 R
    18
    0 W  u% O3 V4 i6 e" J6 n' ?/ B19
    ) ?8 _* Q' l0 ~; h* f# }2 D6 I, Y- `20% _) ?8 a" X  R. \
    21  i$ ]! I/ S9 i3 D. |$ o
    22
    - Q0 F( D, e3 S- }232 m( G5 J8 F- H, A: j
    Ex2:水果销量数据集
    8 R* k  [* L& m) w* H; u现有一份2019年每日水果销量记录表:
    8 k: }5 [+ W$ y$ y5 |
    % Y. y( Z; i: F$ @# [/ S5 ?1 [( |df = pd.read_csv('../data/fruit.csv')' |7 i# T  W. s1 g! z
    df.head(3)- G0 _' E0 s7 q1 M

    $ g% }& `7 L* ^2 tOut[131]: ( A4 V4 o7 A7 j9 t  S$ t
             Date  Fruit  Sale
    / \- ?, J" q% ]. q: u- K0  2019-04-18  Peach    15
    7 u) @5 z% C" ~. p, Y: X$ m1  2019-12-29  Peach    15
    2 Z! H# K: H9 F* j; T+ ]7 m2  2019-06-05  Peach    196 I- X! Q- {9 k- ]3 ?$ c
    1
    ) J' {4 R3 G  N1 {$ Y" ]2
    ' }0 _; Q4 T# v5 k% r& G: |3
    ' W$ Y. N3 ~2 n" r  g% W+ x- Z7 l" h5 ^$ G4
    5 [% h6 ?8 Z' g5 V& m% ]5
    ; w& ~5 p' ?' C2 y) V, s/ K( `+ u) J6
    - Z2 ~$ C8 Q4 U1 k4 [. C2 ?% Y! f7. K- k8 o- n* n' V% p
    8: j, j, D; D0 o$ W; y4 l' l" U
    统计如下指标:! ^# G% n: j2 ?" g
    每月上半月(15号及之前)与下半月葡萄销量的比值
    ) W0 g' C( o1 y每月最后一天的生梨销量总和$ g7 P' h% m9 H. F
    每月最后一天工作日的生梨销量总和- \: X" I& q8 G4 o* g* D
    每月最后五天的苹果销量均值
    ' o/ c) d5 U. i( a1 G按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。& b1 }' |0 m9 ^) \7 P) @! O$ g
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    " |; L8 Y1 V# ]import numpy as np3 h6 b% Q5 y9 L
    import pandas as pd
    3 f7 f, T: b: u% Q% I. M1
    5 q% S' w4 A0 J1 Y- w) w9 G2% y# F5 {, j9 H6 k
    统计如下指标:
    + c+ x. d: Z, S- m6 R% d每月上半月(15号及之前)与下半月葡萄销量的比值
    + o  l/ i- Y! x, v4 K每月最后一天的生梨销量总和
    # v4 V; [+ ~8 \  b( G* c7 f3 P每月最后一天工作日的生梨销量总和
    " p$ v# j* j2 a2 G5 }7 A& ^  c- q; g每月最后五天的苹果销量均值( u1 x, o4 ^* {+ Q/ [
    # 每月上半月(15号及之前)与下半月葡萄销量的比值1 Y. d' G) \3 C/ |  w! C
    df.Date=pd.to_datetime(df.Date)/ T1 @# z; R9 K/ F; d$ x& ^
    sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum(). O2 k" |3 G* L- x4 f0 B+ Y8 h( i
    sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    ) r2 s, O4 ^. V' Z, Vsale=pd.DataFrame(sale)
    % I! N2 X" [3 ]sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
    3 W/ L& y) |. _1 x8 Z* ]: H                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
    9 K9 @9 `( A/ csale.head() # 每个月上下半月的销量
    , u5 ]- {7 v( U2 h5 P: Y
    ( h* h+ H1 k. ?6 b8 e) f, K1 l/ `  Month        15Days        Sale1 `& W/ M! {5 X7 V5 w5 o* |! w$ a
    0        1        False        105032 m) F4 M$ I  [6 v0 w
    1        1        True        12341" \+ ?7 C6 y7 n2 ^' K$ m) m
    2        2        False        10001
    0 ~1 l8 h4 ?4 N. Q3        2        True        101060 c  V) i8 }  O5 P' X
    4        3        False        12814
    " ^/ l7 Q7 V7 I& N% ^( |
    ! J: T* ?. P) g, d# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序2 P9 y) [: Z- n9 A4 v
    sale.groupby(sale['Month'])['Sale'].agg(, x9 u8 O  p3 f6 l
                    lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    4 D  B' e( t+ i* W1 e# d6 w+ h# E0 Q  _1 B; a1 A
    Month; G4 h$ `3 ]  K5 O
    1     1.174998
    % l& |! z( m% X! o2     1.010499
    7 i1 N0 ?+ r2 d5 B" Z3     0.776338& S, A2 _, v: p% E# O
    4     1.0263450 l7 M+ ?; e# w, o
    5     0.900534
    0 P. `6 v' \3 D6     0.980136- a  m. @2 X$ h
    7     1.350960
    3 t9 w, O% L+ r7 f1 Y8     1.091584, {, o5 d! [' l2 J; M/ a
    9     1.116508
    1 T  S+ B+ W3 [$ d8 V8 X; z10    1.020784! U6 E" w. ^/ I& l! O2 W1 Z& l
    11    1.275911
    1 U3 ^9 k6 [% B( `8 J1 A12    0.989662
    ' n. c+ E6 `- {/ \* {( L! NName: Sale, dtype: float64
    : o- d6 g' D3 o& c2 q# B8 c9 |4 S
    12 c% y. p$ J$ o
    2
    " D- Z& M" B3 `& ?37 w/ f8 F9 F$ U; A
    4* q8 y  }* Q  S, D; f6 V" a, s& v
    5' K$ A+ T8 M+ A7 {+ D4 H
    62 P; |' u" Z! o/ @4 d
    77 B+ k7 L; D* s% e- \, M
    86 O$ U5 G7 q) q' I4 g% u' s
    93 M6 a6 x. d  ]4 r8 ~
    10
    2 |+ |' j9 X6 `2 A7 ]/ s2 V117 V" ]7 I% P7 y, t' ]5 d
    122 R6 A: A/ Y0 S/ L! b
    13
    0 y# S* A. z! h; h3 }5 B14& f4 x; |( _) `5 t/ ?: {  v$ R
    15
    : h- a+ ~* x: i16# Y8 P" E3 m2 W4 E: }5 h
    17
    % ]( j3 }, s# S0 O' _2 c182 x. g! X$ m, \# t, _! A2 j
    19- h. |/ y8 E# O; W2 w/ ]
    20
    2 h) t+ l: l, a$ G! p21
    6 o8 J' r0 |: Z  K; W" c22
    4 ]* ]: s7 H7 }& p+ a234 h* A9 c# ]0 P
    24
    ) R+ r$ x. e1 B9 @$ {: O25
    9 ]# K* u$ Q- O26, b: v  D" W, C% Y
    27
    9 k1 Z) T* \3 z5 O2 S28
    3 C' R1 G4 `. {/ B/ G3 f29* d% q5 @. f: {& v! u
    30( ?9 `# [( o9 w5 z3 h, J2 o
    31, R  q( j4 c4 T# Z% [
    32
    1 F0 [( Z, I9 l33
    / s! {% C' T: S" H34' F: K  G" |% s' D6 E
    # 每月最后一天的生梨销量总和
    % k1 @! d: B* q  p$ Q4 ?) W. ?2 gdf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    ( M9 m: c7 i1 ~# J! x/ M5 V" m& V5 k& {) F9 k. H: L
    Date
    $ h% V+ N  ]! _8 G3 _3 Y" @2019-01-31    847
      l. ?% D5 [2 B- s" C2019-02-28    774+ F# |( l0 ^8 A) H% f
    2019-03-31    761
    4 L; @) _8 G4 C/ _! F4 t$ p6 H2 k2019-04-30    648% i$ f- @# ^5 p/ o- z* |, k4 e
    2019-05-31    616
    ! B4 `+ F- J- {2 L) V1
    ! W' S* O) O" _5 e: F0 ]) R. w2$ `& [% g0 e+ e
    3
    $ @  e. o' d) G0 M/ J2 N6 k, d' L4
    % ~* _7 t, `: c" I* x5
    ( l4 J3 h6 K! H) B- s8 p) e' M62 L$ h/ C9 e' x: i% f! w/ y
    7! A6 f3 u/ q- M) Z, T5 h+ y: ]7 f
    8; U" T1 a7 M0 u8 c; D: P0 p; H
    9
      D* i3 Y" _% D% Q3 W$ l/ {# 每月最后一天工作日的生梨销量总和3 [0 a% m6 V8 H" v2 ^
    ls=df.Date+pd.offsets.BMonthEnd()& L. a) J5 U" G$ k# P
    my_filter=pd.to_datetime(ls.unique())
    - h# [& [; K6 j" o- ?+ q. r6 d% tdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()0 e- b& z, o( F' e. V' j: F2 |
    * O& ]' d5 X* C# ~% ^% w
    Date
    - d2 V- Y' a8 o& v. V8 e2019-01-31     847
    ! P) T) r2 p% A: ~- B2019-02-28     774
    3 g# J0 l. A! ~, x$ w. D2019-03-29     510
    . U0 x- E; Z( C% y. {! ]+ ?2019-04-30     648( x* f% z' {7 r; s, {  L
    2019-05-31     616
    7 x) u7 W! i; i5 {. U' S1 I1
    ) O/ F, P$ W3 l9 I" B/ P2 g, d2
    / H5 L# h( ?: u" I5 [31 l. C8 J: i2 Q8 v* _
    4+ P% e$ ~( E0 T+ X, U8 k) ~$ ^
    57 @" n  D! s. h% A( q: W, I
    6
    ( S! f: V6 ]) Z% q5 g2 Z. e8 _7, a0 C* m' u0 }. b5 Y+ w1 y6 ?* z! b1 Z
    8
    7 r: X; D3 }! l! s6 r5 ]9- d4 q- w# k- q% j3 s  f/ v& B
    10, ?8 T" u. z  f2 i
    11
    4 G& P# |& D: [( X# Z0 o  R( ?# 每月最后五天的苹果销量均值. s# |" v8 T# x+ o' m* ]
    start, end = '2019-01-01', '2019-12-31', u( W- m; B3 S% r- _/ M
    end = pd.date_range(start, end, freq='M')
    / e4 H% w$ C' t7 _: ]end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差* b0 v3 H4 l9 C6 ^, Q+ ~; ?& z
    , n* I6 l* @9 x4 P
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)/ e9 k# q% j: ^6 e8 \
    td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
    * J) F( L$ i% h/ c7 Eend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表: z9 [  n& C' O2 ^$ {  F- J

    $ \6 L7 n, J+ V0 m; m# S8 Capple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    $ t/ p" L, X2 K; `7 xapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
    % y: e# b3 |3 \( B" {# n& V6 b) r  k) C/ h
    Date
    5 L$ q+ ~) }' y/ h; b1     65.3137251 E+ i4 L  G. ]; w# I% E8 K
    2     54.061538, G6 ~2 s9 i0 z9 W) ~9 l
    3     59.325581
    5 Z/ K; C* F  E4 B5 E3 e4     65.795455
    6 w7 s2 L- U- c" h0 K; F5     57.465116& `4 C  D) H1 D! D

    5 s( G: x7 a% H1' _9 U0 j) \" e) j
    2+ m  ~* W' G+ T; M! I& x, ?! Z/ r
    39 K& o8 F# L) }/ `/ B3 ^
    4
    $ k& z2 U2 Z, i9 u  }8 y# ?, Z5
    ! t$ A5 d, B3 R69 p  Z; w; H: F2 O6 @6 p1 G: {- o
    7& F' p/ y$ u- Y3 a6 ]. C: c
    8" M5 L' a3 F/ m& W' w- f5 ?) ~
    9
    4 I1 g8 G1 Q8 O2 T: Y1 E10; ]5 R& G3 h- ]4 D4 |" r, u6 w
    117 z7 Z% r- u# u9 q$ }4 @  v5 o
    12
    $ R  Q7 C* f0 I/ Q13
    % K) A% g5 f! S8 w& r; O14% s; \2 u8 @, z* c7 S' \, U
    153 q8 q% D! }3 L
    16
    . a3 g& ~1 n' e4 |0 l9 y  a+ _17# n6 l9 k; N, `- ?# q6 P1 f3 T
    18- L7 a6 `: I" b; O# T# [( P6 V
    # 参考答案:9 Z) R2 n3 j$ E& ^* z7 y; D
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(1 V3 z/ x& d1 W0 ^" k# d  c4 E, h
                ).dt.month)['Date'].nlargest(5).reset_index(drop=True)# G0 x' n5 f: [2 q
    9 Y) J  L  b1 o9 t" m
    res = df.set_index('Date').loc[target_dt].reset_index(
    # }! a* b3 [9 l* ^4 X- D& r            ).query("Fruit == 'Apple'")
    8 K# `$ M6 X! R# ^1 B! G$ t5 |7 Q5 s5 m7 L" E: E
    res = res.groupby(res.Date.dt.month)['Sale'].mean(
    % {2 \! h9 q1 P$ a  x            ).rename_axis('Month')3 c" S& p) R0 D
    # _9 V9 I9 A3 Z( \0 A5 x$ p
    7 N- O+ s$ o) x& W+ I. r
    res.head()' e2 N1 Q/ X2 P5 ^- r
    Out[236]: 7 F3 d8 X* L8 `, ]
    Month" P3 v0 U  I1 i7 D! @* p
    1    65.313725
    0 @9 S& ^$ Z1 S# ~& M2    54.061538
    " R+ v$ b: \- C. v3    59.325581
    8 o1 A- @/ G) v& O: [( Z4    65.795455( G2 S$ ~0 w- W1 {  z; o) ]
    5    57.465116; J$ `1 N( H- s9 L4 S4 q, x
    Name: Sale, dtype: float64. ^5 S) i, k  t: r* a' `! g! W
    ' \, S8 `9 X3 p, _8 s( n$ Y, _
    1; j4 j+ J7 h- w- Q2 B+ M6 a1 W
    2* L4 k5 @6 R7 j" w6 F
    3
    & I4 r9 h" W4 z! v$ q4
    5 }: ^8 U% j3 M* d5
    ; ~. D  t8 a( a/ b% V& c7 E* ~" S$ c6' j) D  E5 @5 \: P) {8 X; y8 L
    7
    1 P9 ?6 o, T) C( D8 V8
    0 q9 K- q9 j. J9+ Q1 V' w2 E- f4 v3 M" [  Q: Z$ e
    10! v& H2 E# e3 a+ U
    11
    ( B( i7 x8 A* O. D0 R& Q12
    " X. r, l: Q  S13
    - U% A, [7 r0 ]4 p( h5 p' c/ {145 @/ F( t: k4 R  c: {
    15- \% |4 `4 U( X7 k2 a+ F* ?6 b1 G
    16
    + n% L/ B- D7 y/ u' ~17+ `* a. D) b/ f. ^1 ~" e; d
    18
    + d$ N- i  L" }9 c3 D' y19" n$ L0 J1 l9 h* l: p
    20
      H$ n4 j/ p+ C1 [按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    / P- |9 M4 i0 oresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.) X1 }4 ]0 f# n, X1 J: \
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 + |, Z/ c, u& v1 V; `' M6 ^
                                            5 b  Q- H9 D- I/ e/ X  a
    result=result.unstack(1).rename_axis(index={'Date':'Month'},
    8 V0 W' o3 I* p5 P+ U# a" J- @                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.
      U$ j6 L6 V+ C; _' s) S( {7 dresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
    5 K, ]3 x: Z3 Vresult.head() # 索引名有空再改吧
    - X  Q  r; |5 j9 R" B9 R# ?- L5 g5 Q3 l) E, y% u; T
              Week        0        1        2        3        4        5        6, w' ?# J- @; w2 \! v
    Fruit Month                                                        ; n4 }3 O  Y- x' `. t$ b& m5 U' S
    Apple        1        46        50        50        45        32        42        23# p% T) b9 v7 @) v2 T1 W
    Banana        1        27        29        24        42        36        24        35
    0 L( v' l  Y' {' U1 Q  RGrape        1        42        75        53        63        36        57        46
    ! U! R- D. v0 O9 ~1 h7 [Peach        1        67        78        73        88        59        49        72
    $ r" I2 T3 `2 ~! uPear        1        39        69        51        54        48        36        40/ D1 M  f( S! x9 d& i
    1
    , a) A" w  t0 r24 W; K! G3 H8 {% O/ r8 k
    33 X# z- X) [- F/ o# p& b
    4
    3 S) w5 X: u1 Y: G: {7 I. b5
    ' a' Z; B4 \2 q6' `8 o3 w0 Y6 `* b0 [5 L5 z: t, O
    78 f" G0 _' B  Y
    8
    7 r. W$ E0 c' H( j9+ h3 J+ z% Z. Y$ I, t( n! H8 u
    10% c1 ?1 H2 p/ G' c$ J
    119 B& Z$ \8 }1 s& B: s
    12: m1 s4 I2 @" \7 p
    13
    2 }  y% o- q8 u7 [1 @, K8 f14
    . B) h0 m2 `6 E6 a: j  ~! W15
    % ?- g3 L; [: j4 F2 u按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。. U/ G0 n  b, ]4 |' \
    # 工作日苹果销量按日期排序% r  Y5 N% j9 N) V' d) H5 z
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()$ Q8 l: X6 P7 q8 h0 Y7 G
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总2 }+ p$ b" I7 {  V$ N4 n% ~1 W
    select_bday.head()4 j3 q/ E+ a: J& ~2 m0 k

    # \7 X7 u' {" k* W/ ]4 }# _Date
    6 l2 S0 Y( Q) ?5 B2 K: y7 q2019-01-01    189
    & i5 o: R6 \/ z% h, j5 X2019-01-02    482  L: Y2 [5 U8 D$ e, f
    2019-01-03    890
    ; |1 p* W8 {+ M! K: Z2019-01-04    5506 V, U* a& K  f
    2019-01-07    494/ u& Y! Z2 Q% v$ i
    5 A! C/ _. P1 |& F6 R
    # 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。6 b& m; {3 h( D) X9 Y2 q6 s! F  }
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()/ h7 q% ?8 i( a7 r

    % K6 @1 c3 ?4 b9 U- n. Y; k" i4 MDate7 w. b7 O" a' X! }3 H9 ?
    2019-01-01    189.000000
    8 w$ ]' w; b& ^8 u2019-01-02    335.500000  W8 L) ^6 {/ S8 ~8 p  g8 V- `  l: B6 Q
    2019-01-03    520.3333336 g4 e3 c/ H5 P
    2019-01-04    527.750000. i, Y' M% O, |- u
    2019-01-05    527.750000
    % `  a$ s# [: Q3 m
    * X0 r8 k$ c" i4 Y4 M# T————————————————. q+ Y: M& k" Q
    版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ) U, [( ^) x9 v% [原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913* ], q, `( j9 J( A% O" K" ?$ R
    1 D% E& v, P0 T" i6 h, _+ r
    ' {9 Z# a3 X; x. t6 S2 F
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-4-12 18:55 , Processed in 0.623154 second(s), 51 queries .

    回顶部