QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2829|回复: 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
    ) y9 A* X. o) i  Q
    2 y' g, w0 ~4 h

    1 R$ ^! n% O# X+ b  _" K: b文章目录
    # X5 N( A' B; @5 Q! }第八章 文本数据- Z6 N$ @. m  b# W7 \3 @$ v) W
    8.1 str对象0 X& V  n5 I! m# [5 J
    8.1.1 str对象的设计意图, \9 c7 }" Q+ d
    8.1.3 string类型
    ; I4 X. L& H& J% p$ H8.2 正则表达式基础
    3 W/ A4 Q: ]5 c6 W8.2.1 . 一般字符的匹配; Y3 y8 ^  t# ?' T, i( Q
    8.2.2 元字符基础2 m0 f& t/ K* G% N* k* P  l' H
    8.2.3 简写字符集$ C! F; T% [2 _' B6 |9 O- C  ~4 V, S
    8.3 文本处理的五类操作
    % I" `+ e* u6 j8.3.1 `str.split `拆分
    9 e( e6 G% _4 m1 I7 }6 S5 S, ]8.3.2 `str.join` 或 `str.cat `合并" |' G% U% C! }3 f
    8.3.3 匹配
    - X) B/ _: C4 W8.3.5 提取
    7 L3 ~6 l0 t+ j% \" g" O7 c8.4、常用字符串函数
    9 |+ b1 y2 t& L6 U( ~2 E2 F8.4.1 字母型函数. B, S$ j5 W8 o2 a
    8.4.2 数值型函数1 W4 S8 A7 I5 v7 Z2 ^
    8.4.3 统计型函数
    ; m5 a7 [% V# y6 i) E5 L8 K( G/ k8.4.4 格式型函数
    2 q6 j4 C* B% S0 O8.5 练习1 I7 ?- a( R; z5 t1 E1 S
    Ex1:房屋信息数据集
    - w8 g- i7 ]9 l3 F( `5 R( E, BEx2:《权力的游戏》剧本数据集
    - @5 Y1 C& V! O# o" B. R第九章 分类数据
    ; t# p, j( `2 @1 x/ }+ M9.1 cat对象5 F# z* ~9 f& Y
    9.1.1 cat对象的属性9 G5 ?4 t3 M1 h
    9.1.2 类别的增加、删除和修改
    , b7 S" {+ }0 V4 F$ q) X) ?0 w( c+ O% ^9.2 有序分类
    ! i& U  E9 {5 r9.2.1 序的建立
    8 C3 u4 z3 r; b2 x& a' D  F9.2.2 排序和比较3 f$ W" O* _6 X5 j0 `' h* I7 E4 C
    9.3 区间类别
    4 d; i/ ^* h1 n9 C3 u8 N1 x8 K9.3.1 利用cut和qcut进行区间构造
    ( F/ O. s/ k2 C- r. C; d9.3.2 一般区间的构造
    + v3 l3 n# u  N5 t: {9.3.3 区间的属性与方法
    9 ?) @) y/ [+ |% i4 d1 m& Q* q8 e9.4 练习
    % `$ i" O7 s0 F. @& {3 a% ~Ex1: 统计未出现的类别
    ; ^$ P4 ^4 ~9 m/ OEx2: 钻石数据集8 m: B8 w6 k$ I7 x- m
    第十章 时序数据
    9 `) p# Y1 h, h10.1 时序中的基本对象  G9 `  T/ h& P4 c, ^& Y: i7 B
    10.2 时间戳
    ! W/ _7 C8 s9 z) z9 \4 Q& k10.2.1 Timestamp的构造与属性
    # b- B: v6 Q+ \3 b, s10.2.2 Datetime序列的生成4 }7 v/ r0 z# G/ K& b3 Q- f
    10.2.3 dt对象
    . r+ z1 z5 ]+ `' U* e- I. _2 V2 |& }10.2.4 时间戳的切片与索引! g& B* e& M+ S3 P3 I) h, q5 n: r
    10.3 时间差
    , `; @! w: w% |8 E10.3.1 Timedelta的生成: T& W4 y1 w1 _, q: M& ~3 ~
    10.2.2 Timedelta的运算
    3 M  ]& J6 r3 X3 G9 e8 z1 c! e10.4 日期偏置
    / a9 s9 \6 B$ e! j, ?* D10.4.1 Offset对象0 S; j! r( `# J/ e$ X5 `7 X0 Y* m
    10.4.2 偏置字符串; K! U& V9 N, w8 D  M
    10.5、时序中的滑窗与分组  w1 {, U* l" e3 M6 C1 }" R
    10.5.1 滑动窗口* s9 c$ C! z  F0 W! i6 U8 k5 H
    10.5.2 重采样
    ' J- h8 Z" _$ I" B10.6 练习
      y! _9 `! a8 p* E: e" l5 PEx1:太阳辐射数据集3 u" m" J- m( E5 g
    Ex2:水果销量数据集: i8 f1 w  ?% }" P* f- W
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网& T2 Y7 r& H3 S( H. m" e0 L
    传送门:
    , h: h, m* \1 U" _) y! y
    - q; Q+ d6 M: d/ l" z! f4 N# R9 Xdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)6 D/ X4 l* F# j) x% \' g) {
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
    ' z& C( a2 Y, c9 L第八章 文本数据
    & W  v% G2 G+ d6 i) \2 Z5 T8.1 str对象, g: y5 |% R6 B! `  M, [8 o0 ]% q: D3 N
    8.1.1 str对象的设计意图
      M) i2 i2 {+ P7 ]9 v+ }# c& [  str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    % p" q6 E; Q9 p1 i( v9 b
    7 ~( e; z  Y$ E+ ]% z2 ^0 k2 Xvar = 'abcd'
    ) |# z6 j0 _3 g( Z1 z9 E( p4 z9 Rstr.upper(var) # Python内置str模块4 H: |1 X, z) M* z
    Out[4]: 'ABCD'
    9 e7 I( j7 d( c7 |
    ) i. C- ~; q6 D9 ~s = pd.Series(['abcd', 'efg', 'hi']); g2 ?4 r# C* g' N# G* Q

    . E8 d& T; w9 Ns.str
    ( W0 T. a3 D" e4 w7 p9 j. oOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    2 }. h3 [8 O0 l: P* A& G6 R: j4 V4 Z7 V! `
    s.str.upper() # pandas中str对象上的upper方法; h: v- G- {4 @
    Out[7]:
    ( K% I- C) _% g0 t9 N0    ABCD. o0 g' U1 Z/ Z. J  T. W
    1     EFG2 j; r0 j& U8 W* `1 c
    2      HI* K% e# e$ g9 P$ G
    dtype: object" {% R: j4 p# E0 d& B4 q
    1
    ; Z, d. H, e# ~. b2
    % e) q$ O2 G2 G- w9 r2 O3! Z5 `( L( h/ d6 {
    4
    2 [" P; ]- X" I8 v1 f& A5  y" @$ E# w% i7 e3 j
    6
    : r$ a7 G' H; Z77 w3 e+ ]# k% }. F9 k; P" [
    86 j& k. g  E7 h7 P3 D/ \/ w
    9' H3 d# S) U  W4 k0 S- q
    10
    ! w. G. G* h5 j0 b8 j4 ~$ p2 t  m112 `( v8 ^& j3 O, D( K
    128 I' W! o6 T! D; O8 o6 M$ d
    134 S- O* F, |5 Y8 B; u
    14% h4 G& P5 m5 }1 c3 q
    151 p5 {- {* Z, }7 ?
    8.1.2 []索引器0 ]$ g: }; g9 V8 ^: C- H6 C. k: o
      对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
    & d, I( [# R) [- ~' @  P! |1 a  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    + A' u6 Y1 h% n% Y# f. x2 G7 M' z
    / f! Y; W# y/ \8 G0 t# ys.str[0]
    0 Y3 H, ?7 I, M" E: F9 N  ^- kOut[10]:
    + n' {' E) K! v1 S* l, Y0    a. }$ @% c/ @+ d$ e8 F  G
    1    e
    ) B' z$ S3 D: ~2    h
    4 S( ?* }. L5 I! H$ b9 @: Adtype: object# t, U( ]# z- Z; x1 s3 O
    $ T" G* t- E* z) j0 \/ N: [+ f
    s.str[-1: 0: -2]
    3 T8 `# l5 ]" |$ g: POut[11]:
    . d* D* B/ y* t, D0 O" ]0    db5 B" E- q* z0 i+ ?. n% j2 y
    1     g
    1 v9 F& \; I6 M: _7 H5 h2     i
    , c% H. s8 L" V2 [' ?  L: E, Xdtype: object
    0 V' G+ m* Z$ p" N
    9 `" U, b: m, \" ws.str[2]3 }* D0 H! \/ t4 I/ S
    Out[12]: * G+ k" p& [. X- d7 z' b1 f* m$ `
    0      c$ K7 I$ R( P+ P* I
    1      g  }9 p' M5 D% A  }6 s+ ^% A
    2    NaN  [5 X) e, Y* p
    dtype: object6 q" R/ S& g, o3 y: x
    1 b7 q) c# l% K0 x% _9 I$ a7 l9 G
    19 E1 ?0 o8 v' N8 N+ _1 Z$ m
    2
    $ a  p; o, N6 k2 m3 b  i6 T# ^3
    - e0 M# o1 g  n0 o# x4/ Q" z( F) D- D5 I; b" f
    5: e/ c& M: K7 V) @
    6
    . x# R% ]* j' q* E  y7
    4 I9 ^6 ?& G& I) n8) R7 a3 H4 D2 ?) z8 P
    9' t: X% ~1 Y5 W/ T0 }, p
    10
    ' L( E; I8 l- t, p' M111 Z! V* ^1 n3 C/ j& U& |
    12
    4 O" l+ y. h0 t, [6 ]( \137 y/ ~% O- f% ~5 x7 x- u" L  v
    148 i. ^0 J3 V; x4 |& l- C
    15- [+ g' i' r5 r2 g$ X
    16
    ( y4 @* @4 E) u5 i- V5 _177 a8 U+ {) J& U2 J+ s
    18
      D5 t+ g  Y- D* Z19
    ) C1 `5 m& H. p8 f) R. `! k4 ]2 N20
    ( t  ^2 M, c5 p0 Y/ C- @2 wimport numpy as np1 q" c0 L7 @( Q0 J
    import pandas as pd
    - M/ y# B9 D' c; [
    . ?# j9 I+ f7 |; E5 Z3 {, W& gs = pd.Series(['abcd', 'efg', 'hi'])0 q) h- t* G; K2 {  X3 c, b  }; O
    s.str[0]
    , i8 U: m, I1 ^8 y2 x4 Y1% ?* ?7 c1 {! m. s) e; s7 W0 U( u
    2
    ) D* b0 t1 M; J2 M) Q  V" p3
    " e% D; |: ]/ v! t* n4& G+ y, r: j1 P5 f9 _, q
    55 O: r* Z! b6 I* z  }
    0    a
    7 L1 r1 q5 W* p# w+ F  a5 ~1    e4 \/ N9 U  k7 J' @) {0 q
    2    h
    " x+ J$ r/ q) V8 E  edtype: object
      T4 W) X' G2 ?$ c18 a8 b. {" I! b- ?, B0 f
    25 Z% ^% ~- z$ n, `5 Z7 f1 U9 N
    36 L6 O" R7 q, i/ M
    49 J% ^) s. c) L0 B
    8.1.3 string类型) F3 u+ h: e5 h, ]0 E( c  W
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。, U$ M/ h& P" ^0 U- x1 Q' Z
      总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:' i+ ~4 Q) ?/ U  E: m4 U! r$ \1 N

    7 K" e! }+ D6 D) A# Y  s二者对于某些对象的 str 序列化方法不同。
    ) ~3 Q. f8 h+ d可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:& v, Z% s) b' u& X
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])# v' T. s: `6 S0 u+ E: k
    s8 l9 a  t2 J  |# z
    1
    $ s* }9 \2 x) v28 W. y, t" B9 `' H5 ~
    0    {1: 'temp_1', 2: 'temp_2'}
    + U8 H3 |9 U0 ?8 L/ B1                        [a, b]
    " B. r  |2 h5 I$ R7 x1 H2                           0.5
    0 D4 N+ f. g' \$ @. Z; x# Q0 }3                     my_string
    9 i8 U, G8 i' {  k6 z1 cdtype: object
    + q( s3 n+ V) S. }! s1
    ' \+ O* z/ L' Y1 y% C  w) |4 |2
    0 y) v7 l% W% |$ M5 ~' h3
    1 b% f/ l, }, F1 \7 e4$ q8 T( S4 }! G2 Z/ O
    5
    . O8 `0 k, c9 m: L& n4 {( d+ Vs.str[1] # 对每个元素取[1]的操作
    + ~# \8 W5 }0 ^12 k. N/ i, T. O  u; Q; o3 B
    0    temp_1
    " v) A0 D2 D' M1         b
    0 y& q' ~2 |: ?4 Y% S2       NaN
    / m: f' _/ f1 {' f3 j5 I) {5 V2 z3         y+ Q7 o! z( e7 M( P
    dtype: object3 C3 W) `- f. m4 o4 A. N
    1
    ) I( G8 i* p' f. H! W8 q5 b2 X25 ?& O/ i; V  @
    3! d' U) m' W- T, T2 g. R4 k
    4
    1 t1 E' ~7 c& {5 m' I; l6 v# Q59 r) z, c6 V  r
    s.astype('string').str[1]
    + O$ u6 T( ]# S6 @1* F& c- y; j6 q% y* w7 Z1 I
    0    1) J: u, B  }4 T! v4 p
    1    '
    + n. j3 j, j/ o8 J& ]; `2    .
    * a2 T* |3 [6 c: B) S! Z3    y, i8 i) T6 F% b3 L+ V# m! @. o+ Y1 n
    dtype: string
    5 i- V  l, [! i+ x. Q1) X. p% U& G; e6 q/ O
    2
    ( m/ I% K9 s6 m3+ m0 ]" @" h5 c/ G: }/ k% k: j; L
    4
    # l; X/ F3 y2 M) v56 T* A: F7 H7 j2 W' v2 J9 T
    除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:  V+ O! S: O; P

    ' C7 Q6 w, d. d. ~2 J( y当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    % i4 m8 D. C1 r2 j: n4 G2 sstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
    & {1 Y+ \: X/ V; W! f' r. Gstring 类型是 Nullable 类型,但 object 不是
    1 N: w( }+ s3 Y/ [6 H# [  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
    ; F8 P/ ~2 `# R- p0 @5 D# P  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。' \) Z: N$ o1 n4 j: e# b: u6 E
    s = pd.Series(['a'])
    9 l: |4 Q; S: @* k( n3 a) I, e4 o( f: }( {7 o( b
    s.str.len()5 K3 S& ^- M5 F. T, ~
    Out[17]:
    / h& i3 B2 c7 L2 H0 S; G% X0    1
    * {2 J( H7 [+ M: Idtype: int641 n$ H5 T) t( h9 g3 j' y* E4 R

    & E' u9 B! P) y* l9 ^! ds.astype('string').str.len()5 W$ Q2 `% n+ K. m! q+ r
    Out[18]:
    0 c' W3 K; l9 n- ~5 P0    1
    - ]0 X, V; m& Vdtype: Int64
    ! L) C7 V- j- M6 P4 O# R- D, S& U3 [$ v* T6 @9 H
    s == 'a'& B$ k% G  U- Y
    Out[19]: 7 X2 _9 b. V# ?" {
    0    True
    ) M/ ?! y, e5 e2 }* G* ydtype: bool& O* y) ?0 b0 h. r9 x+ U

    " E- c. A% T7 j7 p0 B1 G! es.astype('string') == 'a'
    ) T( b4 v0 G/ S( g& }/ o; hOut[20]: * s; G6 t" x$ x5 ^
    0    True3 ?, R  i* ~' T$ A0 [8 v8 X5 r
    dtype: boolean0 D  m8 a3 j. h6 y

    % R+ m! ^# g* {" c! d3 Ws = pd.Series(['a', np.nan]) # 带有缺失值! k- f$ ~5 f) I# m
    # D+ ?5 p& L* P+ E! s" o
    s.str.len()7 l: Z- P% @3 D" N
    Out[22]:
    1 G% M2 d4 }" r. w; ]& @0    1.03 k6 r. F6 f+ G1 I: i, ^& P
    1    NaN: a3 x* V) _! n7 ?2 h0 K
    dtype: float64" ^* f9 e9 m, b# G

    5 s) r" {* l2 p' b+ Ms.astype('string').str.len()$ z) O1 v! g. O
    Out[23]: ; o" s2 H0 I( _" n/ h" d9 V
    0       1
    8 {. _1 I. c4 N1 g! o* u0 T1    <NA>+ L: I6 e, S, }7 |
    dtype: Int64. j& S8 A( p: a1 f7 ?$ \$ R* R
    ( b5 L% L1 ^$ C0 j9 l0 I8 l0 v7 L4 ]: L
    s == 'a'7 h3 P$ m; ?8 B) J; }# K* g  J5 j
    Out[24]:
    * c& j$ q- ?$ N- o' q7 U0     True
    2 U4 M; ^( F& y. T1    False* K# ]$ L, Y5 U/ N: g; S3 P
    dtype: bool
    ( W$ h* d$ e: i/ D
    + e4 U3 g% v5 M9 J' |s.astype('string') == 'a'
    ! e$ {- W% q" r7 Y  ROut[25]: 0 c2 k  C+ x# J' t7 Y
    0    True
    " e0 n# M4 B. S+ ?) c+ T4 S1 x1    <NA>3 d1 M9 f: l) F9 I9 d
    dtype: boolean
    / X- m! x) g9 O5 {6 s6 D
    7 m5 g& S8 n$ d4 W( V1
    . B- g3 h. l* \, ^25 A+ S6 C1 _% A* J- G$ p
    3
    ! C# O4 {6 k" H( R45 i' W$ p7 `- H
    53 z% F5 ^7 O2 X; G
    6
    8 ]: v4 A9 h" ^0 `' }, n4 o" g7
      S& k$ `) M# M5 n2 i7 ?8
    ) E( @6 {$ x% R6 I7 U0 C: z9* L+ O" }5 \: ?' E
    10
    ' h  f) n% j4 c: H6 H! A6 @, V11
    0 `* D; W+ r6 ]/ Q129 p7 w: f; w9 T5 u" l! {4 P
    13& B% _8 N4 r" B# M( D
    149 }: {+ E7 l% X3 D6 m1 `# _, D8 `; s
    15) B7 j- {' H. t* |! i& m
    16+ M! e% A8 k- v! ~  Y9 e
    175 H/ P4 |' S. g3 x7 R
    18) ~. I. w) J1 v& K4 A/ S3 E
    19
    : L0 r# a& B: Z) v9 D20/ Z" w" s" s% M) f2 l4 [
    212 \9 J8 O0 ]. C5 P# m
    22. F8 |  Y: ^/ P# w4 F! L
    23
    1 V1 z* z1 a1 o( x  O4 w" ]24; A! m# M; _& K
    259 e9 ]! s2 |6 V# c% j: p
    26
    ( c/ D/ ~+ j/ y5 q6 R6 i27( R) k8 \5 k% \% ~; O( M/ U; X
    280 u, F5 M( q6 I, ^6 |  u
    29
    % G8 v" q; t0 \& [5 z! w: {+ [30
    9 y, u8 c5 U$ D- E31
    & q" U1 a% X# `* |1 k  t321 v# G+ [/ W9 D1 B, j1 c( D
    33' V8 u( K& _# @3 D- Z2 i
    34
    6 u1 k% F% w0 [% A5 A/ `35
    2 l5 [/ H9 R- q36
    $ w0 u6 m5 ^, Q  m. Z( {, J; R3 h37
    / Z. N7 s; l3 j- ^' Z( a387 n1 U7 D5 v2 g8 ]% ?" Q8 x
    392 c* q' e# c1 a
    406 x5 f$ i, \& Y8 t* e5 [7 C  i
    41
    ' x8 U! n* ?: s7 R2 Q' T5 j42( q7 H) p% c) a  U% d
    43
    5 ^5 W$ O/ F6 X2 l3 Z. ]% c' ?# |$ [44
    0 N3 g3 o9 H# v6 o3 N( ]45
    & t6 O* [) ]/ ^* D. E  S4 ]46# ]+ B& K. E& a7 ]" O6 G2 F" t8 q
    47( q+ Y5 P6 b+ `6 }4 P
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :4 q# D9 c4 O8 ~; p/ v7 G5 Y

    : k  x7 q  q9 ~s = pd.Series([12, 345, 6789])
    ) k9 H! N. m2 M8 W5 r
    2 l: H; H9 T0 g  M0 S  J6 ls.astype('string').str[1]
    ' M2 ?7 e% y5 POut[27]: ! B& R3 F% [3 F! y
    0    2
    4 d' o' |- v" p- j2 I2 L1    4
    # N% S. h7 P9 K+ u: ^9 q# D+ v2    7! S  c# Q0 [0 G2 l' [/ ]
    dtype: string; g. ~! x" ~. x, G6 B6 x
    1. Q& h# l5 }0 J2 y
    2
    : Z4 y5 F5 |- u7 k$ ]/ z- m) j+ P3; K2 ?/ D3 y, C, l2 X
    4
      ?; i; K* Q, b9 B$ e' e" p5
    1 n3 T! R8 k0 M; c. [: L; I5 |6
    ( n/ o9 ^. W1 m" A5 i& X. E7; [% R8 M; |- ~
    8
    : r) E# v1 ^# B, y8.2 正则表达式基础
    4 H% O/ n. c% S) z: t" |这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书5 O5 q* \' [9 X* d$ I, h
    $ K* Z# J$ x2 s( K- q
    8.2.1 . 一般字符的匹配
    / e' K/ w1 b& I正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    / G9 G2 R" R! y
    ' O4 ]9 t4 f! K) R! |/ s0 I3 D4 h) \import re4 c0 ^2 Y8 {! r/ T9 N
    ( E- ]. G* [# o& k# F
    re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配
    % _! M3 s4 J# B+ l" r  yOut[29]: ['Apple', 'Apple']
    ( e+ @/ U% |: c" i6 d1
    . {' R9 y8 D- z/ v1 e) t2& b+ K# g1 I7 W8 \7 y' Q  k1 Y
    3
    - \/ ]2 t1 m' A4
    : |+ U) F$ C  |: M# b" g8.2.2 元字符基础8 {, m0 i6 p9 P2 d0 K- P
    元字符        描述3 A+ a# `( @2 {  [2 i0 {
    .        匹配除换行符以外的任意字符
    ! ?6 i- Z6 F+ I, }0 t$ V/ X[ ]        字符类,匹配方括号中包含的任意字符2 ^6 d0 u' T8 t: x0 h+ A% R
    [^ ]        否定字符类,匹配方括号中不包含的任意字符2 ~/ g# Z$ F0 `. Y. y. s
    *        匹配前面的子表达式零次或多次
    3 x7 }8 B7 y/ j0 ?! y9 E" z' F! G+        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
    0 Z$ K3 d: s0 @$ z# D6 \0 W?        匹配前面的子表达式零次或一次,非贪婪方式
    + h3 B. B' F* C1 i" T1 e+ Z$ X, g) z( K{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
    2 Z. Z: r5 G6 v* S! ~: u: ](xyz)        字符组,按照确切的顺序匹配字符xyz
    4 Z) k: X; n8 n|        分支结构,匹配符号之前的字符或后面的字符8 z+ e1 E; k& Q# ]( l
    \        转义符,它可以还原元字符原来的含义
    - t) o- G6 W, E7 O- P. `  s^        匹配行的开始
      I, `" v9 P5 Z5 u: W( e6 G% m4 a$        匹配行的结束
    $ g2 a0 v' p( `( E9 T& ^/ Gimport re; p% e) \* F( ^5 J- P% v
    re.findall(r'.', 'abc')5 e* Z- G+ `2 I' x
    Out[30]: ['a', 'b', 'c']
    $ ?5 a8 \7 v& t8 h5 w6 U& L  I2 u* ^% Y1 o$ o1 r
    re.findall(r'[ac]', 'abc') # []中有的子串都匹配
      P! x3 U5 _+ ^! }- B  Z( B( fOut[31]: ['a', 'c'], V$ ~' v7 K3 a# K; R3 E
    5 O* Y! K. @$ g" J! j& u: R% |
    re.findall(r'[^ac]', 'abc') 1 e" \" `' |* m: \
    Out[32]: ['b'], H+ a& i6 M& m; k$ O- u
    . u* |4 A1 s1 @. J4 [) |7 K
    re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    - U" W* \! }6 R" ROut[33]: ['aa', 'aa', 'bb', 'bb']
    * r4 |' @3 p. E7 r& y
    1 y  H, q9 B  U  cre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
    - R( }! V- ?7 u8 I* d5 gOut[34]: ['ca', 'bbc', 'bbc']
    - w  F  _3 q- p9 Y( T
    4 v7 D5 w0 c3 }6 H# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
    6 q0 [& b! g6 x"""
    7 \. O: ]) ~0 r/ [2 f1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
    , e( [" S2 i, a5 @6 }2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
    3 ]) N+ h% _+ s' O3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,3 W) @& n. q. @  U& u4 I
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a, w: I) K  f- B2 E" }7 P) n3 O
    """
    0 E, v* u9 i+ Y% G3 a! S5 g' F7 O0 p% g
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。/ g7 H6 J" O5 ?+ ]' z" A
    Out[35]: ['a', 'a', 'a', 'a']6 h8 Z: U: @1 A0 ~5 L& z/ U& `

    7 W0 \6 p( K2 t  o+ \' F7 {# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
    6 |3 Y2 z0 E$ Q) Y" {3 x7 @# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
    6 G( E- I# w) x" G* Y7 Dre.findall(r'\\', 'aa\a*a') ; i1 _' h: f2 r# Z% ^) ?
    []' p7 V4 O& x, t' Z. [8 W* A" m8 [7 a1 @

    ' |: z: t5 g! b: j* s, _re.findall(r'a?.', 'abaacadaae')( c( Y( K  H( X5 X$ @
    Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    # L$ ]# Z' O9 }1 A) Y% G
    % E, w( @( f# G) Kre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
    0 V9 {/ N0 G  N# S[('width', '20'), ('height', '10')]9 V0 }1 p. O% X% t. [0 e% K

    3 H# i, j; A" V' a# m6 f17 i0 v3 W9 h! A9 u/ b& w
    2
      ~( I- }" s; A7 g2 R3
    $ C+ p" }& a" _+ j1 c* T4# M2 L9 p& p: q  N% r
    5! |! y9 `  |- S# J6 x
    6
      D( P% h. R+ U7, g. s% `. w& Y( d2 G
    8
    + T# X5 ]; ^8 s+ {* S& Y9
    * _  n3 n2 k' Z; p  q0 N10! p# U9 }% m- G8 \8 X, t$ o0 M, P
    11! @  Q. S) e2 _8 k& e
    12
    + Z1 r! b, n$ s& }5 n13' U3 L8 u( S& Y" O
    14' v& {1 F: {# N5 d2 Y
    15, `8 K& I- n. r: I7 `
    16
    : Y! V6 l  x% D6 P# F. o+ E/ |  `* Y17
    & \/ n8 Q- a; n, V0 B18" b# I0 {8 Z9 c7 i% S3 ?  I
    199 y  z( M! s( A+ O: i
    20
    " P9 q) E8 ?! V- B21% l% @  y7 s: D+ w- X5 N: u
    22& g. ]+ l6 i2 r2 \4 s# Y; y% r
    23
    & d6 A3 ]: M( `3 X24
    / y0 O7 I& a, d4 i+ J' l& Y& R2 f256 M2 \# G( |' v. ?; m/ r' P; c
    263 j) o2 x( f9 v% b# I1 C# F
    272 C1 b+ U, }6 j6 z9 p
    28
    ) r1 J$ {) H# E* V29
    9 R, U" z  [6 C& ?1 V305 ^, s. K; F2 _8 o1 A# l# b
    31. e! ]( m5 b" S0 S* i" a
    32
    * p, X! i0 r' u2 }33
    7 B2 ?+ v1 I8 X' W( Z34* ~8 {0 S8 Q8 d! K5 C! V  d
    35& U6 @5 ?' ]* w. E- Q
    36' p4 H; t# W8 S8 L- q# i% J
    37
    + a* Z; U, b6 L" `" i2 L8.2.3 简写字符集9 w3 ?" F5 q& q2 X
    则表达式中还有一类简写字符集,其等价于一组字符的集合:
    + T7 L1 j; m. \4 L: E" d( I& S/ v# h6 N0 U3 \) S
    简写        描述
    / c& e, n$ S. k+ N  W- l, g8 K\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
    % b" L5 P: C- R! g( v) }\W        匹配非字母和数字的字符: [^\w]
    + Z% Q' s- N: s5 n' d* J\d        匹配数字: [0-9]
    & A( j3 s0 U+ Y( `' O\D        匹配非数字: [^\d]
    / [4 y% n. p7 {) J* T\s        匹配空格符: [\t\n\f\r\p{Z}]( {6 t& x! H. z( c2 c$ J+ ~
    \S        匹配非空格符: [^\s]
      @7 w1 Y: A& i% Q1 Q\B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    # y1 ~7 r/ `: F) _) zre.findall(r'.s', 'Apple! This Is an Apple!'), t# {+ a4 [" C$ c5 z6 M( \
    Out[37]: ['is', 'Is']
    " s$ z: }. x) n, B1 @/ w+ t( N  e! S
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
    + h( N: D$ S  o; X. K" dOut[38]: ['09', '7w', 'c_', '9q']4 d; A3 `! e% _% J, G

    9 r8 |, e0 [6 L8 z" y7 o, z" ]re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
    0 m' ]6 `, o' G7 T) n& VOut[39]: ['8?', 'p@']& s( v! N8 |$ U8 m( K8 u

    # K7 Q. G% R8 U$ K# q% t% A' r/ Vre.findall(r'.\s.', 'Constant dropping wears the stone.')
    1 _/ p( _- |# }( d0 P  T& H$ zOut[40]: ['t d', 'g w', 's t', 'e s']
    & R6 M3 K" q! Q. c* r
    * _' v% O0 V. R* y4 ire.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',. N. p' s; k4 @( i9 ~# `6 H
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
    9 n8 K/ u6 n9 L! Y4 q( v# i0 v, A% G( d$ U2 p( r  w$ [' b
    Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]& _9 d. \" l( d) R

    9 N7 n, D; r# J1 c/ i18 A6 _0 m! O: ]5 Z- w2 j+ r2 g
    2
    ) q2 F! E5 j7 |: _1 o3
    + \9 o0 l( X7 U2 I; K' K9 D& |* h4
    ( {2 j' m; U5 D2 Y# w5! k; H, B2 D* o, d) Q7 R
    6
    3 R; t! R  q$ a7
    ) W( C* |6 p0 t  P/ b8
    4 I" l; b0 D: C( A) t6 q, L( y6 D9! h, c- C$ D1 n: X" A% R* v
    10
    : ~0 Z" @1 z. U$ T; ~11
    - m( Q% Z# v+ H12
    ' Z4 C8 W4 q& V$ n6 I3 C/ e0 }13: z" t6 h# T0 S) G
    14
    7 c' S1 g6 H7 w; \15! r- i- d0 j- Y# ?( E7 M9 d
    16
    % e/ l( V, `3 S. P0 F: D7 B8.3 文本处理的五类操作
    / ^% F3 N: |3 }. Q( i" t8 K8.3.1 str.split 拆分
    ' [5 @' |& w8 O3 T6 B7 D  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
    ! @2 s. S, q# C0 o8 @2 u9 v8 l
    9 {" N# X# d7 e2 F$ es = pd.Series(['上海市黄浦区方浜中路249号',% n6 M7 H+ c. L( Z! h
                '上海市宝山区密山路5号'])5 R' @* Z! x: n( ]1 E
    9 K: s& _5 r+ s$ M9 K) A- o

    * y6 o/ P  s7 [3 x. v' Z+ fs.str.split('[市区路]') # 每条结果为一行,相当于Series
    ) C, B  k# C1 C; Q8 }; d; R; YOut[43]: , g3 D2 b6 Z1 y7 t, k$ k; V
    0    [上海, 黄浦, 方浜中, 249号]
    3 C& a. |% t2 k5 Z( I1       [上海, 宝山, 密山, 5号]
    6 O2 @; a. C* c6 H8 Xdtype: object) `: [$ ?0 s7 F" w
    ; I/ E  ~3 \/ H: T9 u6 g, e
    s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
    - C7 L5 I: y0 g+ k5 M" BOut[44]: - ~* u# M' W: `; I2 D- L
        0   1         2
    7 S! ~+ d; M& L  E# J. p5 X% _# e0  上海  黄浦  方浜中路249号+ t* N* U( t& e
    1  上海  宝山     密山路5号% J& \% W# x, l6 h4 n+ J% c( c% m
    1  ~4 ]3 v! J$ P. p
    2, C1 Z3 A; A8 g' u7 `
    3
    6 M4 m% R; Q& w45 t- j) }) [' z3 \
    54 G% M3 Y( ^& a/ O& n- |  U
    6
    . C7 Y$ w  F. f7 U8 f7- O; d1 Y/ d0 s
    8
    5 P# ~, g2 U7 O5 A) Y9
    1 {& {: S. M1 a$ X' v: |10' g0 X1 M. Q# Z
    110 |, f4 R  Z/ D% j: p, Y
    127 L; A( D+ r/ L" c" a! E
    13, X( P3 h& B: a6 y" ^
    14
    ) B9 Z" |/ Y+ v9 S: S3 p% r; U15  ?( o  T$ f9 t' j$ M+ p
      类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:, J! g$ K* k7 |4 |( I
    ; I: Y4 E9 c6 ?6 d' _
    s.str.rsplit('[市区路]', n=2, expand=True)
    - a+ O! w/ X! b, B- u& R0 F8 oOut[45]:
    5 z" n% B7 X8 B                0
    8 Q$ V" C* m& J  O, H0  上海市黄浦区方浜中路249号- G& r) E0 I- L
    1     上海市宝山区密山路5号
    / h1 J0 z7 u0 e. `; J7 L1
    $ i$ ~0 ]1 z0 a2
    * p" k9 C5 H( l$ l0 p3, W# {  P2 m* b0 m5 L6 e; v0 r
    4
    4 u" R! V2 L" @: L5, [, g. L* |% v, I
    8.3.2 str.join 或 str.cat 合并( S, \4 `: k1 |! f
    str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。7 x  R! {* M  D) d1 h* g7 s
    str.cat 用于合并两个序列,主要参数为:# s+ K( J" _$ c0 f2 Y! f+ N) n
    sep:连接符、; E- A6 B7 C" o$ i: q6 ~+ W" g
    join:连接形式默认为以索引为键的左连接
    ! g/ m' R8 ~6 T2 v" U& ^6 O5 {na_rep:缺失值替代符号
    ; F" D" \$ ~0 d7 x; o+ qs = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])* S( h) I) o* y& p
    s.str.join('-'); N1 K$ C; M& ?& x+ c' _* b
    Out[47]: ' |; C% S! i; L0 ]8 C
    0    a-b
    9 H- B+ q) w2 B# _5 \" z1    NaN
    . e; o" h* V/ f% m4 B, c5 j$ V8 p2    NaN4 r9 i5 R2 ~" _, j& W2 p; z! ]2 ~
    dtype: object0 u! o, P; {( q- g
    1
    ! @7 x" j: f1 [9 f& Y  g( e2
      ~* ], c" p# P3" t: H6 I& ]6 x5 f+ O! m& F
    4# d6 i) K8 {2 X9 N, C) p* y
    5
    1 {; h4 Z, h) T6& Z$ y. `9 g: l2 f' d
    70 O8 H( p' t3 e
    s1 = pd.Series(['a','b'])4 B, y- u; g+ H/ l
    s2 = pd.Series(['cat','dog'])9 W+ G/ K. X; s" W2 ~! Z
    s1.str.cat(s2,sep='-')! O. e: D7 W; n; p7 k/ M+ r" v
    Out[50]:
    ( a" U! X' Y6 {, p0    a-cat) [+ q  x8 \- N$ o  Z& Z  s
    1    b-dog
    5 H. d! j1 u$ i) s8 M# ddtype: object
    9 g  S" M/ x5 n4 W! s
    * ~+ r) v* q: Y: r: u. i1 js2.index = [1, 2]
    6 Z% Z" p( e" d  G- J) g0 ]s1.str.cat(s2, sep='-', na_rep='?', join='outer')
    ! [1 d6 X( U# ROut[52]: $ v. k' X5 ^( I) \; Z! _9 m
    0      a-?
    : k( b) \& Y0 j  z. T9 }" D4 a1    b-cat
      g4 x! s1 I2 e- Q  g2    ?-dog8 u; R/ Y: ~& P6 o- \
    dtype: object( i: ~: O9 V& ]# _
    1
    9 y2 w) G% h! r0 x# B2; b  f; M: t2 W5 b6 Y( r3 W* ?% x
    3
    5 b4 u4 |5 @% e# ^; m" x4- j5 h8 Q  t# z, u9 a  ]) z" t- {
    5
    ( h# T+ j0 w! Z# s/ y2 w# t7 p6 \$ }6
    . ]# }, m) v- N7 c7! S9 d3 {9 x/ x
    86 x3 C0 f7 z8 ^6 x+ V% t) I
    9
    1 Z$ {9 ]' u1 @2 D% M10
    * e# X3 U2 O/ s( w3 M11' a+ s5 e4 Q. f1 j) C  b) R1 k# o
    12( |6 r! ~+ ]" U; `8 x
    13  \  l4 k& H3 w' S  C
    14
    , L1 U& ]& \! e1 J1 i, [9 W4 j. m157 B" m) Z4 O8 ]/ q
    8.3.3 匹配8 O# d2 j! p0 C% Z
    str.contains返回了每个字符串是否包含正则模式的布尔序列:
    ; i( q" g1 X! \8 ]; Js = pd.Series(['my cat', 'he is fat', 'railway station'])
    8 Q# j. x0 g+ B' a6 Ks.str.contains('\s\wat')1 L! d% Y' ]# [: m" P5 r
    & \: x  b  n; l9 |
    0     True
    3 I7 R9 x! T: t" w+ G8 C& j1     True
    " _& i; b# P5 n; E8 l" W! z2    False; t% |4 e+ r- E" h, Y6 d
    dtype: bool6 L+ F/ h0 L2 y, M; A( n
    1
    ) ~% K( S, o! x! Z" G0 \; I2+ y$ {3 `6 a( j/ L+ @# _6 Y; B
    3
    $ R, V& [* C5 h* M* t4
    / \6 `7 X6 v" o6 [  ?1 N  Z* K5
    2 o0 I0 X3 t: J& n" `7 w0 L6
    0 J/ \5 o! b9 Y0 E7
    # u, {9 x3 i( K) z( U5 istr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    5 ^* y9 b7 S: T; S$ e4 T) us.str.startswith('my')" Z2 t' x; l6 [1 g7 D: T

    , D7 T7 `. V+ d$ P" ~, N% E0     True) Y, a3 W1 V: w& g" Z3 M, P3 ~
    1    False
      E3 l" S1 J* y# r: }7 I( m2 R2    False
    ! K* \5 }2 r& @3 S0 e4 E' Adtype: bool7 f- J$ z. ?% G& V  a- _6 A# [0 h2 y
    1* r! P0 a. `( x9 v/ |& |7 ~* d
    2
    # K  |0 R' R% v9 w2 E' b3 x% K* B3
    / |! G! Q: z8 k6 ^% \/ q. {+ Y4
    7 B$ m* x6 w4 N7 A; W% a4 Y/ R54 M$ n" ~0 h% M) B
    6: r+ l# D2 z, ^
    s.str.endswith('t')4 Y- h+ v" M; E5 J- ]; k; x

    ( Y. _, D7 k- g8 y& D0     True/ d" g( v( j) o# C6 P2 z2 R4 w
    1     True
    * Q* Y) J: t# f; E, F2 k2    False  @% ~" \4 ]. e9 M, W5 g' U# b  z
    dtype: bool  P+ V8 @/ D& k# g8 M3 ]
    1
    + c' B& e/ [" j  O6 |" U2
    % H( Z; n, C+ W3) D9 B/ h  H" H% |( s) l
    4) Y% @) L/ a/ o; C$ u
    5' a8 n3 K( \: V& }( s4 B& K* }
    67 @( N" j8 {/ [" ]' R( K. O
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)4 B- Z' P+ v8 B$ b% Y+ p" P
    s.str.match('m|h')0 g7 Z- B; G# w; w2 G
    s.str.contains('^[m|h]') # 二者等价
    0 G. O2 J/ v1 }' N( y& W! F) S% ]) c0 p; i% j8 W6 n
    0     True7 G6 {2 [2 T7 C: g# a
    1     True
    ! b, V- E0 t" J$ G# B( z- x# o: `2    False
    . v# ?9 u5 V$ r  v" K4 |7 Tdtype: bool# x/ d2 f8 s" B; ?9 X
    1
    1 e- D. M( v+ V( D2
    ( Y2 c# k9 u' C1 G3
    / k  W0 ]' r' t4! e: n$ |: F7 d
    54 ~" Y- B! k& w& W" Z! Q
    6
    2 s4 T! k0 ?0 y7! ^% r0 m& P0 m# f# _4 u( H
    s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配8 ~" n6 }$ L8 {/ t( w) S
    s.str.contains('[f|g]at|n$')       # 二者等价
    8 z) T# ]! y3 K/ o! I  Z5 M
    , q  n2 P- M9 `4 [7 G4 M3 G0    False
    4 p/ u  N- X2 b6 ~* s1 Q/ T7 m( G/ O0 [1     True4 |; Q0 b" L9 w. w( w
    2     True; E$ J8 O$ o% b, b" ~
    dtype: bool
    / m  X  c; U; S! b$ l* Y) _1
    3 z9 q* _8 |  E/ w5 v2  l3 H0 L! y: q8 ^: m
    3
    ( I! `/ o2 `! Y, @* P4# J/ y; v% O) X) G! j6 B5 j
    5
      _3 y* l5 }/ h8 d8 V' X6  v- Y1 z2 d6 N
    7
    ( w5 v# u# g* Y0 r8 ostr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:, j/ |' ^4 y/ _$ k+ }$ S7 z
    s = pd.Series(['This is an apple. That is not an apple.'])- J: I# y6 o# g+ V$ i) ^  t2 N

    ; T  T7 h* t3 }: f2 V3 I- t# L9 }s.str.find('apple')
    1 U. E! m+ a  `4 F0 K  sOut[62]: 8 ^% O& I2 A4 x/ [5 @
    0    117 I6 D% @& L# H  |9 [- `
    dtype: int64
    $ Q# p3 C' R( ^: E; |# M* a1 G* J2 R7 S% b: h0 W: x7 I
    s.str.rfind('apple')
    3 }# E1 `$ c  I) P2 ~5 x2 wOut[63]:
    9 A: ^7 S- C5 M! {: B% [2 {0    33
    . u1 z, d4 G# c& E; ]: Gdtype: int647 \, ^. {6 Z9 b" H. y0 ]$ L
    1" E$ b  h' B7 f: o6 h0 O
    2) `) |+ u% M/ a( x
    3
    8 S  z( n6 E3 L9 {8 z7 c4 y9 P4
    6 B! V4 {5 D3 o* d5
    2 T. F3 g& W1 d, s" A6! J9 P$ v7 u7 _2 B4 R
    7
    4 s. `. ~% R1 c8; P. F6 ^; H* u3 y7 S0 v4 C& C
    9' m! C6 k( ~  [  ?+ @/ D& Z2 ~
    103 L. X3 B: j$ O( m9 H
    11
    7 N1 w# l' ]- @7 M4 z替换
    1 s7 ^/ k9 k6 G6 T- d& U4 M9 }str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    / j$ X* Q4 _% ]s = pd.Series(['a_1_b','c_?'])
    # \  @+ Z: V2 q; l6 k# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?: G2 T8 `; ^2 h( u8 Z9 X- k  r
    s.str.replace('\d|\?', 'new', regex=True)
    " S3 s* z) [3 m2 @/ Y
    ) V6 r& _1 }$ U0    a_new_b
    * ^6 S6 E2 R7 e0 @' Z3 U3 y1 B6 G1      c_new; y$ {/ C6 E5 V: ^. R
    dtype: object
    % ~  i% d4 \+ z2 ?9 A1
    $ w  D' c  ?. y$ u  F6 Y% z2: n' V0 a# g: }/ U. M4 J
    3
    % J, M# G% R  m$ J) B5 W& T2 H4' U( m! Y2 w, J0 e1 O" ^
    57 f; t2 m  ]9 S; t
    66 F$ L& F! |8 P4 W. v
    71 w6 C0 J9 T+ a1 V$ P9 ]
      当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    . W; j, \1 l- j! v6 V/ b2 j0 z1 {7 ]3 F3 W. n
    s = pd.Series(['上海市黄浦区方浜中路249号',5 P: l6 C1 I- G% k$ L
                    '上海市宝山区密山路5号',
    & E6 [8 ^  l9 d' T0 u! }                '北京市昌平区北农路2号'])
    - f) z" V; L9 j4 t& cpat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    + W' S3 K; [" D% _city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
    - g# j: F' G. Z1 gdistrict = {'昌平区': 'CP District',
    6 F7 U0 K/ `( i2 {4 p            '黄浦区': 'HP District',
    4 n$ E7 O) O! `2 @. L! J            '宝山区': 'BS District'}
    6 H3 a* H; G9 j' @* b9 Xroad = {'方浜中路': 'Mid Fangbin Road',
    5 `- M% ^& C7 P; S2 [: N1 X        '密山路': 'Mishan Road',' l* W2 V9 B) t- E9 ^, X
            '北农路': 'Beinong Road'}4 U7 |. i5 k) \  Y- L
    def my_func(m):
    : J6 g* e' c3 l6 @3 t& m2 j/ ^    str_city = city[m.group(1)]
    8 A/ e$ W2 ^* s0 O: J% p+ U    str_district = district[m.group(2)]
    0 [4 V, ]. Q. ^; R    str_road = road[m.group(3)]$ @$ ]; B' w. ]! k' A5 M
        str_no = 'No. ' + m.group(4)[:-1]
    & y; Z3 U1 A7 j1 \    return ' '.join([str_city,
    # C$ S7 [- p; |$ L) `1 ?% L                     str_district,
    : a6 D& L1 x9 ?5 n: \3 P& H                     str_road,6 Y0 W, c% R4 a" ^' i6 S. f
                         str_no])
    2 e+ v' c5 A) L9 [s.str.replace(pat, my_func, regex=True)* I9 p' g- U$ j4 u
    ; }7 D5 K8 f& I: x5 E
    12 G; O$ k5 K- X' M8 H4 V7 i
    21 O: C( t, \. A4 F1 z" v7 R
    38 e+ [- W- z) Q2 ~' k
    4
    8 g  P" E5 F. u3 l; \3 v5# t% }8 x, [1 L+ g$ V* |
    6
    : ^) E1 y! w# [2 f# Z7
    ; [4 G- O' V* I! m+ v85 e" S7 R* [0 @, L
    9* m# w6 j5 r. B2 U6 F1 s: T
    10
    5 `8 k& U+ u* ^( Y112 n" Q: _4 ]$ b& B5 X- u2 n
    12
    # b) u( v$ Z% c, G139 |- ?' Z: E+ w
    14" s+ m- c) x. e
    15: v9 U, y( p# |5 H" Y# I: _' F  c7 x
    169 g( C  Z  {; z  `3 ]
    17
    / J# w' Z1 y; r3 ?, K" I4 J18' N; T0 i& i* F
    19
    % d) S3 C) F2 z, x2 i* }3 e# D201 G1 t1 o& T( p
    218 s. w# S  l" F9 P
    0    Shanghai HP District Mid Fangbin Road No. 249; D* ~1 y/ ~# ]# n
    1           Shanghai BS District Mishan Road No. 5
    / G( G3 b6 B# ?% j- }2           Beijing CP District Beinong Road No. 2
    5 A" E$ ]6 J( U* mdtype: object, ]& z/ C, d  l4 r, }' Z
    1
    $ V' P  c/ r2 ], e; _2
    . m8 D9 m; k! `# E% X+ x3
    0 y$ d* B! o% w2 g# X' y8 n0 [6 k4
    ( k- Z* l) h5 \* l这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:) d' n6 T1 e, d4 }2 P! N

    * e" j, l7 x6 ?( [  j8 d# 将各个子组进行命名
    : F0 ]1 [9 e% K, [1 Dpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'7 _6 X9 ?) J4 x# S, l
    def my_func(m):% ?' L2 a4 K7 C# B3 E' p6 e
        str_city = city[m.group('市名')]2 A3 K# Y& @  x
        str_district = district[m.group('区名')]
    * l# r1 _; G- m; i" k2 Q    str_road = road[m.group('路名')]
    ( u, `9 N: _; f# n# ^    str_no = 'No. ' + m.group('编号')[:-1]
    ; Y% |9 P& U& f  P8 I    return ' '.join([str_city,8 n. q& [! c& v7 b! h( t2 M
                         str_district,/ M. `; \; p0 D+ S( d
                         str_road,
    , Z( o1 N+ j& Z5 K1 S1 `' C! N                     str_no])8 P9 d: ^" S/ \3 d6 Y/ {6 Z8 K
    s.str.replace(pat, my_func, regex=True)% A: S, q# `, E* e/ q. |
    1# H% s) e5 x3 V
    2
    ) V4 i' f. {8 u+ M  B- J. o. ^3
    ! A6 i' R. U- p, Q2 {. Z4' q$ e$ U/ h) D4 N3 u
    5( G$ I8 ~8 R+ j3 J% u
    6
    7 I' e( q2 `2 }( O7
    3 u' K- U7 S8 D  ~9 Z8$ A! z( [% ^8 f8 {' K$ k
    93 f( M9 y. K+ w
    10) o0 r, t8 g( b2 f0 A1 s* I
    11
    ! j. K; ?4 D( h$ Q1 W  x) U3 P12
    : h, O- q$ Y; y- S$ p0    Shanghai HP District Mid Fangbin Road No. 249  ]4 ^3 R. A2 A1 c( t
    1           Shanghai BS District Mishan Road No. 5
    . K  }; @) A' V+ ^1 v% A* |+ W2           Beijing CP District Beinong Road No. 24 v8 }) W) }/ _" R. ^( B
    dtype: object
    # P2 U5 c5 w* _5 p8 i6 D2 u6 Z% t1
    9 ^. S4 X& A8 g6 [27 T+ t7 \! [0 i$ Q1 u4 w, j$ b) W
    3- k) q. A% }" K# U& s5 i0 P. @  f
    4: f2 t0 \3 i) X% M4 J; e
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
    9 h) K7 Y9 M: r; H4 y
    ! Y. ]" B% u( m( @& q8.3.5 提取
    0 k3 q0 P, ~  W9 Dstr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
    ; J7 }$ `2 U6 W% w2 T0 n6 h# Ns.str.split('[市区路]')
    : D: b: I# Q8 v+ zOut[43]: - i7 p6 [5 l9 _% e& @1 V
    0    [上海, 黄浦, 方浜中, 249号]
    2 Y# H0 p+ l1 P+ `1 M1       [上海, 宝山, 密山, 5号]
    : u" k% `9 w5 wdtype: object
    , I6 q0 w, m3 N& C
    ) F& C+ j, r& t8 P+ |! c$ M) Ypat = '(\w+市)(\w+区)(\w+路)(\d+号)'; N: \5 v( f$ Z
    s.str.extract(pat)
    ! [# [9 b1 C$ Y$ v% QOut[78]:
    / r! X  S9 E) ^  E2 ^) h, J    0    1     2     3
    . r2 d% w/ B, H  w' y2 y0  上海市  黄浦区  方浜中路  249号
    5 L0 c6 ~: w8 l$ b2 x( |1  上海市  宝山区   密山路    5号& i- u! H' P3 y
    2  北京市  昌平区   北农路    2号1 _0 g# U3 `9 ~* U7 T& |" M, }! j
    1
    5 }$ U+ X1 }, I! J  X% n2
    7 T* U2 w/ O! x& F4 c9 B3: P. N  A0 `7 B( B
    4
    " Q6 y6 S  T' a/ |2 N( A% z5
    # H9 g) l( J, d1 X6 ~6* ^: g. _6 u/ q0 w  s4 r3 n5 t# o
    74 n7 S, D! I6 O7 U( X) Y
    8
    ( e7 Y3 s# ]3 k3 Z) f6 B4 r7 C96 p2 v4 q9 L1 O; n( K+ u
    10
    / M0 d# j/ x, d/ o+ V/ g11* Y& j; |. W. O2 Y2 S
    12' q7 l: {3 I# i4 M/ Y3 k% H
    13  U" J+ ]  ]9 f3 o" A9 |
    通过子组的命名,可以直接对新生成DataFrame的列命名:
    ' C1 t" s% x, Y) G- p* C8 x" H, M
    & r5 L- v& V. o4 v- r/ Zpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    ; \" F1 Z7 x# Ss.str.extract(pat)
      x& Z: x" D' m  z5 IOut[79]: 4 }# I. [- v% p1 g0 h7 R
        市名   区名    路名    编号
    : k# g) M+ k" b4 e- `0  上海市  黄浦区  方浜中路  249号
    0 E5 F$ s5 C: M# [  G- {& F$ q- [: h1  上海市  宝山区   密山路    5号0 X8 {) r" Y& H( F* h9 `9 I5 K
    2  北京市  昌平区   北农路    2号/ o8 i- [$ }4 p- [
    1, D* x1 a8 N3 ?: u9 _. L
    2% U* a; x/ ]- j$ k' k- G
    38 G' {" c1 h! m$ S; j
    4
    8 T9 f9 E. U, z1 s4 E50 U% m. f) W" \, a- ~
    6( T4 b: B2 ~" @, ?8 d
    7
    9 p& w3 F- X  a3 y7 L4 ?str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
    ) O4 z8 h% r5 |; c7 ys = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
    : ~! w) S: O+ m$ B) M6 h+ _: Vpat = '[A|B](\d+)[T|S](\d+)'
    ' a9 r3 O( O% ?6 m9 \s.str.extractall(pat)$ }3 y7 m' l2 t: ]. J# O$ ]
    Out[83]:# B; {/ S% x1 L
           0   1
    + z+ G9 \  |6 F; i6 N$ J% S     match         2 f/ G7 |& K2 x; {! `
    my_A 0      135  15
    # b6 a: U  d" W( j4 p- g% _     1       26   5! j* O$ f: G! Z; A. y6 i
    my_B 0      674   2. C. v# ?; e' D2 K6 q
         1       25   69 u3 N% j% o2 x* U0 Y
    1' }" j2 f: J3 l  ^5 p
    24 }: C$ z8 n- H$ _: I
    37 [3 {- z! ?4 J" }2 P! h- u) O
    4
    : Y- D/ d+ k: D' n. c6 E5
    : I2 d( i, D& ^6 y) \# }9 ]: C68 m) w( D9 j0 V+ O+ Z9 p
    7
    " c* f# V# |  s. J" r. N/ _8
    * |* p) @6 [- g1 k: r( D3 g9
    , c" q* y4 C% x% P8 M" ]+ }+ l10
    + N% D3 U8 r! Y/ ?pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)') g. c& I5 f; L
    s.str.extractall(pat_with_name)
    ; s: k5 W+ N3 i- m8 Y9 |Out[84]:
    1 e+ t) t* k+ L& N5 T/ ^) Z           name1 name2  g$ W# g% N. F0 G6 s; z4 f1 n4 n
         match            5 p0 s2 o. L- H0 P- X6 U' P
    my_A 0       135    15
    , R1 V8 G5 c$ O  H     1        26     5
    " Q4 B) a. h+ d' x3 g7 bmy_B 0       674     2& D; _2 T, S# M& h( d
         1        25     6  f# d! E# S# [
    1
    $ I& _3 [  k* M2' Q' K$ r  y3 N6 O% ~, }' V
    3% I9 R2 J# v- S& o
    4! U1 R9 T& m* N+ [0 y9 [
    5
      G6 Y! R+ e4 {6 }# G' B6
    , d# E7 B4 ^6 B6 I! Y7
    0 l/ P9 C6 a( d- i/ ?5 B' X, Y) i8
    * L0 R" Y* N5 x2 ^9
    ! s0 `# r: k. f& m+ y3 [str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
    ' r7 g6 K* \  ks.str.findall(pat)$ f8 p9 g; s3 N# `, Z( I! E
    1" z: {! j0 w$ W7 t$ C  S3 G
    my_A    [(135, 15), (26, 5)]
    . d- P0 l/ ^) [0 q: Xmy_B     [(674, 2), (25, 6)]( P. ]; _! G# W* k6 v
    dtype: object! @4 d, B! g; _1 O
    1
    ) m  _) t- T' j1 Z) Z2
    ! p. A8 B: L* Y" I( T+ V7 o1 f39 j5 u+ b6 Z' @8 t% V' Q
    8.4、常用字符串函数3 m/ ]5 d7 s) w8 e% y- V
      除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。+ }  v- e; L8 g  k

    ( Q1 s. |( W3 @. l# g6 R8.4.1 字母型函数, f! m4 y6 U+ e
      upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
    ; ~) n# O5 l7 ~4 u1 |
    # p7 H3 W, S' a' }! i' _/ }s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])" q- Z% o  w) {" t  `9 e+ g. N

    . t7 h$ I  l8 ]s.str.upper()# w4 K* H4 p( y1 G8 W
    Out[87]: ) n, i$ }9 \6 z- _# \
    0                 LOWER
    6 `9 Z8 t% X9 m$ h2 v) ]+ Q& s1              CAPITALS9 {. ?% ]! p& G' [6 E
    2    THIS IS A SENTENCE
    8 Q' M% j( o7 [. L- Q( `5 L. h3              SWAPCASE$ Z8 ^$ F" D9 y2 P. y& I) }
    dtype: object$ i( }8 t8 V2 `. ]

    9 I! Z: J( ]# ~. N) As.str.lower()
    5 m' I5 }7 S& I' m: AOut[88]:
    4 m0 N) p6 L$ g1 h0 T' e/ ~0                 lower
    * a/ a8 B0 c3 z' i# y1              capitals
    3 m' C5 F( r/ S: u2    this is a sentence% A: X) a5 Z0 \3 ?6 P
    3              swapcase- {9 }7 Z; l6 s. Y# f
    dtype: object% G( T. h3 I3 a

    5 y5 T. m/ G; z. A- m2 v* P, u# Ps.str.title()  # 首字母大写+ W( _7 i- H8 Y" g6 e: t* X! {" D
    Out[89]:
    0 F" v" [& ~$ K- [& Q0                 Lower
    : ?' R% S% v# Q$ i! r1              Capitals
    & y2 a4 {- h  G6 a$ R( q+ x2    This Is A Sentence1 f" y* B( O0 u( t7 J& X
    3              Swapcase
    9 F* w3 t6 p% f/ c  bdtype: object4 P( f8 H- b  }( i# ^! i# r: |2 E
    ( p8 ^- Q2 s* i$ X. W) [: K; C
    s.str.capitalize()  # 句首大写+ f" {& h  X% M4 x# w8 Y( m
    Out[90]:
    ! t+ _' t" W  L( D0                 Lower1 s- a: Z) A( c% p5 B# n1 f$ m) k
    1              Capitals  j+ X7 X: x5 C+ S$ o( X
    2    This is a sentence: J4 F6 l' G$ `: Q- S; n3 ^
    3              Swapcase& v: C: k+ U4 M0 p; u& j" I0 p
    dtype: object6 f, D7 M( c$ ^1 m# j" Z$ |; f
    3 L( a  N: U; ^5 _' a1 n
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。# A6 z2 C, |* h- _/ i' `
    Out[91]:
    ) G0 l1 Y+ H! K* J0                 LOWER
      P7 l* r3 V+ q1 P1              capitals' e6 q* e/ H6 l, L0 @. x. J5 _
    2    THIS IS A SENTENCE& {: r, C5 v4 q4 V$ {
    3              sWaPcAsE; I6 Q, e1 N: O" O3 X
    dtype: object
    3 T' @9 M7 X" j3 d% B& n  \2 Z8 b( ~3 I# S+ {( j
    s.str.casefold()  # 去除字符串中所有大小写区别
    3 B- D% c3 P- C7 i( w
    . j0 g% I+ h; p( ^0                 lower
    8 |6 e# X# j% k3 C$ ~1              capitals
    4 S  H, s1 X# I2 W% i: T) {2    this is a sentence
    7 ~9 m: k. T0 c3              swapcase
    2 v/ s$ b) Z$ V( j* @; X# [# ?
    - O8 [6 U. W9 M$ T1 ]' L: H1
    ; [& \2 ~/ O. u$ X+ D, \, l27 B5 E0 ?+ E7 N, j7 S
    3
    0 e' s, `3 W; M7 S0 ]4
    - r7 z. F, d* ~! `) I5
    # o5 w' o% [$ p& E" o6
    2 P) S0 P9 O2 ?6 F70 Q6 ^% O1 S+ D. _- s8 F2 [
    8
    $ }& K$ _. G& A3 x/ ?. I- \9
    , Y9 K1 S* e7 }6 l10
    ' q" V# Q+ n% N11
    ' k/ m1 p. K/ {1 }& @) T12
    " z1 f0 c! m. F% P, K7 Z0 N* t13
    + |" D+ ]6 z4 D& H$ h140 s/ U. Y, c2 p7 }3 Z/ b$ r3 G
    150 b' R, Y# i0 J0 z
    16
    ) }- `/ G8 s1 t  p$ h17
    4 V. ]* f  \+ h$ ~18, W! I# t5 n3 ?
    19
    , a- b. k& a& X) i4 H5 V* @20
      C2 L3 P4 V+ y21/ I7 }* x% G2 g. a; ~
    22( v8 K( G# `: G" }
    23
    + c- X6 z4 t  y, F- z% L24
    - J! h' i, ]$ W4 R! [25% @) D$ k4 @& ]  k. b
    26" _8 G$ `. h9 B3 g, i3 Y
    27
    2 q' h* D2 \3 b286 _/ {) m( ^* @* `
    298 l- A) Q6 T7 h. o
    301 ]& o0 R( r, }
    31
    ; f+ z" h6 @: f1 L' ]4 A321 a( N; w0 q; J! X
    33
    3 t! O  D  d* L  v345 p6 c$ Y" z/ }( |3 v
    35  R& i/ W# r+ d* F: p
    36
    3 h# e1 j( V  \! |: o1 \3 n  b% C37
    - H5 [1 K4 o! c& l. p38
    9 N1 U$ M, J5 B" |6 `39  a& Y" }2 @" S  I
    40
      z& e3 \0 @& U- v% v41( ]! T' R' x1 `3 W' P8 D3 k6 G
    42
    ' z2 H: Y7 B% z438 [( [$ a5 w; l% t  m0 {0 f
    446 A! r& S  n2 |/ ^
    45
    % L! s  t$ A' [3 l  n+ O465 Q# ]" C+ _7 _" _# l$ g" a
    47( n! f& m  ^  i) @" s! o
    48
    7 A' H6 j* [+ w8 B8 r( E7 l4 f8.4.2 数值型函数
    " C6 t/ ?$ y2 ~4 N' P& B  这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:! I  t, S  F, Z
    ; G  X9 `# N0 |6 X$ W
    errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:; O* e- R6 _: y+ L  M; E! s5 b
    raise:直接报错,默认选项( n+ b. [8 H# o2 ~& A
    coerce:设为缺失值9 z1 y( i. c8 a, V# q0 d. m
    ignore:保持原来的字符串。$ Z' t0 J8 u+ e9 I$ W
    downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
    7 t' S9 R1 H8 }9 j& b: Js = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    ! g2 m7 P4 E3 m, l7 y+ H4 E8 _$ [1 p, X& ]
    pd.to_numeric(s, errors='ignore')# `3 ?- h  t( |1 Q
    Out[93]: / e  }/ f7 G% H: n+ ~& H
    0       1- F4 h# K7 L1 Z( A
    1     2.26 V# `5 B* L0 e  \( N
    2      2e/ _: i1 q+ D1 q4 o4 m, l+ j
    3      ??$ {& z- s& A0 g; p/ Z5 X
    4    -2.1! e7 A$ ]3 J; r- ]$ ]
    5       0
      N+ w  H3 R& e: _4 Q! @6 Cdtype: object
    ' n. [- T% ~* G; r% {
    ; f. u3 b7 i+ _+ u6 lpd.to_numeric(s, errors='coerce')
      T5 A' x% x; |+ w$ s* k" c* g9 UOut[94]:
    3 c& _" I2 M  }- |' T* @, M  R4 I0    1.0
    9 Y# s) |/ @- s2 U  u: E1 r4 p1    2.20 f' Y. g/ b6 ^( z) x
    2    NaN
    8 e$ F. h. A1 B  l3    NaN# L* K1 b4 i/ D- v- U
    4   -2.14 w: |: l( T, M; h3 x3 g
    5    0.0
    . F, d, Q# Z6 [6 U% Hdtype: float64
    * h9 Z" d; i' i8 J& J+ |: c, F
    # a/ h3 y. L6 L' v& l14 E9 N6 k- k" c( Y7 m2 V
    29 J7 D: G# q5 K
    3
    % c& _) G- M# X- P+ L5 {& Y4
    ! X0 ~0 _5 ^+ S# X5 }0 U  A- w5
    - t. C+ |+ n# C! P" c& U$ r6
    ; }* v1 A2 r! s. t" m$ e7
    ' l( z" V! n0 M$ G  n+ s. I8
    6 W! I& B( s1 B7 O4 l: a9
    9 O/ f& N$ h% x# |+ X+ o9 y10
    : f3 Q' U4 ^' @1 o: }118 R0 ^5 g7 c+ f: L. p% c2 |8 s
    12, M/ V. q1 r. f& e$ n: v4 G
    13' c4 I: c5 W- U
    147 H9 }5 K& r& i& P) i9 v9 U$ k+ k! Q
    15
    ) l7 X( ]) e& T6 O0 k( ^) A16- V5 _5 q; z2 D
    17) F5 N5 _; o; o# t2 Z5 I
    18, a  ^: a; h# S) X6 a
    19
    7 G9 T9 g$ s8 X+ p+ a/ G: U4 Q207 I3 v/ g6 y, S! s
    21
    ; ?# u+ d/ d5 S/ O  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:: e% L! G3 H' ^/ V5 f" Q0 p" W

    " [: {% E5 v, I: ?& Cs[pd.to_numeric(s, errors='coerce').isna()]
    ; `1 F! q+ V) n; @! S% rOut[95]:
    2 c7 G* m8 A, _3 A# L/ X2    2e
    . \' ~. [0 u7 u; R6 E" S9 F3 U0 F3    ??
    ' Z2 Y" n. a/ t6 l: G9 ?2 I$ _dtype: object
    + W; _7 V" X+ F9 i5 i+ }. Z: k1) R! p; r* ^# h
    2
    . q" K- {1 p. U. E% h3
    + w4 V: ~& O$ u4 Y. i" T4
    : X" }! i. a$ E; [7 e; T4 u51 ~& r4 f8 c4 }
    8.4.3 统计型函数
    $ F. f+ o2 F0 q, o  count和len的作用分别是返回出现正则模式的次数和字符串的长度:1 ?9 b) Y7 A. }8 M% J0 x

    8 v4 n0 w- O6 r8 ]$ Z6 L- D+ q2 vs = pd.Series(['cat rat fat at', 'get feed sheet heat'])" R" _6 p0 j- [- R1 q1 ?
    ; A+ _0 a+ O2 a* v+ }
    s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次7 R# H' e" h$ ]; H3 |( S& [
    Out[97]: ; h1 K! M- d- P% N% s5 J2 _0 e3 o
    0    2. W% o% _3 Q4 s; B3 L- c7 Z
    1    27 F8 j0 a) Q' s; b7 s
    dtype: int64$ c8 i' A4 e3 g
    ) d- |5 R/ I1 @5 g6 C
    s.str.len(). ^' `0 K9 ~1 d- S( |7 \, H. H
    Out[98]:
    ' E! W( {4 B& E, |6 J0    14& Z, W0 k6 U& N& o# P# d$ o
    1    19
    % A0 X, x1 B: D6 ~8 T5 Gdtype: int64
    : x' [' y+ Z/ @3 L) N1
    ' f) K2 r6 Y* l1 r+ z7 ]28 _% o! W# o1 r( f9 `
    3
    1 I$ ]# K# [! f; C8 H4
    2 Q' K1 x4 M5 e; \, ?# @9 E$ @5
    5 A( W0 f( h, X, c/ L65 V5 y# B+ v3 }8 P. ?7 k0 ~7 }3 J5 \
    7: l3 M; p1 l7 I% e4 \# ]
    8
    ; p4 D* Q* a3 o4 m9) `3 @9 ]$ V4 q6 J' K+ i& n# {9 K
    10' ^% C8 F! I: H6 e
    11
    + t  {+ h9 g4 ~- E, `2 d6 _$ O126 v9 u, N5 ?+ W
    13
    / z. x3 L. _# O5 z8.4.4 格式型函数
    9 m7 v$ k* ^2 b2 a! c3 o; R  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
    0 C" @" F/ T  W  W, _6 e6 e9 q
    my_index = pd.Index([' col1', 'col2 ', ' col3 '])
    4 V$ b3 [" j( R$ D. a2 i- C) A' m7 x' o7 D/ o# y
    my_index.str.strip().str.len(), t* r  R0 g8 f# m: ^
    Out[100]: Int64Index([4, 4, 4], dtype='int64')
    4 z1 Z$ d) {/ ]3 l2 ~) w1 S& @: ]
    ( X; i$ J( z+ c9 h; Cmy_index.str.rstrip().str.len()* U5 i5 P5 }4 ?
    Out[101]: Int64Index([5, 4, 5], dtype='int64')
    ( R$ Q9 i. s: n( B( `* y# \9 X/ h; E. K, `
    my_index.str.lstrip().str.len()
    2 ]; a- _; N! N: O0 t$ s- O" ?( zOut[102]: Int64Index([4, 5, 5], dtype='int64')
    3 o/ d% Q/ ?2 E2 A! T14 m6 \2 {/ V) S% Z
    2
    $ v$ [( x0 U$ e2 u34 X  B7 ~( k1 ^4 i7 A( O! z
    4
    5 [' [5 o, p3 O* ~/ W$ z7 k56 ^: [" z4 A9 j; x6 n" q
    6& n: r* E4 U' c3 a/ o& l
    73 v) j, T# Q  Z% G
    87 r4 o9 W; ]% T* ?' g, }
    9
    4 g( C# R, C  V* e7 T9 s# ~, S10
    , Y7 p. {  F7 ~; @4 F. e  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:3 |% P1 k6 G- D9 p! M+ V5 g

    6 t6 J4 [; e& e9 }0 y9 d/ y* ys = pd.Series(['a','b','c'])6 D) t* r% H7 i% F3 V9 ?
    3 R( w2 e/ l+ L; @/ b- {
    s.str.pad(5,'left','*')& Q' Q4 k: G; h0 }/ X' T
    Out[104]: 8 b' s% V( A) i  X) w: X
    0    ****a
    . z4 n: g# r8 K, S0 H1    ****b
    # I4 a, }6 f3 `) f( n8 Y2    ****c9 c) q1 G/ C8 y  A, C4 Z
    dtype: object
    & M  s9 ?6 q% b# P2 O9 `& `- i. J1 @9 v# _! ~6 n; |
    s.str.pad(5,'right','*')9 Z9 f, C* L' x
    Out[105]: 5 y0 [7 ^5 M/ ?' a  G
    0    a****2 P- \8 u! W0 z; ]2 @
    1    b****
    7 A0 u4 h7 w& q2    c****" W* @& l9 H: N$ F. N( f
    dtype: object
    ! f3 B0 e1 C; E% B2 g2 [3 K
    % j. v, E# O: u7 qs.str.pad(5,'both','*')
    9 D" F' N: m+ l  r/ D: C7 gOut[106]: # P/ a$ d0 l$ r5 R' t; h+ f
    0    **a**
    ; h1 Q( I, U% J, s1 r! E% B# @$ {$ i1    **b**
    # r- o7 Q0 u" m+ g3 D! V& i% H2    **c**) u) V, }9 g! J" y
    dtype: object
    2 C* q' E6 R6 c. M. `* c! G& p* b8 h! H  p
    12 y* \3 K* V. e8 ^& K) B, k' \& v
    2
    3 n; u% G- X+ m; A: x! d3
    / f! t* w' T. z1 X8 p6 X4
    / w% M' n; z4 W$ b9 L5, O# l7 F/ {) a5 `% Q+ Z& `) V
    6
    $ F% M. C+ S' B- k7% S1 s$ A( }% c& O: O/ h6 P
    8
    & J& a" K' h7 t3 N6 m* @99 C$ e+ s/ Q9 A& d: @( t. N6 Z; x/ K
    10# m: v8 e% b  [* `- S3 g
    11' v- E& n$ I. p, G: d
    12
    * \5 B5 D# ]$ A! Q130 t: F9 a% H) ^$ A& z
    14
    ! ^: C* {, o. d/ M: w$ q15. c) W; [3 h% A4 V
    16
    ( ~! m0 M2 {$ R2 f' K) Z17
    . y# q) C  P+ ~4 z, e3 s, \18
    5 F& F' Z' S5 h19# X- x9 ]5 L$ M; _$ ~9 K
    20" }& R* Q: z& D0 E4 T6 q  G
    21* w; D+ I9 R) a
    22
    ) |- S8 p3 _9 d: |% b0 B  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
    # d- @9 ]( C) h3 ~2 l% e) E3 ], a, K7 o; W
    s.str.rjust(5, '*')
    0 M; N, [/ ^& T4 O. V0 M# }. t) W5 W6 |Out[107]:
    8 j6 v' [2 P, O+ c/ G0    ****a# [3 [6 m; U( p5 s
    1    ****b, d$ P0 c6 \: [* z8 G' z7 X
    2    ****c3 A( q" t5 R: L) l7 R9 J
    dtype: object
    ( U! X6 z/ ^% W% ]4 q2 e1 g, Z) n
    s.str.ljust(5, '*')" f! x$ u% X/ J
    Out[108]:
    4 v5 C4 g2 y( x$ t0 R1 V+ x0    a***** {  [. ^' t( E" E. U! B' o
    1    b****& S% i- O4 u" `' ]3 I, y
    2    c****
    4 \# B+ e* ^! n0 n% Cdtype: object
    ) w9 O" n0 N, m" d. w
    1 D  U5 n8 e, w9 ds.str.center(5, '*')5 o1 Y+ n: _2 m2 r& ?4 y: Q
    Out[109]: 1 n5 e7 P0 O" ]9 M
    0    **a**
    * n: C( o' ?  k( G+ @$ D7 U4 _1    **b**
    ( ]: l- L* T* t2    **c**
    1 H. @- g; l8 ^" F- X  F5 Ddtype: object
    . U( k( ?3 H# k# I7 `2 y8 R2 M5 U0 Q; {/ O
    1) w0 g+ T5 ^8 u2 G4 r8 T
    2( O9 F' ^5 [6 N$ H0 x5 q
    3
    * S$ p4 @- m" K6 f0 j5 Q# p4
    , h9 X+ h# M# A8 A5
    / R; J& ~3 z# R2 R& Y! ~67 @- K4 I, ?# A! d% Y. i0 R
    7  K" F( b0 ~8 _- `
    8
    0 C, v% X- @+ S+ E* b! v4 [7 `98 ?& E$ m: Q* E* x) m( Y4 x: [
    103 w( g$ c0 ?: c! k
    11
    9 e: |2 c5 ~' p; R) u: p, Q6 m12
    ; F5 e/ S! i8 R13. W# \0 b* o8 P; M" E+ _; R
    14% u8 g( |) w' a9 G  N, v- m; q
    15/ Q# x5 f1 s. a  O
    16
    , B/ I; o6 L  n8 F6 C. A17& i! N3 {1 H0 m* z( y' D. N
    18$ A7 O4 I- c+ B( J
    19
    4 u$ u. v: Y2 X) O: F20
    $ {+ F1 Z1 V8 v% \  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
    " `# Z2 J4 V$ E; c
    ! N8 I# o1 e4 C; h% }s = pd.Series([7, 155, 303000]).astype('string')
    ; M. Z" R* c1 y
    / e2 i% f2 b% Hs.str.pad(6,'left','0')5 x  ^) V' {% ~
    Out[111]:
    8 ?* E% U) x+ @5 b( F4 c0    000007
      m! ^, \' W7 V! u2 _. H. w1    000155
    ) M, P7 u; ?9 ?, q! b2    303000+ S/ F1 [1 \7 d
    dtype: string& p5 J7 d) I3 P& A1 u0 ?
    6 w* w9 N$ {  A
    s.str.rjust(6,'0')$ d' W; m3 b5 r% o' D/ e1 T
    Out[112]: 6 R3 V3 h7 ]! e. J
    0    000007, {& V7 f& r1 L0 K# o
    1    000155# T. l( V7 [% M7 s: A1 P  f% Z! G
    2    303000
    $ p! t, j7 \+ Ddtype: string
    4 ?" P" I5 ~6 G6 z- ~# x3 Y7 x' N  y4 u! k
    s.str.zfill(6)' \5 R2 r: k4 d% t* @" M
    Out[113]:
    0 u4 B6 K. {# V; ]5 w0    000007
    0 h6 e& q8 j. u* y+ {. o1    0001558 W% Y( ~) ~, l5 ^( o3 r
    2    3030007 L- a) w$ o, y; i) ]
    dtype: string
    3 S' a6 X0 D: u; C- T- b9 ~  a' E) f, i9 Y7 |/ d5 l0 l
    16 W* F( X* |& A3 S- a) y
    2; z" a! I6 a8 U# N1 ^
    39 E" a! ?: g4 F3 ~0 ]/ D5 U7 w
    4
    % B; _! H/ U- P! b3 _8 T* I8 a  K5
    ! j$ o/ B( l- g6# t; E8 ~" M( Z- S
    7
    2 h+ \- v) N) h$ G8 C: z* s8
    . x0 \$ v8 P) z; g) }" s1 u" }$ u9
    ' c* u" U# q2 V, c: R  Y( E102 {  g: `1 U! v! x. d
    11
    . E0 y' |5 g4 _/ X8 f; U12
    3 G7 M: d! n) r3 E/ z. J; _; J13, \1 c5 S' U; k9 _/ E
    14
    9 {" i. z. F( C. h9 H/ i15/ p6 P' r7 y: {
    16
    7 M) N, n' S2 {& Z; ^$ ^( `, B17
    3 [& a2 V$ s# ^; K% i187 t2 c% a5 m8 @
    19
    : Q9 t: o$ U1 T$ O2 h* \; d5 X20
    ! [5 o& [; U3 X+ v5 P21
    8 f1 R! W/ D: `' I% f222 ^6 U. h8 |- w
    8.5 练习9 ^" l4 G0 W. u% A' t% b# O! F3 M
    Ex1:房屋信息数据集, ]" J/ Z& w7 o4 ]5 O
    现有一份房屋信息数据集如下:
    0 _1 z% N( b0 ]+ `
    # \2 _  N: G) f' Odf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
    ; ^: l( m6 P# Zdf.head(3)  g3 Z# `  [) y: L, d* ]3 p
    Out[115]:
    2 F! n3 D/ ~: P( ^      floor    year    area price
    3 Y+ J: L1 y% `; Q" h0   高层(共6层)  1986年建  58.23㎡  155万1 m/ ?) ~; P7 ~  Z& M
    1  中层(共20层)  2020年建     88㎡  155万
    ; z5 Z( Q( a  [" |+ u/ c! B2  低层(共28层)  2010年建  89.33㎡  365万
    : e) Y$ ]1 e: i1
    $ n; d8 W8 a# f3 T24 `7 I" }' N8 |+ a( \: D$ m
    3" y% B/ }: ]( E# R
    4
    ! Z* e; {8 l  P% G4 _5: Z) b1 y" j: p
    6
    " O1 d. i( Z! l9 k) A: ^" m' C1 D7$ _1 g4 r2 Y) {. E" A! O5 x
    将year列改为整数年份存储。
    . c: _# J# V# f& o将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    ) Y9 Q' l+ Y) p$ D计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
    7 ~; J. s" W/ \# H4 a将year列改为整数年份存储。
    6 s0 Y6 V0 G! G/ r! B3 K"""- e3 E/ i( [- v
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。7 w8 W; x( q7 B# q
    注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
    ! U0 ^: O1 ~1 w7 p3 X9 g转成int后,序列还有缺失值所以,还是变成了object。
    - [5 ]; v2 v. v9 c& c, j& |- y6 k而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
    7 C% D7 W  F/ ~! d" a& T$ ["""# L) u* f3 S' i: e7 ?4 I
    df = df.convert_dtypes()
    # i% c- v" U% H) G2 Z/ W$ zdf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
    1 `1 v% h# Y  a, z2 ]* U2 y0 _df.loc[df.year.notna()]['year'].head()
    ( ]8 M+ m6 B9 c
    8 d, e8 @' C$ F6 c0        1986* L( S/ }- F1 R
    1        20205 B7 Y1 T0 P! q. h: j* Y
    2        2010
    , k3 f9 S$ N8 M. ^& b. W- d6 I3        2014$ |9 p" r- M5 b: R* N
    4        2015' j  o/ g3 c# A& s/ H# ~9 M6 }& ?
    Name: year, Length: 12850, dtype: Int64
    4 q, S9 h: n4 i2 N) |$ H0 O( X8 W, O: O7 B, L$ ^. s
    15 M% k8 R9 t+ J: y/ J
    2
    3 Z1 p6 r% @0 y, X: @( m33 u# O/ ~/ N* w/ r
    46 F; m2 g6 y5 k% F4 E0 o& J
    5$ E) {- [  U  k
    6
    9 w  V! A/ T2 @7
    8 V$ p8 J  ?  R& c8, n7 _( j% i" Q) ?$ \* n7 x, y
    9' ^) E, p  X0 y" E
    10, Q. L( a7 Z/ ~0 b
    112 k3 a4 D5 T& l. W; \) e  s, }, K
    12. F- l  @! e/ b1 J
    13
    / ]: z( r+ D& F1 A" m14( A" D( J4 _4 ]7 X' {
    153 o1 |2 o$ E' d4 c
    16
    5 x: U) a- K' i' ?  ~" e% S参考答案:
    * u1 T) X% O9 S! |& d2 u/ v+ T3 s- O9 K
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
    5 W: o  F3 I1 p' X* l
    3 B; h$ q9 t$ E& I: Q5 kdf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    1 ?3 F8 u& h& E: S2 Zdf.loc[df.year.notna()]['year']4 d+ d# e$ G& `' r) C" s+ X) E' U* M: ^
    1& u" v6 a! T' l. e  o7 \
    2- b1 n5 |) O! o6 C% @. r  l5 P
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。7 P6 q" Z2 `( o6 ^/ d( j+ P
    pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
    / _0 m9 C/ s# @. d8 edf2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次$ k8 T* p; x  K7 }" R8 {7 L( w' Z
    df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型
    : o: `1 n4 N& V6 \df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              $ g- C- T# F" `. X- R" L$ f
    df=df[['Level','Highest','year','area','price']]
    * y9 Q7 v, {2 m1 Z1 pdf.head()8 t4 x  |/ Y* G6 L
    2 W) F; P) M5 X: U# m  S3 P* L
       Level  Highest        year        area        price6 J8 Y. E3 x# V' K
    0        高层                6                1986        58.23㎡        155万3 f: h* |; w- b5 z
    1        中层                20                2020        88㎡        155万
    ; l+ R" {( A% K2 I2        低层                28                2010        89.33㎡        365万2 b5 L$ ?0 P/ n
    3        低层                20                2014        82㎡        308万
    ( K) b/ f8 ^' Z2 y, M5 p4        高层                1                2015        98㎡        117万
    . I, b" C: ^" H1
    # E5 }% Z" ~: Y2
    ( B/ c# T6 h3 a/ g3 w- W3
    ! q; c  t; v  f$ v6 Q1 l; w3 H2 r4 d4% \- q! W* {4 a1 U
    5$ D: k+ F4 w) t% s0 `8 i& \5 x
    6- n0 W: e% n8 U
    71 d6 `+ ]2 G- W. |+ }
    86 A; m- B6 C* b- V2 z* _
    98 P) i: W  j6 c
    10
    3 _6 I* P( w& `# g: Q& P11
    1 @' q6 j- s0 g! L" [$ a1 m  P1 `123 m0 _9 B4 X; ]0 H$ X3 Z
    13: \: e- D. g6 ]7 P7 V
    # 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了1 z0 ]# J# P# V& q; G
    pat = '(\w层)(共(\d+)层)'
    7 y- e' s& n+ e7 W5 S( _3 cnew_cols = df.floor.str.extract(pat).rename(
    . u2 C: W+ `' X1 e. k, Z9 F+ W& ^0 {                    columns={0:'Level', 1:'Highest'})
    1 u+ Z; G- v! T7 p: F, Y; Z- \' K9 O( n8 F5 i1 k
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)* N2 O* k4 U/ G- u( {
    df.head(3)
    . }5 i& s; R. M) F( u- z% c/ l7 a, V
    Out[163]:   P: W2 }3 A( z. C' n
       year    area price    Level Highest% `2 u. {1 S; B( V* U
    0  1986  58.23㎡  155万    高层       6
    2 r2 K' h( c9 A/ h0 [- q. _  e1  2020     88㎡  155万    中层      20
    " w& c% D5 J7 U1 K2 f. z2  2010  89.33㎡  365万    低层      28
    8 h0 G% a: m$ l9 X12 i% v3 c5 }; m- Q! o. N
    2
    2 E) J* D6 F9 X$ i4 N3
    ) c+ O) l( u7 }. ^( g49 h' @3 G8 X- }. h0 C
    5% d+ o2 N) n- w* m
    61 S* h2 |  t0 W9 H) N0 X
    7) h8 Q0 P& b% @: ]0 ], J
    8( U+ `3 B1 h/ O# B& T) O
    9
    2 n. B1 ^: P8 X) |10# e. u7 |6 L, R) J+ O
    11
    . g  V1 Y! n8 m$ ?12. ~9 o' H9 [7 g: J7 }! J
    13( m8 Z3 ?* a, i, H, C) p5 e4 Z
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    8 ]/ h  h) ]8 m. D"""5 A& f- W3 v8 ^# \+ h0 ]
    str.findall返回的结果都是列表,只能用apply取值去掉列表形式) V1 Q' b; O" T6 x# T4 q* t
    参考答案用pd.to_numeric(df.area.str[:-1])更简洁% T" H4 d8 _1 V+ D2 U8 e9 |
    由于area和price都没有缺失值,所以可以直接转类型% \/ l7 u, h$ Q8 @6 a
    """& k- h7 J- S$ Z  \/ p. i2 X  Y
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
    " Z8 u$ Q2 X8 Qdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
    ; b5 Q  a0 l/ ^5 U, l9 Adf.eval('avg_price=10000*new_price/new_area',inplace=True)
    1 w$ S  T( F( l9 i# e; \# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
    0 F, B1 j( a. {* `3 t# 最后数字+元/平米写法更简单& i9 V3 \" c1 q
    df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'+ K, J$ k+ s" d6 w( [
    del df['new_area'],df['new_price']
    - F7 r* h$ s( Tdf.head()
    0 s( @6 C' v$ M& V. N. z6 K; w0 N# C! N0 e/ [, t) d
       Level        Highest        year        area        price        avg_price
    4 z' Y# R/ `$ X/ j5 E, V0        高层                        6        1986        58.23㎡        155万        26618元/平米3 U5 H# L  w7 Y( d, G
    1        中层                        20        2020        88㎡        155万        17613元/平米9 w4 _0 `3 R/ t+ Z: ]
    2        低层                        28        2010        89.33㎡        365万        40859元/平米
    ! Q/ E  x# t7 \. L& L3        低层                        20        2014        82㎡        308万        37560元/平米
    ( ~) o% P7 N/ w5 N4        高层                        1        2015        98㎡        117万        11938元/平米9 x- K4 A$ T* ~; h$ A& I

    # x" E8 u; F; p6 L2 N1- H( Z: i: U; i  b
    23 G6 G! K( B5 {/ P9 _" P3 S2 U' ~
    3! m' \! z$ q5 W- W$ m/ t
    4
    ; m/ K5 _$ J$ n0 c5
    * A2 U: O% R  @6 C$ L) \9 S6) @. }: q7 B# X" r5 b  i$ b
    7
    6 R! d; S' c/ ~. |84 }" U5 u' ^8 _6 x$ C
    92 }$ t  t: g! E$ L  S5 z
    102 e& M# D' F: `, ^+ X$ }3 W' x$ r
    11
    , x. p; S$ A: s  F8 v12
    9 i2 n6 i! S$ \2 o) R13
    ) K% ]% j7 w3 L0 {6 `14
    0 a2 a9 u1 ^9 Z' _. |% C15
    ! }$ S/ N3 Q2 m1 T+ V16( Z4 y( x- G3 G' W* z# X) O; V
    17) p4 O5 _6 d2 z
    18
    2 m6 I6 `& `7 X% C& e# n196 h, L+ r" C, B$ o# A
    20/ C9 f$ D2 }; Q9 f9 s- r
    # 参考答案
    - _' B* f9 G# C5 S9 ]s_area = pd.to_numeric(df.area.str[:-1]), T" g% T; `5 V9 E
    s_price = pd.to_numeric(df.price.str[:-1])( k6 u! A6 s4 t0 ]
    df['avg_price'] = ((s_price/s_area)*10000).astype(# f! P' l% Z' L! D' C0 t8 r& n
                        'int').astype('string') + '元/平米'  u. X9 E$ u9 X# d/ c1 H
    . K8 T' |- p7 v3 B+ _. R4 s
    df.head(3)3 h, c6 T5 ]4 P. l% d% c
    Out[167]:
    ( d( ^# k; ^  L* l) r   year    area   price   Level Highest  avg_price
    : Q6 C9 ?/ ?% c: Z/ v$ g) l5 W0  1986  58.23㎡  155万    高层     6          26618元/平米
    " Q3 ]$ F+ }8 d% d) {. D" E* U1  2020     88㎡  155万    中层     20          17613元/平米
    3 j$ v5 v% F5 I5 \) F% ~2  2010  89.33㎡  365万    低层     28          40859元/平米
    ) F# Y9 ?4 s' f+ W16 I, h$ |+ H8 F) p( q  V2 N) y
    2) B- i# R7 t* u6 M
    3& u* Q* T/ |, ?* m
    4/ y! C; z7 `5 P/ C, X& Y# ~5 t
    5; k! c; t. l+ \
    6
    3 F) G; ?' y$ W; p, C0 Q7" n# |/ X8 N8 z9 s+ A$ D  j' Q
    8
    & ?7 K: H/ U" p, p1 R; B3 L* d/ V9
    ' N' n$ C$ R: J0 Q/ g' z10" D  n( P6 F5 G) a# ]" B
    11
    $ p  m) t3 C/ v- B" I129 p4 P. {3 Z8 H  V' f- e$ N; ?
    Ex2:《权力的游戏》剧本数据集
    ' i, ~0 {8 e0 L7 G; i5 d现有一份权力的游戏剧本数据集如下:, M" D8 x* h& A2 a* L

    7 Q( g) s6 D8 w8 G- [# hdf = pd.read_csv('../data/script.csv')% ~: l! X5 Z4 k* i4 u8 t: J5 b
    df.head(3)
      C& R; t: M7 a! R7 O$ W* s. b" M: z1 N: ]+ h
    Out[115]:
    % M( Q9 J7 X2 e" n1 TOut[117]:
    # I. o3 A$ x7 w8 D3 q' M. |/ M  Release Date    Season   Episode      Episode Title          Name                                           Sentence3 K: E, c" x& w- ?, C$ T& s3 m
    0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s.../ I/ q7 ?# q  q- k! L
    1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this..." A; u4 x7 Y+ ]& s, V
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce # p+ s2 a: z  t/ I, L; ~
    1+ X6 ?+ m# ~6 r6 x4 L
    2
    4 V( R# |9 ]7 e( ?; |% _1 J3
    2 ~: J) |" i* p: S! v4
    0 x# T# M5 d! _8 O" A# m' I5
    ! R1 P( ]% Q% ]5 S8 ]: G6
    7 Q: C( S5 s0 j: I. F7  X, ?' V  [7 h0 V: f: m7 j8 ^- q
    8! d+ @- o( L. _1 Z$ I: ~7 l
    98 U  f8 a6 \2 ], _* L, z5 z
    计算每一个Episode的台词条数。
    ! ~' _6 q) q& T0 T: d以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    $ ~  H  G/ w9 X# E若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    ! ]" H  |! U! w; |$ B/ J计算每一个Episode的台词条数。
    4 R& X2 n1 D& B' Kdf.columns =df.columns.str.strip() #  列名中有空格
    8 C8 X2 s6 f8 u0 L) u8 ]- \, V8 qdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()0 @  A( K" [7 G9 y9 O( S  f/ J/ ?

    9 ^4 t7 X& C' a1 [6 v0 Tseason    Episode  1 q8 R' j0 G" t, T7 W$ L
    Season 7  Episode 5    505
    ) l- O) x; o/ N5 ^. W2 ?Season 3  Episode 2    480& F& N: r& a3 r' C5 r) I$ u
    Season 4  Episode 1    475
    8 k3 k2 }9 H/ ]1 i4 U, ZSeason 3  Episode 5    440
      R" R; |+ m) u. d; \; JSeason 2  Episode 2    432
    . F4 u8 H# k6 c1$ z* y; R8 T9 E+ \" @8 e3 Z
    20 U$ I5 k# {1 u9 M) Q
    39 ?( S5 T4 i) ]. d0 s
    46 K  a' v' h4 `6 u! O9 u! z
    5
    9 G* j6 n' ]- O- @5 I9 H1 i6
    " e# e8 t$ M% K8 G3 b2 y0 w7
    - A, n  E/ D& D/ g# `/ Q7 L/ |82 b. Q& K9 Q& o% a% s- }
    9+ y6 y7 B$ G2 h4 K+ g- E1 e
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。& O) {# }. D0 r! o7 g& s- i
    # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数7 K, ?; ?! Z- |  y3 U
    df['len_words']=df['Sentence'].str.count(r' ')+10 O* s3 B- X- J0 h" |0 \
    df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()0 L0 C2 [9 [* ^& O% l8 H6 ]

    2 v/ p3 s" f$ u4 ~Name
    ' ]5 J# p3 e6 \, _male singer          109.0000004 q- W  M  Y6 N6 D2 S
    slave owner           77.000000
    " [( Z; r3 v4 n, v1 c6 ^manderly              62.000000, ], `4 |/ `  Y* ]  R
    lollys stokeworth     62.000000
    9 t+ j8 P+ Y! h% Hdothraki matron       56.666667
    ( r$ j4 d$ z7 f8 a" e! x9 jName: len_words, dtype: float64& B" ~9 }8 {) ]9 C9 m* r& ^
    1
    , W+ |2 \5 k: A  Y2
    % l1 H/ Y5 t' u% B9 V# n3
    3 F1 v  R% |& }/ K: _  c4% H1 u# x9 u8 p2 a) I
    5
    3 Y7 _" [) a2 `# k% M, {/ v9 ~) r6# v, [& X/ f" ]
    7
      q: {. F0 D+ }8 A' R) Y) }- |- r8' M/ [- v0 N5 l; @/ ]6 T) |
    9& F9 R8 V( W  y
    10( |7 l# b( T0 O( m) S
    111 l0 Y* z* b6 ~7 i6 I6 }
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。: |! Q  Y0 Z# f1 Q& a0 r& ^4 h
    df['Sentence'].str.count(r'\?') #  计算每人提问数; B5 J; ]) Y8 k! P$ v1 S8 a+ @
    ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0& n% t8 @" t. T$ z" ]
    del ls[23911] # 末行删去, I  K8 Q2 a1 x( Z) p/ q1 }
    df['len_questions']=ls
    : y! G! \  x* H: }. d' Qdf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()( y! r2 c, a3 M# z, r
    - t. j( e2 }' x
    Name1 [9 u5 K0 |8 f& q) |; w- W
    tyrion lannister    527+ _0 ?) t. a& h% w0 e8 p
    jon snow            374
    5 r4 W& |/ ]2 n' i% K2 x% ?+ Z. ?/ [jaime lannister     283
    ) S- x, @& @  m: ~8 F, N3 ~& warya stark          265# \  R6 S! i/ ~* V. j" g
    cersei lannister    246# N$ {$ B% t9 x+ {8 i7 f
    Name: len_questions, dtype: int64
      n+ B/ v" {6 Y, `3 q. z) M' |( D1 z. O
    # 参考答案+ ]; a9 a# \8 [; \7 n
    s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
    9 `$ C- ]& n% t, es.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()4 q- ^* j  V1 k: U0 Y
    ' G1 n: O5 s* |  L0 W7 i' z
    1
    % }7 R$ N" _3 @2
    6 G! w" H2 W4 P+ g3' w( o) N; k. {% ?* E( h+ A) }
    4# v, \, b6 W: b* }5 \* Z4 O
    5
    % A; ^, k5 b/ F+ V6
    ! f5 L; G9 o5 V9 J' J6 @7
    . M7 C$ z) ?& g3 }% m8
    $ K9 I+ q* u& D) Z" f- I5 u9  R/ y4 O- i9 m3 W- ?" u: q
    105 k9 V8 Y7 M0 T! t( E* E. \
    116 e$ K6 N; b/ E% ?6 D# R
    124 N, o$ F) p2 C" B% ]  R  b
    133 c2 q: p  [& X, D/ ^+ A6 g0 ]# n
    14* ^( m6 ], Q) X% i# @
    15
    $ R8 U% B" n1 k& o6 e& H16  a* D) ]; T) y( ~
    17/ z9 S7 p' C. _" E4 }+ {3 ^& v8 `
    第九章 分类数据
    1 V/ d+ Z( g3 `+ p% _5 n/ Iimport numpy as np
    ( H) V9 ~% V- N- G% Qimport pandas as pd2 G& ]7 m  S2 |& B1 K& R
    1
    1 O; J# j: E- D, Z2 R( L& P5 A2
    7 U  f5 b! T4 A( I6 z. N% ^  J7 P  ?. J9.1 cat对象
    + L0 b8 y3 X& \; p% e0 |. Y* Z2 R9.1.1 cat对象的属性
    5 i0 O! N% B8 M- l6 q  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。/ k( \; W' a. T/ Y
    , i; ^0 R( N- d( K/ m) g; B- d: ]
    df = pd.read_csv('data/learn_pandas.csv',  O2 y. U" ]2 ?, h
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    ! ]4 S4 G. C8 x( @0 ys = df.Grade.astype('category')
    . H0 U2 Z- Q* T  f% V$ P# `+ ]1 D% p, G  d7 G2 j4 T- A
    s.head()
    0 Y2 K8 H, o+ {Out[5]:   e) P" i. U, J0 d
    0     Freshman% C! _0 R6 M5 U/ o( O  i4 ~( }$ K% u
    1     Freshman: h& T$ w$ p; j' G; g) Z) B) D3 A1 \
    2       Senior) Z& D* r- T2 M5 ^. X. i8 F  @
    3    Sophomore
    5 P. ^* b+ y/ s9 l( `' n- I3 ^- d# n4    Sophomore4 r& ]3 X8 [+ \7 R  Q  l, X6 `# X
    Name: Grade, dtype: category- b: i3 X/ F. k9 S; P/ l6 C$ ?
    Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']! S0 `) \$ e& T! X5 k! a
    1
    ! k: ~$ C* s0 b2
    # {, B( [/ T6 q: t7 i! g" R' s3( ?/ k6 j6 h& I
    4
    * S+ q- I3 o( Y% H5" V: Z1 u0 W) S  m/ G
    6: }0 Y  I! w2 X9 Z9 F4 Y- g
    71 D/ @8 p) [& d1 ]
    8( x# Y- T& |% g. C  M
    9& ?" \  T! X4 _0 K, l1 I
    10
    4 T8 _! w+ X/ G% ]% |11
    ; ]0 s( ?- a! m& S4 v12
    " ~- ^! k5 t; L) e- l13
    + Z6 ~6 I8 G/ k+ b3 j7 G  在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
    8 ^( j! a% l4 u0 _8 e/ U& t
    ! i5 m; t! c4 bs.cat
    0 i+ t/ v3 F  W1 HOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0># c; }; ^' H" k6 F, r3 J' z
    1
    ( a# j4 R5 d, y  v7 W1 @  G* g) n2
    6 C) |; ~7 }. ]$ L$ n  [cat的属性:& r# B! b9 _: F+ I0 w
    5 p. R  F: `# Z2 K, Z. Q$ _
    cat.categories:查看类别的本身,它以Index类型存储
    9 L2 m; P: B5 _2 I' Pcat.ordered:类别是否有序
    2 \3 @. {" c% s+ vcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序+ ~! x0 ]1 l8 h2 q& p* M; b
    s.cat.categories1 _9 `* f& I: ]5 y9 j3 m5 f# L
    Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
    - X3 N3 x* P9 w3 }. G' ]/ K4 N' G7 u7 g) d3 [% _7 T8 w
    s.cat.ordered! l! m: `; I( k+ M$ F
    Out[8]: False" B7 T$ E- Y3 ?+ G; O( m+ @& K/ {
    + J8 J) ^" u/ E& @6 U
    s.cat.codes.head()
    6 `& S. B" R  W2 \$ S! }Out[9]:
    $ G6 ?+ u8 w# K, `! I0    0
    1 ], I6 a% Q( |- Z. ?" h1    0- x7 A9 ~" f1 J! |: S
    2    2  N: Z; e1 }$ P5 [; Y# e8 |1 r
    3    32 {0 e4 p7 B$ _3 d, i3 D" i2 z
    4    3, G! H5 g7 o2 G- y4 o
    dtype: int8
    2 c# u. I$ l' V4 \0 T$ n& J12 y1 t% h4 c% r" e
    2$ U7 X  R" Y+ _/ f9 I/ R$ K
    3
    & I6 I4 [- i  e2 ]: x4( R- T. u- p, v
    5
    9 L, m) o* r6 G0 b4 v0 `3 S0 ?" H3 L6% ~# c! D# U- Z
    7% ?# ~5 I' w3 y/ I" L7 o9 V7 p
    8; j; v- K4 Y5 E8 G  v, f9 t" B
    93 i  r; G0 V/ y5 J9 S1 B
    10! Q! P2 P3 W1 E2 |% Q+ d0 o
    112 F: y; H3 Q0 B$ ]
    126 E( V7 n5 N5 n  ]
    13
    ; C+ q7 o( |- w4 H: _/ j4 k! `* q14
    6 a) C, ^# ?8 L' F/ N. v0 h9.1.2 类别的增加、删除和修改' j! U' I, Y& i
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?8 `6 I/ S# q6 t  }. M+ ?
    & A" a+ d8 m$ f6 g9 r' f2 |2 l
    【NOTE】类别不得直接修改# _- K% W' a9 n5 V
    在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
    5 V: f  R" T3 [8 r* J
      a9 W9 B2 ?6 Z9 Iadd_categories:增加类别  G0 A: d; o, e) g- g& v
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别7 k- s4 Q# q* m  ]
    s.cat.categories3 [+ {' W! ^) L; T" l6 m

    4 \* r# j/ ]) S, V8 L% EIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')  D2 t& @$ m. |
    1
    . @6 `4 a5 m' t6 T+ b! g5 |2. ^  ^8 ~4 ^$ w- w$ U
    3! V7 }! `9 `2 Z1 u
    47 S% L$ J. y. @6 ^8 \3 w
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
    ! V' q4 e: X0 v, I. M4 ds = s.cat.remove_categories('Freshman'): n! z$ ?: o4 G! k( B
    & B  V; X! ]; }7 b
    s.cat.categories" b2 Z; l$ l6 W+ Q2 i# U/ N
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    - I  p1 p1 n) r! u0 N5 f8 N+ Q& j( u5 `; x0 y, \" H+ M
    s.head()
    4 ?  d- d' H) U  `Out[14]:
    3 k6 I: W# X0 _: N- \& Q7 Z0          NaN
    0 n. b+ c: V' m4 u2 a3 W1          NaN- Z8 d- `' S6 ?
    2       Senior# X  {$ q3 Q2 Y8 L: Q/ e
    3    Sophomore
    6 o: n/ Z; f8 u3 [6 B! S0 f' M  V  A4    Sophomore
    / _) Y8 V+ \5 Z& ZName: Grade, dtype: category8 W8 G, W; {7 n2 G: B
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']4 i; f' I# E" {
    1
      O6 R# n4 G4 C) }' n. U2
    # t0 h* ^3 u% n3
    5 a( A. c8 b! J8 ~% E% W- T4
    9 O* ]7 ^6 M2 m! _+ |7 ~5
    ( h- C/ ^. a& k! f6
    ' n1 b6 b4 o7 o; E6 F6 N& l4 k7+ D0 \5 E) B% R
    81 G9 V) ]/ Y9 _" ~+ ^
    94 X, K8 I. _( ?' U! V6 U( D
    10, a+ P& H  k1 W" `
    11
    . c5 ?: v$ h# B( M1 {( b121 j0 E& @, K# ]) p
    13
    % L5 C$ R2 Y! n: c( O0 G0 F14: r* a" V* M! ~
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。0 D: ~: ]: C( Q9 x7 O
    s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
    : g4 e6 O9 F: q- U; i! ms.cat.categories' f9 C0 s# n+ ]3 E( ]  z4 R- X+ A
    Out[16]: Index(['Sophomore', 'PhD'], dtype='object')' K# _2 o) s0 r# X5 v+ n/ z

    7 _5 Y. U) K, J- C! Es.head()4 V7 e% H5 G! p
    Out[17]: 5 C8 `: a2 ?6 s' A
    0          NaN
    2 G+ ~: B% y. Y1          NaN8 W9 M8 b% C3 `5 \% K' k7 ]
    2          NaN
    $ e3 w) R3 U2 x+ u( o3    Sophomore5 G4 H8 J+ Y) x$ X+ {
    4    Sophomore6 L, D# f: M% h% q: m( f
    Name: Grade, dtype: category
    ; |: v/ k3 @% W/ [; C  D/ SCategories (2, object): ['Sophomore', 'PhD']
    . q& \. i. x0 I# {* P( o11 {6 i5 u9 c2 q! ?2 [
    2
    * i/ X: H9 M! F; ^- E2 ^3# h# k; B$ D) n' Q* N
    4* Z& G2 x2 N: T6 n
    5
    7 h. a: G2 c. L+ R6
    9 d# {# h! x( ^" ]7
    . w# C" \* d) y) Y: V' W* G8
    1 v8 d" }% i1 d6 F4 d- l95 g: e3 c2 I2 y
    10) ]! e5 y3 M3 g2 N. n9 X: R
    11
    2 T" ?! Y, g: E! d12& v% Q! ^  U1 U. v
    13
    * x/ F8 j; q" O) Y# cremove_unused_categories:删除未出现在序列中的类别
    . y( x4 I; M+ d0 H/ Ws = s.cat.remove_unused_categories() # 移除了未出现的博士生类别% o4 d# {7 }6 \0 i* x9 j
    s.cat.categories
    / j8 S5 K3 ~+ d! e5 _5 |0 O, G6 g3 F  w2 s3 b9 t
    Index(['Sophomore'], dtype='object')
    / y  O: b$ |- d  g1
    / V: c# i6 C: Q. r4 s& }  P- j4 h. H2
    7 j0 q6 H4 d$ V0 N3
    5 i4 W+ H) U$ L! L) A- L3 X46 h9 Q# K: _/ Y  P3 u
    rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:! Z& P; Z0 {- x3 b
    s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})2 o) p; o6 X  }. u6 F( v
    s.head()3 A5 K" Y1 p7 S
    3 x" B- y: q+ r9 S, N. d# \
    0        NaN
    0 m0 M7 Z: a3 w5 ]/ L1        NaN
    5 E8 G( C5 S* C' s8 I- _' S" }2        NaN
    : X$ G& V# K! u8 \6 z; a) s4 |3    本科二年级学生- I+ T- B. `) D; [( M
    4    本科二年级学生4 l0 Y; p% Z7 N. y& \  Y" |+ e
    Name: Grade, dtype: category
    ; i" l8 Z0 l# Q2 ^: O" l0 gCategories (1, object): ['本科二年级学生']; Y# C6 j& }0 S0 J
    1
    . o5 |, V0 T, E& N9 W6 `& o4 {28 l' {- ~* y8 Y- d. T
    3* M5 K8 Y" Y# E
    4
      b) v2 ?* ]* [, ]" H7 W7 p5# \1 g% A# w  E3 D9 n; P6 x
    67 |( u( Z$ g1 W* @* w
    7
    ' p' E0 X$ @. w+ M9 M; P' p8% d- T4 S) Y8 P- \* O3 p
    9& i5 [5 G' u3 b9 l! h
    10
    * x9 W. b1 H4 R) H, i9.2 有序分类
    * Y0 z) a1 R. ?8 a( i9.2.1 序的建立6 |3 n4 z0 U/ v0 {
      有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:5 a" p8 e' Q9 \5 Q" M
    4 l0 V9 s( g& O
    s = df.Grade.astype('category')
    5 K6 a. i3 I# Js = s.cat.reorder_categories(['Freshman', 'Sophomore',
    9 q: u$ ?+ e1 p" {                              'Junior', 'Senior'],ordered=True)  j1 U/ @' a) q8 w( Z6 z4 Z
    s.head()
    % I% E5 B- a/ n- XOut[24]: " z$ K4 H  @' u& L( @
    0     Freshman& I9 a! [& }2 V& {
    1     Freshman
    # J4 a) u$ S  e2       Senior
    ' q4 H' }) i/ ~& Z3    Sophomore+ ?" S0 y/ `1 P
    4    Sophomore7 t2 B3 T2 w) \/ e$ l# C" }8 o8 `
    Name: Grade, dtype: category
    : ]0 ^! X1 D4 D, ]0 ]1 `! i/ ^Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']% W* D2 M/ Y" U; X6 E; s. H2 q

    ! k- l) n7 A) s# T7 r& r: Ds.cat.as_unordered().head()
    9 V/ _6 |- C7 P8 M3 {Out[25]: . }) `& s/ A7 ^% Q$ D3 ?& y; L
    0     Freshman; Q/ Y- k" g$ j$ ?1 B$ D
    1     Freshman
    ( @) F7 b; o  ^# t2       Senior3 b* }3 D9 J  s7 b) `1 y- E
    3    Sophomore
    , Z- y# a! t8 b! A/ ~; c& x4    Sophomore  B# B! Q) O# u# U7 w4 \" C
    Name: Grade, dtype: category4 O6 X: P9 ?0 c  Z! ~7 [8 ~$ L2 a
    Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
    4 u1 _( E% O% v8 K8 h0 l& D, a2 D. ]6 c6 Z7 F
    1
    6 e; }1 h  T$ T( H2 Z6 r' P2
    2 |+ g3 P" d' N3 u+ E% `# F3 n3
    5 ~5 j; A9 b+ I+ q/ x, |4) Q" g5 m) X) ]  l( U
    59 d) m# N( z( }! |# Z
    6
    " l; \! q  G0 E7
    7 t) {4 W: }2 p. A  P6 o- ~) q4 `0 R9 T8: M) r# q7 ]' v1 }  }$ u& t
    9. f- O3 a4 N# i! t" D* w
    10
    ( v% e8 j0 q; F( ^11+ s+ I4 u) S0 [; U1 ?
    12& P0 R" {: x' H; x$ R
    13/ b# D* n8 g8 u- d0 c/ x% \
    14
    4 K2 Z/ t( ^) {9 F15
    ' E/ y7 M$ S: L+ F8 ^! W' K16/ u/ s$ k' ^7 Y
    17
      L: i8 O# W, z& O# Q+ C+ Z18
    5 |0 I# S/ U6 i19
    ( h9 Q$ u( Q' ~# ]% Y$ ]" Q1 M/ O204 x6 @4 C0 [! \0 ]. s' N5 h
    21
    : R! C" B  F9 O( p224 o8 S, x8 I" X1 W* i
      如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
    " C+ S" @" C# ]0 B
    7 K; x" M" c! T: A9.2.2 排序和比较
    0 p1 y$ Y: `! T1 B4 |1 x在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    4 k" y4 \  R/ E& i. H* l& b
    ' @8 m9 K/ G  S% m  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:( C& p2 Q6 j6 I5 H  B2 o( K

    4 N( y+ c- L: `) cdf.Grade = df.Grade.astype('category')
    # t, S5 Y1 \& i7 K' ]df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)9 v$ ]# A9 Q5 n: Y2 Y1 d8 g" K7 l
    df.sort_values('Grade').head() # 值排序$ j2 M& s  G+ X" a
    Out[28]: 3 u) \2 U1 r: E# F, G
            Grade           Name  Gender  Height  Weight) ~2 T+ s% g, I/ g6 e! c
    0    Freshman   Gaopeng Yang  Female   158.9    46.0
    " k! \+ z" Q3 O* w105  Freshman      Qiang Shi  Female   164.5    52.0' j0 u* M9 }* s( h4 u
    96   Freshman  Changmei Feng  Female   163.8    56.0
    3 a& \: `6 O/ \& S88   Freshman   Xiaopeng Han  Female   164.1    53.05 X6 c+ t3 L& I7 i1 X
    81   Freshman    Yanli Zhang  Female   165.1    52.07 j. _- C" e' N7 u# N: q
    / C& f" Z* r1 w- a4 F  O3 A
    df.set_index('Grade').sort_index().head() # 索引排序
    4 c. ~$ p# s$ a& SOut[29]:
    ) k) G, w/ g4 r! A# }                   Name  Gender  Height  Weight( D; _. ?3 i4 q3 E  ]9 Q& H
    Grade                                          
    ( @+ n- c& _$ g% m7 C9 xFreshman   Gaopeng Yang  Female   158.9    46.0
      Z! H; Z* i! ^0 g5 S( `Freshman      Qiang Shi  Female   164.5    52.0: K6 y* p: x! _7 p0 P. `9 p# h
    Freshman  Changmei Feng  Female   163.8    56.0, |, m8 L" q; P) L7 c7 m
    Freshman   Xiaopeng Han  Female   164.1    53.0
    % M$ P6 \1 F3 F& v8 FFreshman    Yanli Zhang  Female   165.1    52.02 Z  _* R0 B6 U+ r4 y1 j

    # P8 `, m2 z6 ^; @1
    + s% c: S4 {4 R7 \7 q2
    8 S5 l$ V  a$ {5 ?/ `3
    3 s: e; R/ q' [5 L1 S49 a4 L" \8 n$ N+ p; Z& g
    5& ]# b' A, \3 [" h$ G$ |
    6
    9 M" @5 B6 r: D. Y76 Y) }' G7 @9 H4 q! A& v- W* ^
    8+ |3 E) H! E8 i7 @
    9
    8 C: j% ]/ J  u; w) u10' n0 _  P3 z8 h8 c8 @! i
    119 _9 U! j0 v" b, N; ]3 A3 p
    12
    & F: i; {% R. |6 ~13
    3 n# `* j8 }0 C5 L" C14. X/ N7 R, q- G5 s9 C4 s; j
    15
    ) c$ t, J9 w4 _9 @) n16
    4 K9 j; r+ P9 Q/ v1 N: G; ]17
    ; }  ~" }, N/ G$ y5 c8 ^18
    5 x- J* t6 k- p199 y; k. F  ~+ q" d1 _8 r- n
    20
    + _8 u- v6 O' h  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:1 Q' s" j" z. S0 b$ Z! F
    - H) q4 K, b1 f# f. Y; H
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
    . t$ w2 e3 p' u6 Y>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。# i! |0 {$ p3 _# ]5 U
    res1 = df.Grade == 'Sophomore'
    ! O3 f( y: {8 @& ^' c9 [$ O1 ]: s2 [" L. i8 z6 V
    res1.head()4 `" [6 w& j5 V  ]1 V- C  d6 T; ?; O
    Out[31]: ' U/ }1 N2 |& |9 S2 y: j
    0    False
    8 K) s& s' n- F/ t* S8 p2 ^1    False1 H" \4 q, l7 Y* o+ n& _
    2    False9 H6 _6 O0 J. S% w0 W6 e
    3     True
    # C" \2 v2 l2 Y3 k1 {) s4     True
    & Q( Z. L2 ^; f6 r+ o/ {3 `Name: Grade, dtype: bool- o# u, r0 L# q

    # ?) O5 I" t5 T' Y& bres2 = df.Grade == ['PhD']*df.shape[0]% Y8 N- A! X" u
    ) Q. Q: A) ^: c2 j) t4 k
    res2.head()
    % M) W5 u$ `) Z9 H  p+ UOut[33]:
    5 v8 }) [. \/ n. U" ?, N2 K2 h+ W0    False* \% i5 R. b! G
    1    False" S0 |5 z6 c) U2 A# W" a( v
    2    False
    * m- k2 E/ J+ `) P2 l* e  g# t6 W3    False2 q1 R6 g# z6 [- d. j% P
    4    False7 o7 J) o4 q) B3 P/ D
    Name: Grade, dtype: bool4 e1 E# V/ ]. h4 A/ C1 l
    3 `1 Y5 c6 ^1 `# I4 U
    res3 = df.Grade <= 'Sophomore'
    + I1 I2 w/ C. }; h. o, L, C# ]/ N( g( M* n
    res3.head()2 j* _  J* ?' C' c( W
    Out[35]: 6 Z" m  w% \9 T$ [. w
    0     True
    9 c: e) r3 N2 {. ~/ D4 D* Z; u; e1     True
    8 k' G6 S1 o) d8 A- n2    False
    # ~3 `5 z$ x6 S3     True& u7 \3 C8 A8 m
    4     True! m- U- H8 X+ Q2 I) p1 C& T9 \
    Name: Grade, dtype: bool
    ' c) p' {% C8 ?, w6 E7 n" z
    % ]7 k6 m* t1 y6 b# g2 i# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。7 g. ^" S0 ^: W2 N0 G% q
    res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) ; l7 v: L7 \/ q0 V1 Q# e# ^3 g; J

    5 U6 r7 F% N& j8 {! I! u% C: Z6 ?res4.head()% m$ N% p: Z/ L( U! x0 v
    Out[37]: * z% w" i. Z0 {5 Y
    0     True
    / s, ~7 f1 g. N" g/ U1     True
    " s' W# x1 o( G9 ?6 }& ^+ @' B8 l0 P2    False
    $ ?; Y! X7 l$ K6 ?8 q3     True
    9 I& d2 y/ K' P2 R  Q4     True
    + `# T  o( [/ m+ HName: Grade, dtype: bool
    ) j; q: f: R, g" Z5 ^% j6 G
    ( z4 `0 h" s9 x% m9 ]2 W1
    ; D4 M7 q0 K7 ~2 y2- Y. n: x! r" D3 C; k! s
    3' U: _6 P# [7 h, P% A$ c
    4) W% q3 u8 E: W. Y
    5
    + }, X" O! d) L( R$ A6
    1 `+ x4 ]' l  J0 o1 n) \4 h7 |0 Q74 Q1 O4 C+ I8 C4 O0 H- `* C& C& g) G& Z
    8
    # k7 ]8 M) _+ e! f$ d5 b& B9! |; h" Z  y2 X
    100 J+ W* \  m7 v. i% l2 ]" }
    118 @+ B6 W* F" x! W* i" E
    12
    # f5 l' d+ l& B3 w" @* y0 F9 U& C13; m% Q. ~/ D% ?% E
    14
    6 X: C/ }1 p' y) a' ^- O0 u15
    + g& W; \% H: G, A, d/ H! J16
    0 a$ X0 J) h+ P* M; J17* p% Y' Y( [' z$ B$ z; g
    18+ ^0 C5 S3 {/ Z3 W6 K" `$ Z! ?( N
    193 B* Q6 G, F4 Y+ \: j
    20
    - \1 j/ R# O1 ~: c2 c" b) n5 c3 Y9 O8 k214 w" H$ Y. U+ a$ Q5 n4 [0 [) \
    22) G. @& Y1 Q$ P) ]3 K
    23
    3 t# L- q; K$ l- M, V24
    + |; J' ~# F; U: c% i25
    * r+ `& P- N6 V, v, P2 n/ |26
    5 L/ ^- }3 v+ {: l& ~- G' d27
    9 Y* R( Y8 X' g, n; T28
    ( D) g  i' i. i29
    1 ?7 S1 J4 S4 E- l. \4 C30
    ( |2 z7 j) k( ~2 u# _4 T31
    " p" }& x( w8 o5 Z: }32
    8 Q1 z. F# a& F  y+ X. a* y& {) C337 H8 T- O" D5 H) y9 B
    34
    8 F; }1 @; {1 \' n9 V% N3 N35
    ' b: D( w& c, k36  C! N" R+ k8 B' _! x5 u& P7 \
    371 M5 O/ T, R: I+ F! t; Z
    38% c: z7 e2 d0 J9 H
    390 A' k4 ?  p0 Z# a
    40
    9 D* {; a- {' `41$ E0 V# [5 |: o3 i
    42
    4 o- I9 g  S7 X, d/ `9 l2 y43
    * q# l" C. q, g0 ]5 b- k442 B! K$ T2 \& S& {: Q$ \
    9.3 区间类别3 E5 E1 E7 k, D
    9.3.1 利用cut和qcut进行区间构造
    + H# Z" J* f" ~5 \* h4 A  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
    9 e. }- j$ l  ?9 U/ t& Z- R4 x  ]# z7 ~# z, f! e3 @" v
    cut函数常用参数有:( Y5 u! M" p6 j$ r3 k
    bins:最重要的参数。5 G6 O+ v" ]7 F6 V6 ~  j5 E1 H
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)! P. ]" B/ d2 |7 {7 I4 |5 N0 n. p
    也可以传入列表,表示按指定区间分割点分割。
    , ?( T) ?, o' s& E7 n/ K  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。! o9 k/ N" n9 w( `! V
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
    $ X) K& c5 V. |0 o2 ?# S: R, k# L9 B: ^% d5 ^+ k% ?1 I
    s = pd.Series([1,2])% {3 y& r# M9 M5 A4 r" V
    # bin传入整数7 P" u! ?5 W7 k" n2 f7 h& N

    / f0 @' n+ Z: ]: @0 d$ [pd.cut(s, bins=2)
    4 Y/ [1 D+ G# `* `" A' b# P/ C' @Out[39]: 2 i) @% Y" g% o0 F
    0    (0.999, 1.5]) D% ^# l* `/ T1 r6 |3 l
    1      (1.5, 2.0]
    6 d. A& e( T+ f4 F* Pdtype: category1 a1 a/ S% P. C7 x: r2 i* H) N
    Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]  g! z' t9 W# o
    9 o5 E8 p& i( F+ F5 I/ C6 B# J! u
    pd.cut(s, bins=2, right=False)2 Q7 }5 F) q5 I! g1 }0 k
    Out[40]: * f2 D$ P6 m& F7 I
    0      [1.0, 1.5)3 F8 {7 S8 [# R
    1    [1.5, 2.001)
    : ^# J7 n4 h4 F" C  b3 e2 `) F6 }! mdtype: category
    + Z2 e+ \) I! U6 X8 H; j7 ^; [% r! D5 mCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
    1 N  e- y3 Q& n, }/ S9 a+ p9 r
    3 P$ t5 s0 K3 h5 d  U( B3 @% i$ H' N. N, B/ e! y6 a2 y  C: K
    # bin传入分割点列表(使用`np.infty`可以表示无穷大):( k' [: P. w) a4 Q
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])5 q$ Q9 T' Y$ i# r& s- ~, g# c4 z
    Out[41]:
    % G/ b! Q& v, v6 R  B0    (-inf, 1.2]
      v% g2 ]+ K. s3 H1     (1.8, 2.2]' l0 {& Y3 a) L
    dtype: category4 H" v7 ]; T! y: _4 _
    Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]0 T; {5 N) h+ j5 M# W6 a% V+ Z

    % W$ u8 \  W0 o; _( P1
    6 ?( j8 F6 T0 V7 B: H2
    - M$ K. S) m/ K6 q  f3
    1 b. L5 q5 [; I) c- p( v2 t4( Q  c1 F, Q1 r/ h$ {% K
    5) y3 o, x+ W' Q
    6
    ' c& [9 @: ]1 A7! @: A* Y8 \6 ]& x3 |* R5 q
    89 E, c5 H2 G5 o) Z8 K, C
    92 J' p( @& O1 i
    10
    4 @, a% s+ n+ K0 u111 u2 R. C8 g& S
    12
    4 v1 r* `  m9 ?$ `7 o13
    ( V, Q9 _. C" _. I$ J' A( d. Q14% |. f2 h* L% ]9 S" r7 B/ P
    155 d# k( I7 b3 |5 P4 M
    16
    ( M: Q; w* k; f5 U% O17
    ( Z3 j8 B. c6 T8 k9 A9 f18
    0 h. W2 k1 I/ |6 x0 u! w# W19# D) }7 w+ B, D: Y) l
    202 K% j* U7 }  P# M6 G
    21
    ; B4 o; Q8 {2 R22
    ' D$ `8 I, ]4 k9 i* J23
    * T1 K+ n2 t, S. |9 Q# [& k0 d24
    7 k" Q+ ~' d. }* n! k25( F; A! \$ w& Q
    labels:区间的名字+ ^7 T: S6 U4 A0 R/ [# e, M
    retbins:是否返回分割点(默认不返回)- s5 ^+ Q8 ~; ?
    默认retbins=Flase时,返回每个元素所属区间的列表
    & k: O' p) Y! J' c) \retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值8 c+ j2 [$ S3 u- f: F& |4 d$ M

    4 ~/ s( g7 f$ D. d0 x0 J( B! x6 v' Ss = df.Weight* }9 I7 L+ `! l2 Y) h
    res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
    $ c  X  D6 U2 W# Ores[0][:2]) s' U$ K) Q% T1 P  v5 y

    5 Y  E" T! {% |Out[44]: 4 a! z& p. I* [1 I; o
    0    small+ S8 ^% d+ T, h. u; x0 w7 g
    1      big& s/ N$ [" t# d( q
    dtype: category8 S" M. M! R$ n* Z+ `
    Categories (2, object): ['small' < 'big']( S7 t' O5 T# ~/ V4 L+ W

    ' X4 |$ ~. j' ]; S% lres[1] # 该元素为返回的分割点
    0 a1 ?  o* O- {* F- YOut[45]: array([0.999, 1.5  , 2.   ])4 @6 J# g5 _: z) F' g* t
    1
      o7 s* e/ k* R* D22 k" H7 J" c: L7 ~3 y7 \
    3* H* X/ z6 R! K! ^# n- X9 N
    4
      X* [0 p# R- A) {58 f* P8 c/ J6 [7 n2 ]
    6
    # v7 p5 r8 h1 X3 Y7
    ' L! p% H0 M6 V& G2 o+ Q8
      k4 \( m$ [7 Q9 T2 H9
    8 V  B4 `2 N; F9 s4 P. @+ P( g10
    6 {8 q2 N' G* K  e$ J119 U* A* T- y' @7 B- ?+ L. p- P' t  J$ ^
    12
    " [. d* ]5 L0 [* j3 x8 ?4 Tqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。; C/ L( \% X' P, j  w% s8 b
    q为整数n时,指按照n等分位数把数据分箱( F3 `! G+ p' ~" O
    q为浮点列表时,表示相应的分位数分割点。
    ; Z: g2 i9 s/ Z. r  D6 js = df.Weight
    ) |  {' d% t1 U  |# M. Z) h4 y0 [" g
    1 P2 }5 A4 e! Npd.qcut(s, q=3).head()% a4 M! A0 }- M( D
    Out[47]:
    : V. F, Q, I5 W. S2 U8 |. _0    (33.999, 48.0]
    ! X6 j1 P/ N4 u5 [  @1      (55.0, 89.0]$ h; e1 {# J2 B) P! T6 Z
    2      (55.0, 89.0]" o$ _6 ^; b) B9 M6 I& {& D- o
    3    (33.999, 48.0]3 Y5 S( b8 X9 |& S: l
    4      (55.0, 89.0]4 p! O' A* u/ r$ y7 K
    Name: Weight, dtype: category8 O* G1 M$ I" l; f  r- k4 \0 u
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]% J& C) l% l2 }
    8 s9 ?$ v1 U" A$ l8 y. o
    pd.qcut(s, q=[0,0.2,0.8,1]).head()
    * D3 k1 Y! L& OOut[48]: , R9 m6 E) j5 V8 ?" S' ]/ ?
    0      (44.0, 69.4]
    : u" _' n3 n+ m; j. `1      (69.4, 89.0]/ h1 Y; l* K. v3 P8 M! m
    2      (69.4, 89.0]
    5 p$ m8 s7 o5 ?* u( L# X0 R. h3    (33.999, 44.0]
    $ A9 h5 G) |; R4      (69.4, 89.0]7 v3 y" @; B$ k0 Y( L' @
    Name: Weight, dtype: category
    & P3 M2 T5 ~5 V, bCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
    / |# z4 m) \* |; `& F1 T) X. L
    : Y: K1 D3 n7 w- e5 a8 p1
    9 S% T3 Z2 |5 G: D; b# h2% C2 D+ r' F2 \* g* t7 j7 b& ]9 `% |
    3: ]& r$ [/ l. n1 M  o0 q) ^+ K& i
    4' B( Q$ t6 W. N5 q* ]) Q* e$ U
    5  b8 v, ?& z# F& b* A5 k* _: L0 a
    6
    ! {. V3 h, R: S6 _! y# ]2 `7. S) k" h5 X+ {9 c, n2 J9 {
    8
    + {7 |8 G$ p; ^9
    - z: d8 z* A' d3 ~5 @10! N5 V8 X5 T( T$ s! v! v
    11
    & {: q, S3 W; a, Z: L12; }$ R* D* U6 \3 \: }; h; k
    13
    % @2 P) ]4 Q# j, N14+ ^) O! s7 A% ]
    15
    $ q" E1 z, E* z! X! [2 ^' Q5 \16
    ' ?" i+ w) c1 F+ S177 [! \0 ?) W; F& A5 X9 j
    18
    ( B) w/ I2 X* @$ O) W19- J; o( I3 O. t# ~8 r7 j& @
    20
    2 d6 K, t; [7 b21
    % L4 h& R" ^! B% \9.3.2 一般区间的构造
    1 ^$ C( ?& I" T8 n/ S' p) r. ]! x  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。
    # o$ H8 g$ w# B2 B5 \' _/ A- G+ a- z
    开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
    , V6 U5 a0 a# O3 }$ D- p" [5 k7 `my_interval = pd.Interval(0, 1, 'right')
    " n$ M8 T) S  B# z, y4 z! e# M5 F' ~0 \1 h# O* }- x
    my_interval/ y2 A. D. |( E
    Out[50]: Interval(0, 1, closed='right')
    9 R. ^4 J# |" |% W' k1 c1+ s% Q7 \; R) a& b
    26 ]7 t& Z( c) G% @  `6 s) {
    32 M& \$ \$ }: j: I
    4
    ) y/ u! m! N. Q5 T# C! {区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。4 }! g3 T% l* ?+ L3 n' A9 r
    使用in可以判断元素是否属于区间
    2 R4 t9 Q% Z) M" _# `用overlaps可以判断两个区间是否有交集:; g2 I1 X( q7 J
    0.5 in my_interval
    5 g  f9 ?/ P- t8 N; P! L2 {. w! T/ U2 X  A: R' P) \) \& e
    True
    % _' n+ B  ]' u6 {4 y) p17 [4 m' ?5 v3 ~% l% @1 p* `) I* Q
    2
    $ W. J. g! P- n9 b" l) @' R3
    * H7 s9 |) m% Y# f5 P/ V' k, a8 ~6 u' ^my_interval_2 = pd.Interval(0.5, 1.5, 'left')% @% n+ M3 H! [8 h- C
    my_interval.overlaps(my_interval_2)
    . ~0 a; t4 n6 C9 T$ m
    / L6 x. H3 G: W- {' xTrue0 U9 g  M  n4 J. h4 E: }% D! D
    17 p; A4 I" \* Z
    25 e1 A; C  G& R$ j# P4 j
    3
    " R7 q8 I0 ?% I) K  `3 R4/ g2 y) S# r  z
      pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:8 q9 T3 Q; y/ Q; D  Z8 v
    " t- i# N  B/ J# x2 T+ {
    from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    " L5 }4 I  P6 Fpd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
    , T; y7 h- [2 F- r
    + r. X- h( G" M) {, d* k2 o) V3 QIntervalIndex([[1, 3], [3, 6], [6, 10]],3 L. L, r  h1 Y6 H, l6 I$ A
                   closed='both',6 l& m( r- c' w2 b$ F6 P: G
                   dtype='interval[int64]')/ P+ @% n0 a5 D9 |& K7 q
    14 G0 U5 ?6 V: A# u" D/ Z" i( B
    2) ^7 G. {& u/ N# \7 c, g
    36 g1 B" }: `5 N' m7 G
    4
    ) ~. ?& @$ `; \3 p* z, e! p0 [6 b9 P5
    8 j; t, K- \" k1 `, W2 X& sfrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
    ' e: e' p7 L2 P: o) T: m4 mpd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')+ ^5 f9 T5 J% m' }. k
    ! y- d: H5 D4 @3 B) N
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    8 W; Z' z5 U% P8 Q* v' r3 C                  closed='neither',. ?5 T) m! v, H/ g7 R
                      dtype='interval[int64]')# j, ~8 s" g' i/ ?9 ~
    16 n  i3 {& q0 D* _0 L$ H. r* g
    2
    ! w6 O7 C: L- w8 Z5 b3" d9 r  g' Q" n4 X# `
    4
    1 C, |* E6 y3 l" V! E9 |: c5
      g9 P5 v* x# e. n# c, ofrom_tuples:传入起点和终点元组构成的列表:( u! I& D- e8 f6 p1 H% L) z9 c
    pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    ( N& R; O" E1 I3 f( |4 C2 O1 R' R1 [$ F+ M# h/ f( A; V& e3 Q
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],+ y! n  d& {5 U- w, |9 d
                  closed='neither',
    + k* _0 I' t$ @8 G* @3 m              dtype='interval[int64]')8 [6 {/ g* o" a; e* x$ X6 ?
    1
      h! P! e8 r, s2 O2- I& W; z/ u- e# i% [
    3  B8 }  X( \) K2 Z! k" \% J
    4
    - P7 e( x! j! R$ {- i% X# Y5
    ! J' M" `' G3 B" einterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
    7 Y, i* c! t9 g4 X4 a' V5 Npd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数1 G7 }6 r% e5 V' ~$ r$ r- H
    Out[57]:
    ; _7 R5 L! p1 EIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
    2 A% G" a& T' ^              closed='right',
    ! ]6 B+ H% n& @% w9 V              dtype='interval[float64]')5 \# E" z! H( I2 b0 L9 J: E# ^& A
    ! f4 t+ F: F* T; P( O( h8 h5 H7 y
    pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度& `5 g! C* j3 k  ~
    Out[58]: - v1 N8 p# g& a" w  f
    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]],; x+ ?4 P6 M8 X. h- @7 d
                  closed='right',
    " z5 I% s& O# L4 s: s& o              dtype='interval[float64]')
    7 ?! T( ]- c: z( w1 |1
    8 i8 H/ Z) n9 Z+ t2 T2
    % I$ c9 _( q3 T% P$ Q) r. Y3
    ( O$ e4 R8 Q% R4 `; Q1 `5 d) `4
    0 [5 B8 y( z# P  {! N: Y5/ Y: e* h5 b: ?" H, O8 R
    6
    % z4 s8 \8 V) R# g  ]7) |( g/ e3 E1 r1 j
    8: a% v* r, y9 r; v$ ~  U
    93 }: L" p: E' P, S2 e
    10
      Y- B& t! T. m0 ~4 o11
    % C* a2 e, j: y6 P【练一练】6 G% T0 J( u; g" {
      无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。* ]+ A3 s$ g* O( F7 e5 |
    / y9 o) ~0 ^8 }8 t3 W, E, _
      除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。$ z4 |1 D9 F) Q' u8 w
    9 l$ l; `; \( [/ A# c: {
    my_interval
      W# l' u% a; ?: J  `& E; u- lOut[59]: Interval(0, 1, closed='right')
    1 U0 e. s2 y" F( B0 D7 J
    - F% M6 n# r9 Omy_interval_2. y; B* l( Y4 U
    Out[60]: Interval(0.5, 1.5, closed='left')) ~! B2 i9 @  }( h+ r. x
    ( D: s, g! v4 k* s# H' U0 X  x4 _! p
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')
    4 x% N3 ?9 h( h9 l' V8 F. V, SOut[61]:
    1 G4 e6 }* N. I; b! ?. M- AIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    0 j8 @+ P" ^3 C. h7 [  _8 I5 j4 K              closed='left',! ^! _' z) S" i7 N7 ^
                  dtype='interval[float64]')
    8 [$ D6 L: e8 w* F. s1 X& M1
      q3 J- ]% J* ?2 t' ]2
    ( O7 r. Z: }3 l! {* w+ a3
      y: H- K3 T% ]4 Y4
    7 z. U! w1 Q( }0 b$ D5
    & H! l7 D' ?. P  H9 p( g0 Y! y6
    ) B( T8 }8 E, o; G! g6 R7
    / M+ W$ C6 v8 _7 N/ z% {' n85 r8 j2 i$ E, J% }5 P. B
    9/ w  A! j; x3 _! \4 r2 Y9 ^: P
    10" \8 X' I) ]4 X3 c: K2 d& d
    11
    ) x+ Y- O( u) p% I- x1 k8 }' j  s7 G9.3.3 区间的属性与方法
    & P+ O4 q+ t8 o1 y# G1 v; `  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:# j  }. v! J# q# u, _
    , L" b* F) k1 }4 Q6 F
    s=df.Weight' u3 l* X$ Q  j; _
    id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示7 b/ O* O: x0 d& P5 @
    id_interval[:3]
    1 O- M4 M' X0 v3 Y$ t  t
    " h. l% h. @1 M' X) y9 vIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
    ! x7 c6 h4 y6 ]2 y' B; Z9 G                 closed='right',! A! G0 M5 v7 S$ P) `
                     name='Weight',
    ' K6 Z: O7 t# O' z& c                 dtype='interval[float64]')
    8 R$ m9 {7 ~+ m# K* F, K15 I+ _7 H& G1 W8 b/ H4 p2 {& M
    2
    9 p- U/ n" V. u$ F3( C- ?1 h) @, b6 o( t2 u
    4# C' W& b  D* b! p
    5
    + O5 I3 i2 p1 N# z6
    " d/ e! M" h8 D: Q% }- a0 Z  |70 E; z+ A) G" }
    8/ N, q3 G8 T6 o; _" g8 U: x
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
    , x2 P3 |+ }; ]" e. b. xid_demo = id_interval[:5] # 选出前5个展示
    7 a4 }  S! \6 T" d* Y- @% ]  @( d- P5 `1 O
    id_demo% U, v7 R1 ^0 \4 I
    Out[64]:
    % r% w# F/ b* K5 v! _& jIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    ; z, t/ l# D! D  c& o9 V              closed='right',
    % K- ^2 w# A. E( T- x! t              name='Weight',9 f* C& l" f' l  l+ }( q( L
                  dtype='interval[float64]')  n) y/ [8 N* x- }% I8 b4 B; N
    ! R( b* T- x8 p( v
    id_demo.left # 获取这五个区间的左端点
    : n. q0 _& S1 y2 }0 pOut[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64'). J& d+ I' g: R9 b6 m
    6 ^/ \5 Q$ x  B6 U- d0 Z" j) Y7 _6 Z$ ~
    id_demo.right # 获取这五个区间的右端点
    # r5 S3 o& d( fOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')/ b' n! [! r/ n; Y9 n6 o
    5 l+ N; L+ |' H& x
    id_demo.mid
    ; T: q( ~. @3 V4 xOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
    2 B, E% X, N- ?
    , A7 U7 c. }" |( jid_demo.length, b  D7 j& o) t# z8 f7 P
    Out[68]:
    " A8 _/ w9 I' Z& N; |Float64Index([18.387999999999998, 18.334000000000003, 18.333,
    . Y! F8 N( E  A. B; @. V- b              18.387999999999998, 18.333],8 s5 R: E/ ?2 c+ T3 G
                 dtype='float64')
    % {, |( P1 V; f0 S& q$ T! n6 @' l% k6 b: W. u+ Q9 a
    1, e; p: p' c8 g7 l' W8 c9 X
    2
      l  x& B% L' k& e1 ~+ a. ^31 N: B7 p1 G% C
    4; L! h  T. c. p4 q# m5 R
    5
    : i! w' s  S2 V3 m& C# Y9 O+ ^" a6
    $ R+ Z! P8 q4 O7 v' a70 ~2 M2 k0 `1 U3 M
    8
    . s8 C! d: k# @2 b, v( e2 c# j# ?96 n. T# o9 _. V4 b+ [9 j
    10- M& H5 c( g  f& T( I2 E
    11
    5 P. k( l5 w: r$ s$ f5 V12
    % T1 Y' j, X0 @4 ]6 _! }4 i* X6 B8 _13# S9 Q' c* E' V, B8 D( Y7 n
    14
    3 P  \% U8 ]6 B8 _3 g- }; P1 ~) S15
    * m! o5 Q* k9 ^6 \2 Y4 G16% F, x( X3 _1 E6 @
    17
    : t7 @4 W6 c, }1 K2 R- h2 Y8 H! [18
    8 g: A( }( D$ O2 l' _' f3 i19
    ! [5 M! H$ Y& G) h) y9 j3 m5 F# z20) ]3 a4 `3 d/ v4 y) ~
    21
    3 S; `. M- A  Z' d22
    ; N. X5 ]; c$ X9 t, v% V; l0 d  f7 W23& Y3 D5 |* _/ Y2 O/ h7 g
    IntervalIndex还有两个常用方法:
    ; G/ G/ p5 y. k( `% h6 c! ycontains:逐个判断每个区间是否包含某元素3 _  _% V' z( v& `2 ?  w+ O2 @
    overlaps:是否和一个pd.Interval对象有交集。
    3 a' A: \  {. ^% Y1 t- N9 G+ Oid_demo.contains(50)
      m- s, Y2 r8 j( LOut[69]: array([ True, False, False,  True, False])
    0 b, N7 I% k! O4 @4 D. z0 l6 a2 y  p. U7 r- b
    id_demo.overlaps(pd.Interval(40,60))& T7 \2 b0 a0 _4 ^
    Out[70]: array([ True,  True, False,  True, False])& F* f% Y1 V: x
    1
    2 L' U0 }7 B0 @3 v5 y. P20 d- I3 a+ p) g/ J
    3
    & j& z7 m/ f) _2 t7 p6 z. C% Z4
    " b+ F5 e# f; N5
    + M2 M, n% E" {) C; u9.4 练习9 |# V0 r0 V8 B/ f% m
    Ex1: 统计未出现的类别
    1 R5 R0 D% G3 T# q' j  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
    # ?3 G; Z! I2 c) t) z( Z( _
    6 a: C2 }4 i" \df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
    8 \- {' Y: E4 \! upd.crosstab(df.A, df.B)
    , y& T) K8 @0 y/ e9 H9 ^: g' S2 s* a
    # ?6 H2 e" I. U0 P& D( B% p% QOut[72]:
    1 m+ @$ s! Q2 q: y' UB  cat  dog: v/ y0 o  s% h( T; n
    A          0 K9 |+ {  ~7 ?+ _  ?1 l
    a    2    0- a% L9 |& \$ r
    b    1    0! }6 u, [; K+ J6 n. g" O# V
    c    0    1# j9 O& H# \" E; n% p
    1
    * }; P) ?; X$ l- p9 \0 L# G& ~2. n4 B: v$ L3 r' l% E0 h
    3
    : Z# n: }1 f( X! v, X4$ r* W7 N8 v  G( Z6 T$ ?
    5
    $ D. r2 T% P- B6 C2 g5 @6
    1 v( T) M, |6 J1 x0 f" T. F7
    2 a( e3 X" T2 x+ D% u+ s8
    " B& V. ]9 p; q& C$ N# }3 V# Q9
    - b. C5 h7 O: ]. B3 _  但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:* U) {" Y7 s$ V' d6 w

    8 X$ J4 M2 n4 ^+ v- {4 ydf.B = df.B.astype('category').cat.add_categories('sheep')' H1 n) ], i& s
    pd.crosstab(df.A, df.B, dropna=False)3 @& G: a/ j8 q# ]
    ' c1 l+ {. w4 L. F% \. [
    Out[74]: ! ]# \8 u% t" J% n4 f( D8 _8 r, L! [
    B  cat  dog  sheep2 m5 M; K* G; z& s
    A                 # U7 T' @3 }8 w/ a0 W- f
    a    2    0      0/ E4 x* ]" w$ V. C; d
    b    1    0      0
    & ?2 h% l& G( k6 K3 ^: N/ E# u5 Qc    0    1      0
    5 s$ A6 h, o. {4 _1
    " w  ^5 I) ]# L$ D# Q23 A* ^% s" v. I% u6 ~  T
    3
    ( x  @* _/ D4 }, d( L4
    " \/ ?: e1 Z6 ]# i0 @5
    : [2 E; O. E" ]) N2 ^' R6
    # D- a) J2 R  o# Q: S- a1 y5 y# G7" s4 }0 H/ u1 ^! W
    8/ P! l* i, D# S2 }" B
    9
    6 u( ]3 s; l$ ^请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
    + A& A0 \0 f) k$ Q. p$ A* @& @
    ! e/ O1 i8 C4 z( J" A7 \4 i, [Ex2: 钻石数据集
    $ J, ^, c, |; {( w  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
    & M& i6 h# m1 [/ `3 y7 T1 x3 b  @3 d: `. u# a
    df = pd.read_csv('../data/diamonds.csv')
    + F# _" G0 y3 _7 t2 S4 W3 Xdf.head(3)
    7 o& u) s' J' N5 \5 _8 i$ Y# e4 W
    7 u) W  C; J  C6 h1 Q: ?: [) LOut[76]: 8 P; b; i( @. i3 f
       carat      cut    clarity  price
    3 `/ q& Y& T9 A7 K$ g0   0.23     Ideal     SI2     326
    " q- y+ x4 z! ^; G1   0.21    Premium    SI1     326( f2 x. V! V) w; W$ {2 q- V
    2   0.23     Good      VS1     327, K! F2 Y2 v8 `3 E) w8 T# g5 }
    1- ~( I; b+ C2 g  u7 G
    2
    , v) A6 _" m, J* n. D" }3  F) ^/ j( R# O+ j; w5 T
    49 e) G5 q3 ?7 H* @9 V" T; a
    5
    * M0 h. |; |* ^' p3 w6& r4 e/ ~: ]2 H, g
    7
    1 g' p6 W4 V( @  A  G5 J* u9 g8 b86 A" v$ F+ l8 _7 h
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    8 B3 h4 G/ I: }, a钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。, e" t5 K0 i. |8 t  K
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    4 I% g  d# q9 i7 T: ?2 }对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    6 P& \0 c# L3 o2 f第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。/ |2 s+ }% |! }2 Y* _
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。+ [6 X' i1 M' W# q5 X
    先看看数据结构:
    . {6 c9 \9 f6 U0 q* c1 ?; S$ O: c% j; v* ~7 b5 J
    df.info()
    4 t$ F2 d2 f6 R1 FData columns (total 4 columns):$ i# t: k" R! z1 G) |
    #   Column   Non-Null Count  Dtype  " v3 G2 g  E  D" ^% I
    ---  ------   --------------  -----  
    . A5 b( [. s" y  [( ^' I8 D. N7 N 0   carat    53940 non-null  float64' `: T) x% K8 R( Q
    1   cut      53940 non-null  object 5 d1 Y- c) B5 A1 e" R8 e2 I6 s
    2   clarity  53940 non-null  object
    - a1 [# M! j  J0 A# u4 G: f2 g9 E 3   price    53940 non-null  int64  ' _' Z& T6 o3 j' h
    dtypes: float64(1), int64(1), object(2), |4 O3 p% j/ D
    10 i+ g, t3 @- K
    2# V: k( [0 A! ?9 ~6 @( k
    3
    ; D/ R4 o+ d" ^* D$ P7 l8 D+ a' G45 C: d3 \5 u" F( j' p
    5
    1 C4 Y* E! y- D* U; i6
    7 V9 h; E8 C7 o( L4 Q0 K  N2 _, p; T7
    , {. k: g, _# F3 u, }' y8
    , ^5 y& m- l  Z: W7 P$ T3 W95 U: @# W* n  m9 S" U. G
    比较两种操作的性能# C  _5 O+ D6 H; |; I( ]& {- N
    %time df.cut.unique()
    7 U; r/ X( ^$ S! _4 H. }0 }: b8 K) y& E  Y6 c2 L8 \
    Wall time: 5.98 ms% A; s3 c9 [* C1 ^; h* S
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
    6 H8 c8 @6 A6 k! m, L1' ~* _* ~0 p6 x. ]7 ^
    2
    5 d. E( K0 g/ h9 ^% K# g3
    2 p7 L2 k# h# _9 K4
    # m1 O# U  V0 S6 H%time df.cut.astype('category').unique()
    2 C  ?; E3 ~; y6 z; k% F, R! F
    # }7 W8 q8 p1 K' ^( A, F2 iWall time: 8.01 ms  # 转换类型加统计类别,一共8ms
    5 i- r" y. ]1 [$ M['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']; ]3 l, o3 ^) v9 r
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']0 G0 i2 C( H6 N3 W
    1
    + d% }  E% g  d- s2 T9 H20 e, Q  [) H$ W% f* Q
    3
    6 D/ _4 P( N7 N4. U" K3 E& ^9 y% k
    56 U2 T9 D7 v+ g  g8 G0 t% N+ E# N
    df.cut=df.cut.astype('category')8 `" O1 H  W  k4 \
    %time df.cut.unique() # 类别属性统计,2ms) `: E1 j7 Q  K) P4 g& e. P: E
    5 v" U8 C/ F+ Y1 T1 P/ N
    Wall time: 2 ms
    - ~2 |$ I0 P4 N1 W" C5 V: C' E['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']4 t  T  k% F$ J
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']( J% x( N2 I& |% `9 ]# @( j
    14 X1 }) t5 ]  M
    2) T' h# w* ~- @! @$ F6 H& R, f
    3
    4 d  m# k6 V' Q) x4 H& d. ]47 n, C- |& F" k7 Y% k
    5
    5 @- c: F% n  i; [; _/ g67 P" e# @3 s9 \% f* h! t; n4 T4 Z
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    : `" W  C$ ]3 d6 jls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']2 T. u8 H! Z2 `' ]0 e: V$ ]! B
    ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    4 J' n8 F" d1 J2 Udf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    ! Q8 m7 b) l1 i7 W9 x3 p2 Q9 f8 G1 \df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)( b8 x4 l  e% T8 |! j3 }! Q
    6 d2 M# n2 U' @  t# l* x* C. q* Z
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    " }* @0 B  h4 ?" a6 |4 P) j1 }5 X
    4 P* Y) v6 r6 R8 K: @" V+ U0 A; ?        carat         cut        clarity        price1 {0 {8 b6 y2 M# S
    315        0.96        Ideal          I1        28019 @$ m9 R; x/ y. P( @7 u# N/ Y
    535        0.96        Ideal          I1        2826; f  |8 N; i; S8 i6 |+ s% Y. Y
    551        0.97        Ideal          I1        2830
    ! Q  X- E6 D) L% f! ]& d1% Q7 O  f; g2 F- v* l3 J
    2
    & p" d6 l# r. |5 D* b4 E& H  B3) V4 o- y; \9 F) t, p/ A
    4
    5 C* Y$ H3 w% {/ M5/ e7 j# r  R+ s: G0 D# V/ B- q
    6
      @( S$ v: x, P6 R4 j76 u& I9 ]- Y1 f. U: k- b( D
    8, V$ Y# }0 d( O; w
    9
    2 E6 ~1 g8 p, D7 M10# ^' D; V) c3 j
    11
    + c' _6 I% Z) r) {8 I/ g! b, m$ Z分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。$ F! z; [5 h  [' A
    # 第一种是将类别重命名为整数3 i6 x1 R: Z+ C5 H6 V# K, ?  E4 ^
    dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))' p# x. V7 n) v+ u( x* X
    dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))% ]! S) B1 X+ i$ d& |8 w& R1 V1 }6 z/ J

    ' s# u4 b4 l( w! X+ J: K2 b8 ldf.cut=df.cut.cat.rename_categories(dict1)
    2 L* q$ _' _0 p6 Pdf.clarity=df.clarity.cat.rename_categories(dict2)1 q% E4 ^# u2 `3 Q. H  ^
    df.head(3)3 S, v9 F+ B; S. y& a- a- p

    3 \! W& r2 i4 M3 u' f        carat        cut        clarity        price
    % v) a2 z4 {& u/ ^0        0.23        0          6                3265 f! v$ D( [6 u- i7 E
    1        0.21        1          5                326
    & y( K' A3 y7 i2 B+ u" I2        0.23        3          3                327
    8 F) M) M9 u) ?: x. Q5 F1
    6 ~( C+ O4 p, W7 z' X9 B2, \& D& ?" K9 C4 G, T+ m
    3& t; ~% N( V$ ~2 Q8 h* N) ^( D
    4
    & L  m9 [# p# `55 p' S7 o8 ^4 j( Z9 \
    6
    4 h3 n) `5 S1 K. n) M1 u7/ E3 ?6 u3 @' d4 ]
    8
    ; Z% [( }# @% S- L+ o4 y9
    + L. c0 h7 [- h( g8 m10* Y9 D' X4 A) K$ E; C5 I" r
    11" C- }+ z, m5 H' z8 g2 p" D
    12& x8 f$ h* J  \2 q
    # 第二种应该是报错object属性,然后直接进行替换
    * \% C" \* L( r4 N3 L6 J! idf = pd.read_csv('data/diamonds.csv')
    1 I1 c6 v4 s3 U) C' u6 B% [! jfor i,j in enumerate(ls_cut[::-1]):6 M/ w) H7 L( g8 U! J
        df.loc[df.cut==j,'cut']=i ; M6 x, r- t. k) @% ]

    $ }) O6 D$ s! Z0 _0 D- m1 \0 u0 Qfor k,l in enumerate(ls_clarity[::-1]):
    ( f3 }  [1 Z& G( O& r    df.loc[df.clarity==l,'clarity']=k/ x$ W+ |2 s7 c5 ^3 T
    df.head(3)
    / W- B0 J- G$ G  @. @
    * u7 V0 t+ c" X  x, g        carat        cut        clarity        price
    ; P1 e# _0 E' Q# M, g! B( ?: G0        0.23        0          6                326
    * R' t; s( @* W4 v8 \# c- H1        0.21        1          5                326
    ! i& E) U& M( L- r5 _2        0.23        3          3                327
    1 G4 s5 U4 k' X$ D% }13 J) n6 w8 W4 o8 A
    2
    5 \1 H+ J: A' c& O- ~% }0 Z- e3
    $ E8 Z2 c  b8 m2 D; d( O2 C7 x$ Q" U4
    ) _, c; t1 o9 V5
    ( @7 j' b6 u9 t: t( Y6 d# y0 d2 u6
    + Z. c, a! ~) E+ h7
    ( z8 y" r$ W/ ]) h89 f3 l; F3 N( d; o8 k
    9! N2 [% o! E6 y' C' S
    10' t' L. s. |# L+ E: j3 \+ U7 }1 `
    11
    4 `" U% e" Y# x3 O% ]6 ~' t12
    5 J/ P8 u$ O1 |8 X4 `0 k13
    * G0 P- _) X& Z! i, b5 H8 x/ {对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。5 i0 O1 D4 Y7 a# L: r1 I
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
    : H- j& K, _/ [$ Iavg=df.price/df.carat
    & n4 W  X3 x$ b6 ^/ z, l3 N9 u5 i0 g7 `
    df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    6 R/ z+ v/ q8 t) z$ A: D% m2 X8 v7 h                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]+ x8 G& O; r( v# E4 y  n- J
    1 _. {3 I+ p5 ^) F/ t! E% I8 U
    df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],# E8 M( [3 M$ L
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    + ~- Z* P2 n4 l+ W. ~- n6 xdf.head()0 \) r9 c1 [0 V/ J" d
    4 r$ p; K; {3 n1 D: f3 R
            carat        cut         clarity        price        price_quantile        price_list$ Y: Y6 p  Q0 O. P
    0        0.23        0                6                326                        Very Low                Low
    , E. b" w" ^" \6 E* U1        0.21        1                5                326                        Very Low                Low9 a; W8 y+ W" _' d: r7 w- D
    2        0.23        3                3                327                        Very Low                Low
    - f- z' |! s) \( R2 g3        0.29        1                4                334                        Very Low                Low; A" u* e0 b1 Y9 F: O- P9 A5 J
    4        0.31        3                6                335                        Very Low                Low                                       
    . j' f  M# ?1 e5 I" D; A8 y# B/ U0 }
    1
    * X& m3 `( ]- A: ~/ t" q7 _1 \2
    , O4 ~1 n) D' l4 E- C7 H+ b30 Q) y- T. d) K, F& J
    4/ \8 u2 {0 R7 ?* y! U- E
    5
      [/ w! k' G- g- Z- \+ q+ s5 x: i5 _6: Q4 y8 t9 U6 }# x- v
    70 y* F2 U' ^' i, m* X# E( j$ ^8 }8 L
    8
    ( z5 `% b2 |) J/ J90 {6 S9 ^; e3 K3 o2 k
    10
    / o4 n7 e7 e) A8 f5 d& V8 R9 \8 l7 i) T116 o; E' ?5 F8 P( V
    12
    " P8 u$ n  r- ]7 W- l; X13
    % d7 x1 j4 K+ K6 H6 T& e14$ }$ A" }  e4 Q
    15
    7 {/ M4 B; K# y5 y$ v0 c4 _" X) L16
    " M3 x& x  b% M. N分割点分别是:
    ! V! R. U& A+ X& N" n/ ?2 k' r
    2 I; y5 s% A4 z% Harray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])5 Q4 l3 x+ x2 o6 p
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    / P- }  E. z1 O6 W1# e2 v- Z) `5 M
    27 A0 P& r+ f) o# E, @  W
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    7 S. {0 o  O' v( Xdf['price_list'].cat.categories # 原先设定的类别数
    1 A1 m1 e& C& Q( n! n3 g( ZIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')/ H! G# s% U% V+ z

    / m4 ~6 w, `$ V' hdf['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别; p! ?, T, l+ X" a* U; ]6 V
    Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    - t& o% j4 [1 Q1 i& ]1
    . ~% A. E3 b& B' }7 _7 W& O25 {7 P4 O/ n  L6 E
    3
    $ @! I: o1 J. M- v+ k9 b4
    ! z  ^( P! c0 e6 C/ O" j51 t( d# r( V2 R  G8 i8 U* n' w
    avg.sort_values() # 可见首尾区间确实是没有的& U1 R/ F, g' P+ r$ f) |3 q
    31962     1051.162791
    5 t3 m0 [0 O5 H+ J, J0 `& R15        1078.125000
    4 t* P5 ^1 Q! [4 K* K  O: P0 B4         1080.645161. M( Z' C3 E; _! ^' Q6 W( b" Y6 m
    28285     1109.090909
    ; V0 }- g! N5 a2 m$ k% i4 @13        1109.677419/ G0 P6 L1 Y! r% Z: E# I$ f
                 ...     
    ( Q9 O- l4 s6 w1 W9 ~) I26998    16764.705882
    5 b1 z* i2 W2 y3 r% j; n9 M27457    16928.971963
    : S+ d& N, N  [  f27226    17077.669903
    # [5 |' q1 ?8 G" V3 H+ f) O27530    17083.177570
    $ g6 a9 U6 ]: @# G6 g% c27635    17828.846154: _5 [7 b; L6 b  [6 }
    1* I# Q2 T6 v8 q% G; B
    2
    # l: B$ X# x5 n7 }( \' F& Z! k34 \3 ?' t% \3 v0 ]+ k
    4
    7 _5 j) W' _1 F3 c; C5
    ! [* _1 c7 b  n# e5 f/ q6 O: {3 U6
    / ^0 C$ D8 f3 f# O" Z0 x7
    ; a. ]: v! n( p3 G8" Q! W6 G0 Z  x) U4 g
    9
    3 n' |& S! X9 f8 l# P10
    9 R3 C6 E( t5 [& P11" s# m' U" }* c; g* Z
    12
    ( z0 h% i  w% m对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    1 V* }( Y  @9 n# d# B& J# 分割时区间不能有命名,否则字符串传入错误。
    3 j) p& i& [! p9 G/ D. uid_interval=pd.IntervalIndex(
    7 A2 Y. }3 d) }2 u6 t    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
    . e3 L, d5 C0 p1 {$ h" O: U" H                            )
    7 Z( u, j& G3 G. F* m) H6 Eid_interval.left
    : i- L( g) Y# X" c. V5 u/ @# aid_interval.right
    & ]8 ^+ }4 E+ b: B8 y1 s; bid_interval.length                            3 N3 c; `' m9 l1 l
    16 d5 G7 e/ c4 H: |  Y2 ]$ p
    2# x) ~0 ?) K( ]
    37 B* f9 E. o1 {7 S8 `
    4
    5 x- w* Q1 B! V3 f1 l# F& A; k; F; S50 H- @% x6 X( m' J+ ?: c; o
    6
    , w# \/ x; k' A" h70 F1 q: t) n; W
    第十章 时序数据4 r) N+ [& {2 g8 x, N
    import numpy as np
      z4 a/ O" T$ l( o5 himport pandas as pd
    , v5 P0 |5 X4 t& [' |! Y6 R( t1* `( X/ X( O" c8 }4 ?4 A1 b6 l7 q
    29 Z: \% v; h) _4 a

    # B+ `/ G- y0 c7 ?
    & P$ L4 ^6 I9 d10.1 时序中的基本对象
    : {+ B  c7 R) ~2 N8 K% C6 `  时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?+ \. a5 D  ^1 }, I! B- d$ M

    & w# L( M7 H: U会出现时间戳(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的简写。
    ; V' G( M5 r% @5 C* h
    ; @% c6 S0 o0 A! O. n- V/ d会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    5 @. r! e- \! w- m
    $ W- I& j7 x% T; A! O$ w会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。2 ?* \% Y4 {8 D/ E1 v. p7 _4 E% q
    0 p2 [1 W) F& I( V
    会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    # }3 c7 s1 j2 r! W
    - n  B$ ^) F( ~' T. r, N; @0 |4 ~  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:- i- P7 N" G% S
    # q1 b, t/ ^$ V" f5 {
    概念        单元素类型        数组类型        pandas数据类型
    & t8 p4 t/ P( \6 @Date times        Timestamp        DatetimeIndex        datetime64[ns]
    6 p3 V7 Z9 N* S: I9 ?# N- }. wTime deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    3 e+ y+ H6 R2 G% y+ _Time spans        Period        PeriodIndex        period[freq]
    0 n# E( d# x; V9 |Date offsets        DateOffset        None        None
    $ e9 F# `3 O  o+ a7 s% i  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
      D+ r' {, z# ~# f8 O) q3 o, P# H9 B
    10.2 时间戳
    5 f: }4 B1 }2 W9 q10.2.1 Timestamp的构造与属性
    9 E% J5 N/ O, b" W3 b0 d单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:3 Y  ~8 J4 }  \( X

    3 i2 J, @9 D# A1 K$ Tts = pd.Timestamp('2020/1/1')& @% [" A& S2 W# a; z/ J

      m! U% ~4 B6 v# i+ Rts
    , F1 h: d& a  x/ i% T6 COut[4]: Timestamp('2020-01-01 00:00:00')0 E  m4 @$ o, N. s* D2 h  Y; j
    4 D. e  j6 U" j" f
    ts = pd.Timestamp('2020-1-1 08:10:30'); x+ M4 t7 Z& t

      a. D1 \& f8 x3 `% K; _ts
    6 a4 f! Q9 P& b7 h3 I% dOut[6]: Timestamp('2020-01-01 08:10:30')
    / b7 O/ l! L  J& H2 f1
    " Q5 L  k3 C0 _5 \/ }" J2 k% `2
      F6 K+ ^. H* _9 p1 t, g3
    , ], e7 w7 w) A2 E4
    5 j) ?0 T. d8 J) x5: x6 ^, ~2 B; E& y2 v5 n
    6
    6 d; {- M( C$ y  T$ ^) e7
    7 m8 F1 g- b$ f  D9 I& O8! ]  N3 h7 x( m6 D2 S& P! m; s0 O( F7 b
    9$ ~4 |% u* `; K" v
    通过year, month, day, hour, min, second可以获取具体的数值:
    + j5 n# x4 i' ~) v1 b4 Y; O5 ]: m- \; H2 s3 Y6 I; c' L' N
    ts.year
    4 [. w+ L% ^  l* d! i; i' @  p6 S0 @Out[7]: 20208 [- ]; ~& e5 B
    % i/ n+ _7 i( }
    ts.month
    4 x) h8 }; \4 C7 O( B9 [Out[8]: 18 l* _1 O( \; k! ?& e- y8 l

    7 h# H7 I  U: f  P8 l* Xts.day
    ' K5 m' d+ R. XOut[9]: 18 B8 Z8 n# a$ G9 L7 T# y" L0 X! ]
    ! P  B8 C5 q- ^( i3 D
    ts.hour+ i( Q8 Z, `8 f1 l1 k4 i" e5 h
    Out[10]: 8' N" B0 y/ D, o6 F0 [( [! o
    5 d7 N. `( d7 A7 X3 O* z
    ts.minute
    7 G/ A8 w! ?: {, }7 R8 mOut[11]: 10
    . x' L' Y9 [6 c! P* Z  r
    # Y- Z/ \  A3 Y) r* F0 Vts.second
    2 w& j3 v8 Y# R! r# ^- b1 iOut[12]: 30' F& i; I7 Z! X# e
    0 I$ R0 m( I) S- Y" o& E
    1
    & t0 k. t2 P2 W3 D. p$ ?2
    $ N& c* e3 r( K: y3
      N: y  v! ?3 d, B) g* |4* M% M0 M; s) J1 S0 z
    5
    & @8 U* m3 a9 w3 T4 p6
    # {' I5 Y  [1 X: Q6 M. N+ o. {7) y' G& U8 `3 |" y
    8
    5 f- N7 Q2 f" j; D, r9/ K0 `! B( k/ B. N+ h) X; z
    10+ Z( Z  C; E3 j0 o5 `9 i. u: P# E
    116 b4 e( N4 R, f. V) [& Z
    12) w, b$ ^8 X: e) O6 r  n
    13$ v2 r) u/ s5 f. o! V" T
    14
    " n# t( G' L3 c  `1 m3 z8 N! d15/ W4 W+ q2 F4 T: @' [! g6 M4 n
    16
    ; H$ s2 D' `3 ]177 N* K3 @) \7 }& Z' l6 O, Q9 e
    # 获取当前时间% j3 r6 \; n8 T# B5 h2 i# k) G4 T( {
    now=pd.Timestamp.now()
    + h8 g' V$ c+ p) d18 o& z8 o/ u+ y- X2 `
    2; j4 b' Z2 F$ `, T* _
    在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:; F0 ?& J4 J' ^& m/ e
    T i m e   R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)
    ' u$ W$ d1 f+ _2 K  S/ oTimeRange= ' ^7 E0 S# m: f. i; L
    10
    8 g2 Z, S) n9 E8 C0 D+ J* }90 n9 X7 t% w1 ^% n7 d9 n. Z# V
    ×60×60×24×365
      O4 U0 y7 u0 b2
    $ R6 o- E/ q  u64) Z$ C) j% {: f3 y+ m5 M

    3 j$ P0 D) I. _  l3 U5 N  \: H+ W9 g7 `9 y) r
    ≈585(Years)' r- X. S0 p) D' P2 A- a8 [
    : i, Y; U6 K" X) G" v  Z2 Y# G% ?
    通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    : B# J  L  U0 x( u0 O8 F" a% x) ?( d4 ^3 K. j# S  B) w
    pd.Timestamp.max6 y1 k1 w0 t& |) }- t7 ]6 ^5 Z7 }6 L
    Out[13]: Timestamp('2262-04-11 23:47:16.854775807')! I! g6 u- p& Q$ s$ `
    % S) p# `; z4 M* T$ C5 P2 j2 _
    pd.Timestamp.min
    1 H, X, V* G# R6 s2 ^Out[14]: Timestamp('1677-09-21 00:12:43.145225')
    " _! G& s- f7 _2 P, T- t
    + v3 S( `2 K* K1 ~$ D3 i' [) |pd.Timestamp.max.year - pd.Timestamp.min.year
    5 |9 R; ~9 L  N- }7 s8 t( qOut[15]: 5855 w; \3 N% s+ ]& [( a% b0 `
    1
    8 @& `9 k, B7 K% F$ X" o  r8 q9 D2
    7 t2 }8 f) c% a& o" c; n4 {3) C/ f( ?" |1 T/ E
    4
    5 S! ~2 i1 s: c+ o; ]5 P5
      \# z" M6 c  w0 P6
    + a. ?2 c4 v" o7 m. L' M7
    1 D  a( ]( x3 T: k2 n8
    0 t. @: l2 n9 q& K* H6 I10.2.2 Datetime序列的生成
    + ^6 S' [! E' e$ j' Upandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
    # o2 T+ u$ p; ]/ c3 n4 ^                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
    : x' m) n) h9 ]" [$ K$ r* h, V1
    : n2 k& w( _. _0 A1 F; L29 f- ~1 ?" e  f* Z1 E2 l7 [3 |$ a' ^
    pandas.to_datetime将arg转换为日期时间。
    2 f. T5 N" c6 t2 s" J5 E2 s
    9 @* E2 @0 d+ [2 i! Z' R3 r% O0 Q2 uarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
      [& L# [9 n/ Perrors:2 j9 V# Y$ g1 t; n: D* q
    - ‘raise’:默认值,无效解析将引发异常
    4 r# K4 v# `5 q% O4 o& @: `- ‘raise’:无效解析将返回输入
    6 H+ C# {, \7 ^- K' _- ‘coerce’:无效解析将被设置为NaT
    % Q( Y& j- v" \. w, C% z4 {& x/ gdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。2 h. y. Y( a2 H8 Y! O1 a
    yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)+ b# {8 S% T) e7 ^# W3 J8 O% R
    utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档) R$ b/ Y+ m3 I7 J. t. T' ]" q: z
    format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
      `* w2 }; y% ^2 A! l3 m% dunitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。6 `$ L8 Q, [" o
    to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
    - L' s+ O3 f) M; Rpd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
    2 n, W( n* y* Q3 T. x" x7 V) o% f$ V4 H2 |( K2 R; H, I5 ~
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
    ( j. F$ G. o8 a( Z, N- {+ t- H7 |. W1
    4 M% u- ~9 }- h6 u3 K22 Y4 O- |# E0 S' F! m* |
    35 b8 {: k9 e0 a8 D$ C/ B4 J
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    0 h: H* M$ k5 y9 P$ n/ H3 L8 c' y4 b6 A; c9 M3 `8 u
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d'). d& I- r  Q8 t; s3 y; L+ `) C
    temp' i" B* V4 x% J% G
    4 B, n4 I! j4 V6 U  R
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    5 l0 p1 H+ c% E1
    . M, w; J) ?% s5 r7 J! c2
    0 d" S6 u" j4 o+ w: l3
    0 q- }- }+ Y2 u: `% f( P4
    % L% T, t4 y% b+ }1 d. u  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:- _9 K- J: j  a- F+ }; G/ V8 X
    % Z! o" \6 i2 U& T) e+ z
    pd.Series(temp).head()( c: c6 d1 z8 n0 M* B' y
    0 K& e0 Y8 M0 E, ]2 C- B. `
    0   2020-01-01* `# G. ^) @8 k( A/ F7 d
    1   2020-01-03' _6 j; D. d0 N( I! I
    dtype: datetime64[ns]
    , C- j- \9 \2 w) l  J1
    # P1 b! h; i( C" H- ?0 V2
    * e7 v. ]6 \# j* R' M( y3
    4 [! H+ X# O: z4 I4
    ) G1 @1 W* v; L7 @/ P5
    , u8 o4 r" |& J+ y! `- c% N下面的序列本身就是Series,所以不需要再转化。
      m) ~0 M) M* ?. L: o
    # T0 N8 k, l  B) R% T  G& `df = pd.read_csv('../data/learn_pandas.csv')) ^! @! m% t6 t7 s3 P7 _
    s = pd.to_datetime(df.Test_Date)
    9 e" F3 e: V% _& m$ Ls.head(). l" t" A5 p( p9 ?  d+ A" L* ^+ j: r
    ; z& t2 v4 U( w! l
    0   2019-10-05
    & C9 a1 O+ x4 n, Q1   2019-09-04$ j4 @2 l: {4 a! I
    2   2019-09-12
    7 S" H9 t1 N0 {  a8 u% m3   2020-01-03: t* a8 n2 M' g. M
    4   2019-11-061 M7 d3 [6 O, R% U$ @7 V
    Name: Test_Date, dtype: datetime64[ns]; A% c8 _$ t- @$ v
    1* d% V* c# E) f! u0 s3 ]3 C
    2
    - C4 D4 \: N- g0 u" v% F( m3$ s2 G( m8 i' \# Z5 x- L/ |
    4$ |  t8 k, M9 C3 z
    51 Z8 h( Q- z2 c; x  E. t9 T
    6
    % K0 p# X# j; L8 s7
    * W3 c4 k; J( R/ H9 u: C2 Y6 l8+ A. s1 c6 P+ W
    9: {/ o7 v1 [2 D! }9 e
    10
    . t4 }4 M, q; G; c$ u" Q% w把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
    * s( V$ P# D& U6 Vdf_date_cols = pd.DataFrame({'year': [2020, 2020],+ L4 h( o- t! X, d
                                 'month': [1, 1],
    ( _8 k3 }, g. T! Z- g                             'day': [1, 2],
      f. R3 l# n' |: T  L                             'hour': [10, 20],9 {: A; k% Y8 o
                                 'minute': [30, 50],
    7 `0 e2 t" h: q# D! }# ?9 }+ j                             'second': [20, 40]})
    ( ^( l' O8 f( x$ q1 Mpd.to_datetime(df_date_cols)
    3 [9 ?4 V# Y- t' ^% o3 r' ~, ]: p; x# A
    0   2020-01-01 10:30:20% j+ L( j" x9 h3 J3 v# c: j
    1   2020-01-02 20:50:40
    7 i! J+ e+ a7 H) v' \) @$ {4 F; {dtype: datetime64[ns]
    , S( u0 R- c+ m' K  L$ Z6 L5 m# F1  p' n  x7 W$ F# S4 |
    2
    6 v  f5 T9 i" d& g. I+ I3 Y39 F# o4 G1 o/ t( p
    4
    % l" c1 i; r  Z9 b. J! N5
    . `' f& e5 I! Z4 R6 G- d8 C/ H6
    + x; V1 T4 w' `* t2 p5 P1 {' c7
    8 j4 B  f; Z. P  p% |8) i( w; |, y: ]7 v5 |0 M
    95 B5 z6 G( u4 K4 b  n+ o( c  u
    10
    3 v4 @  t* \% u& a* J; ?11
    ; @1 U7 F5 [+ K" vdate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    : [+ S1 k$ w! k: Z! x1 z" Hpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含7 ?8 U  l( u' G* j7 d+ {2 V. S3 u
    Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')5 e2 @* T# a0 f
    - _4 C0 I1 ]6 {2 s6 S! ~  K3 ]8 g
    pd.date_range('2020-1-1','2020-2-28', freq='10D')
    9 l+ o/ {. E; N& C; O4 s9 U$ v2 [* f6 JOut[26]: 6 O  J3 m6 T3 E0 U
    DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',: P. s- {+ x. z/ M2 {
                   '2020-02-10', '2020-02-20'],; Q+ r8 |4 x6 R+ d1 W! v
                  dtype='datetime64[ns]', freq='10D')
    , T. @  s) Q: W" _1 I% w- _! W8 c$ `7 G* M; x1 S
    pd.date_range('2020-1-1',
    0 o: t0 n$ w0 I              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
    3 c0 o) }  i" S6 n* n' Z6 x3 |1 q/ s+ F" L- Q/ @/ W
    Out[27]: ( R* `9 x5 N; B! c# r
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
    % `9 \2 o# ?! G( F" w9 `* F2 v% Q               '2020-01-24 04:48:00', '2020-02-04 19:12:00',
      ]( [$ A. B5 I3 P/ T               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],
    8 x, D# E$ v* Y              dtype='datetime64[ns]', freq=None)
    ) @" l: k% q. G" v# e6 `: u5 r/ @* i* a, G, y/ D. o1 V
    1
    7 S# G, {' Q* G1 P" C2/ b0 E1 v" G) W0 T8 ?! t4 K4 w
    3( Y3 x3 E0 U/ q- J3 C) @
    4
    % c6 S4 s! u- w; K! W, P% ?; X1 q5( [: V: e0 s- N; u
    6( a" [- k7 m+ m8 a# A# |
    7
    3 s7 ~( j) ]5 c4 h8
    1 @0 N" f3 L$ c2 D; b$ z1 I9
      j/ o6 A; N# h$ y10; }# G$ j$ i+ K9 ?: c! B
    11
    6 c4 E2 N- P$ q; C# `& ?128 ?2 @. p  J3 D- ~7 U9 m
    13
    4 z. P* a  v. @1 o! D# m14( g: B. ?) }9 m9 w5 Z
    15) y1 N1 m$ b8 R  @
    16+ J* N  G# [7 r5 _  K
    17
    ' r8 u' m# c. z这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
      p( {7 t# ?; W9 }3 c; h1 E# V/ Q) G3 D1 |  w4 A
    【练一练】
    * i# l/ y! j( g7 n% \  rTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。  ]! E$ f) R) E; N! b$ w- F$ ]' r+ [
    9 t9 O8 E+ j; f) v+ L; {
    ls=['2020-01-01','2020-02-20']1 _  W* d* f$ k! t) K9 r  }6 j
    def dates(ls,n):4 M  B1 p0 t/ k5 ~
        min=pd.Timestamp(ls[0]).value/10**93 m3 k  y# ?1 g" E/ k- ]/ e/ F5 I
        max=pd.Timestamp(ls[1]).value/10**9
    4 f( f! ?% Y  N. B    times=np.random.randint(min,max+1,n)0 v5 _) o, i* V; x( H7 J! {
        return  pd.to_datetime(times,unit='s')" p2 ^- v) i! C% \
    dates(ls,10)   X, x9 F4 a6 d
    - K$ W$ z) o5 \  S3 A  e
    DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',4 s( o6 A1 [8 u: [/ t
                   '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    ( Q& I, T6 C4 T               '2020-02-15 00:18:33', '2020-02-11 02:18:07',' u& s5 ~; Y3 L" B5 S
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',
    & d# l4 s: P! ~               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
    # E6 m4 w# P; C  w              dtype='datetime64[ns]', freq=None)" I: Z1 k, X8 `+ X# }* ?# c
    1
    3 g7 a4 ]* U' t2; V( w1 {3 z( w' C, n7 H/ S
    3  G+ u! U$ P$ u$ L' p
    4
    & S! B$ b0 k6 j53 C, t# e4 o) U$ ]1 p3 C
    64 z4 d! J: n9 z
    7
    : n/ t: `3 N1 e, d) Q. d4 w) d87 W8 v' t6 D. R/ ]9 U2 I8 N
    93 R/ s$ g0 X4 |, A
    10
      ?! y* v# K+ B/ L11
    6 @* a$ n5 ]# K. Z* {* H5 {# \12  h4 E  y7 l  ~! `
    13
    & I# l# h" |" F, v) h% w14
    ; U: ~; I/ ~% J6 o6 uasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
    ; @) b7 |( R2 q! Es = pd.Series(np.random.rand(5),
    4 v0 N7 c* [9 m: U            index=pd.to_datetime([- b) t: v* R6 w# I+ A/ y( o- n0 Y# |% ]' T
                    '2020-1-%d'%i for i in range(1,10,2)]))
    ) z, M3 \/ Q7 \/ e
    7 Z2 T" @/ A3 _3 h; m- O
    / ]( W; J9 e  J% \/ m( A* [% u( a2 m& Es.head()/ R+ a, y: F1 f8 o9 ?& ?  ?
    Out[29]: 4 y+ e0 w# {, ?
    2020-01-01    0.836578- b* }* ], h9 }5 P
    2020-01-03    0.678419
    5 w8 w2 B5 M9 K" k" j2020-01-05    0.711897  R0 P8 ~$ S' ^4 O
    2020-01-07    0.487429$ s! Z% e% a0 O& w
    2020-01-09    0.604705& ]4 t+ D: [' A/ H3 I8 r
    dtype: float649 H, Q  a9 B8 ?+ O
    1 l' M4 H' G* y; c+ Z% h
    s.asfreq('D').head()" l: C0 t: b7 h
    Out[30]:
    2 t: v' j! R& i# j/ t/ ?2020-01-01    0.836578. `" R3 j/ s9 K; b7 W( t8 m
    2020-01-02         NaN
    ( Y9 v9 P, V: g- {/ e6 w, t2020-01-03    0.678419' i% [0 k- m! ]8 C8 O8 x& Y8 y
    2020-01-04         NaN
    6 z% m9 b$ u0 `% r# ^" R  K- b" c2020-01-05    0.711897
    - ^5 }8 l7 {# G" e6 A7 @Freq: D, dtype: float64
    5 w4 h0 ]- j9 i0 k, ?
    $ k1 u1 u" X, l, p) k2 fs.asfreq('12H').head()1 }8 H3 e6 H. C
    Out[31]: & o* I, s6 M: i* M
    2020-01-01 00:00:00    0.836578
    $ H3 b' c2 S% H! N/ V2020-01-01 12:00:00         NaN
    ) ?% ^1 \! Z1 C- r1 P4 r2020-01-02 00:00:00         NaN1 K: \2 ~7 V3 L; A
    2020-01-02 12:00:00         NaN
    , h. }) J- l5 \2 G' H2020-01-03 00:00:00    0.678419
    " q0 R" S$ O9 I# ^: w+ YFreq: 12H, dtype: float64
    9 B8 P0 N, X: a: q/ p( f
    3 x3 V2 [3 |. I1
    / O+ \  l+ c7 [% p. ~2% D  k% _& ^( [5 B: F# A1 Z
    3
    & E; h& v" |3 u6 T4
    " B/ A7 R4 x+ w* V* I5% w& I+ a7 T- R2 K" |/ @
    6
    # F% H. z( d! g2 E" x4 @3 o7
    ; u8 B( N# W4 W% d8
    ) i  Y2 g- r; S9
    . C* L2 w  h; P: Z/ w10
    3 S/ [) ?& f& g# o) \: v2 y; N11
    % \8 m: A) ?7 [, O* [$ g* B4 m  H12
    9 R# x( r9 {' B  U13
    4 N* V1 y( N8 t) C1 N$ u14, S  h+ M& w; {) g- C+ q, w) F) B" P
    15
    ) D4 D, r1 t9 L6 Z16
    : G! M6 [# d, }, H5 `0 u17$ Y1 j, I' t/ U) Y+ ?# P
    18) l( U# a& Q, c
    19
    2 _, L6 a# l0 ?* C) h20$ |" T+ D, d# U2 ]
    211 W1 {% ]& `2 g0 C' s' ?2 T4 w
    22
    3 x- Y3 a1 y8 y, }5 e2 o3 H3 N. p23. S0 O) D* ~" n8 u
    24
    - c" {5 J: p' }( n7 v* i25
    2 B, L2 W+ f0 q" D5 ?4 q7 d& O. I26
    $ H' e( n% T. i/ m) j# H/ z27
    ' e* D, C/ t) H. [4 X1 [  j28
    " r% p  }; h6 k4 ?: e: w. t29
    5 `) _1 V# `0 a30
    / i& k2 K7 P9 R) s2 p" f0 `; R31
    # @4 _% f/ s) ?1 i( [9 ?; r. j; w【NOTE】datetime64[ns] 序列的极值与均值0 Q0 ?" _. f" `! F# p
      前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。5 U* _  l1 g6 Q# p
    8 c4 ^6 ]. H6 L) C7 O8 r
    10.2.3 dt对象
    # L5 w: _' T) ?0 Q: g  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。8 ]' H" Z0 G* a3 [! I) h

    0 {, H6 S8 S$ v8 I( q8 j第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。. ^2 Q9 P" g0 _9 F. S" J- d2 ?
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
    . [, h9 S: m3 E; G* z: [8 X3 ~' F7 ?+ C
    s.dt.date
    ! z" v, z1 X8 n, [Out[33]:
    ' K4 Y) O; z- l" [; n2 n' O$ N0    2020-01-01- X) M! q9 V$ Y' v6 U
    1    2020-01-02
    # ^/ s7 q2 K* L  f( {) w" r2    2020-01-03
    7 |5 b& R% t, D7 z8 e: sdtype: object; u% h5 x4 q- ^- c* B( x, c
    , G6 H$ J2 N* L) f9 Z
    s.dt.time. t' y2 V, M1 ^& p4 P8 C9 |
    Out[34]:
    ) Q7 L# x( M/ \7 S& ^: v! n+ \0    00:00:00
    0 \  Y+ }) \  z1 M1    00:00:00
    ! n9 m" l! P! k  U! F; F2    00:00:00
    3 k: E2 ~5 T9 n5 w8 q2 Wdtype: object
    / F$ Q6 ~8 k' T$ M  s% R9 ^
    % i0 [7 k( H. `5 n6 ds.dt.day
    8 Z! t: B9 f9 n# {' Y9 h* e* u6 YOut[35]: ; x$ `6 M4 W1 x# |9 {
    0    13 g# U. g. {3 o1 z: U
    1    2
    ' z+ b  G# H/ b; d  u% e0 a0 `2    32 q/ s2 A8 d! X; r! h
    dtype: int64% F- \; O: G: F4 j
    , J) C* p- f, Z9 [$ N( J
    s.dt.daysinmonth
    0 V. o3 N5 _: U3 F( |1 SOut[36]:
    " q! r8 `2 g+ n8 E( P7 Y* Z5 u' i0    31( D2 P7 ]4 K4 x$ F) z! s! X$ f$ {
    1    31
    ) G1 t& m, e+ b5 f5 _2    31
    4 C8 v! ?' F4 E; h* o+ ]dtype: int64
    9 q# N& I+ I2 P9 }
    $ p' J' Z8 }$ E! Q$ `8 i( o6 D3 n11 M" q9 M/ b0 j8 F6 w0 W% u
    2
    # F) ~) m# Q8 l5 w% J- Y" [, Y3
      g( l& g: S. |! b# d41 n& L1 e( @. U) R' K  R5 i; s8 {4 y
    5
    $ H7 T' {4 Y: ~2 T6( `# i, t! C" {* K. Z) {
    7
    8 Y& H+ o/ ]6 i2 ~0 n: i2 p* s80 Q  {- U0 a2 F5 j  J# T/ e2 z
    9
    5 F  x; i; v4 }2 E2 e10
    5 Y- D+ G4 N% p' P2 j! W( \11- b6 ^3 F7 A( d) \" @, I
    12' u" [4 \0 S( A
    13
    ! [5 J. E5 b7 P' x' {0 e14; Q# ^: t7 ?" e4 {% s9 G: o0 B. V
    15! K& k# m  G& ~/ e1 M8 _9 Y7 N
    162 E1 N6 @) D3 C; s
    17
    * b( E! q" q7 y2 ?; J( e* q2 g: T182 E2 Y$ f/ Z1 G
    19. w* p! ]9 S: w" z
    204 {" W8 _" A# Y7 S
    21; e6 U' [, T* B6 q( h
    22
    0 q. k, I$ v6 S* [! ]4 b9 C9 W23
    * W6 x4 u. c" w24, X8 j& q2 c2 R% |4 H6 `6 h& y6 H
    25
    7 P- l' X/ u- Q1 I6 Y3 k* u8 }0 Q26+ r! N4 n* t8 e3 V; X5 e
    27
    4 a* C( S) j( Z* l( _6 w! E285 R8 c: W: H6 y
    29
    2 l$ `  L' E, z2 o/ R9 ]  f  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:$ B8 ?) _. F/ e
    ; T" w# O" L( ?6 `8 D! ]
    s.dt.dayofweek4 S) i6 m! J: z6 L6 D5 s+ a
    Out[37]:
    5 X+ X6 {2 b; G9 a5 E; i- x0    2
    , a/ e# r6 F0 U1    3' _7 `6 V  m, {7 T8 r% b3 g
    2    44 u  O7 `- X% |- Y5 O: L
    dtype: int64
    % Q% D: z; I; v9 g9 o
    3 d2 m5 H5 j) ~- s  xs.dt.month_name(). p2 b6 V# a( g7 N/ t" f
    Out[38]:
    * g% x& s. j5 m4 W0    January
    4 [; i0 z$ u) _1    January
    1 f9 O; P6 f* E9 o" t( x" s2    January( @; B, M7 N& p- U" Y
    dtype: object
    ! i5 G' M7 I+ m& x* L
    " i- o3 T, C- k$ Ds.dt.day_name()
    + t8 _0 E- Z" X2 y4 \8 n3 dOut[39]: 1 i; ]1 d7 F  b
    0    Wednesday
    2 {8 x7 T7 {, Q# d8 ~# d1     Thursday- O- M! g( g% y
    2       Friday
    ' r+ f- _; h; X# P5 b' Ydtype: object
    - i: A. Q- B1 M0 |+ t
    * Y6 f  h) X4 a0 W3 B2 a* m1 p1
    ) W6 }! P$ d, x- p$ D+ Q8 N2
    4 d" B$ A# p$ R5 p. L. ]3
    8 {3 K' ~) J2 z9 j4
    5 w/ `( h7 `6 S9 ^# Q1 n5
    0 b' Q$ q+ h* H3 G% t5 C( C6  w0 v- r/ K& _. C  m5 Y" u
    7
    " _- ^# `+ m9 \) q0 ?6 N9 M8
    . |2 v6 p) \, v9) b/ U) E: |, T; V0 }
    105 m. }" B  J" K  X0 C9 n
    11+ b  }* I; Q) {3 I8 X, ]" A
    12
    & t% }3 E3 d* h7 ~13& t! W7 l( g' j' t) S" ]: D
    14
    6 G+ m! r2 E) a- W157 ?. t5 d. T" E' B
    16& J3 [( s3 `) z% K# l
    17- m/ D1 e! |+ G2 V8 _
    18
    3 _& B! E4 l" ~19% P1 U- x4 g$ _5 d4 x: l& M, i
    20
    . Q# p% T' F- G9 S0 {0 {第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:( q+ E6 v* E* o' ?# R
    s.dt.is_year_start # 还可选 is_quarter/month_start' n- e! e1 i* T. B
    Out[40]: / W/ S) c0 ^3 X, A
    0     True- H7 U! g% W4 b# e& T9 }
    1    False5 n. L8 o1 |- T+ ^; r1 f1 P2 a8 t
    2    False5 P' [, H; K6 M1 \1 p
    dtype: bool5 L! X5 Y+ w% A
    , V" y  A8 W' M, a1 a0 p
    s.dt.is_year_end # 还可选 is_quarter/month_end, |( [3 I! l6 t6 {  d0 E
    Out[41]:
      `8 m+ n) t$ {$ v. r6 G0    False
    ; [/ @; _  V  s6 g3 n# R/ n5 G1 @1    False# Y- }6 P+ l5 d! K& P* c
    2    False# F5 w6 Y/ V2 \4 S/ z" I
    dtype: bool
    4 Q; h! J/ `. g! ^8 S. n9 O1
    % y: `' t& v( z( a2
    9 b( h6 M/ `& O, I1 B' @3" C' Q1 x! w4 X, l) V. `
    42 l2 g: @! h: d% p/ R, Y* x( b
    56 s  y; c! I* }
    6+ N2 g! U6 L" h1 Y+ x9 L. D) U
    7
    ; g- O4 L+ E0 @1 p/ ]% ~8: Z. X( }/ F5 d. A7 E$ P
    9
    * o4 ^2 y; L: \* i6 L10! h3 A% J2 ^8 A- G
    11
    ; z  H- d' [; M3 d3 J2 i0 q124 ^1 R( Q) M+ m! L* H: k/ Q
    13% I4 |# X) E/ K( }; i
    第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。/ W& t2 }: L" `9 t
    s = pd.Series(pd.date_range('2020-1-1 20:35:00',
    , D- |/ r7 @9 q9 l7 J, G, P$ `                            '2020-1-1 22:35:00',
    5 e) Z6 C% G% g+ a+ E, A# p                            freq='45min'))
    ( }) A) P1 l7 q% Z( |+ W0 W! w' F3 x2 e1 y% k) V: \

    % W- y; Z. F: c2 A  _5 J! M) P& |s4 M& x9 F: M7 S0 Z9 ~
    Out[43]:
    * Q' r3 w. H3 V9 r8 @) h3 C6 B# _0   2020-01-01 20:35:00* J/ m% H5 m" H
    1   2020-01-01 21:20:00( `! B' T: R. T! i0 D( K! G/ ]: _
    2   2020-01-01 22:05:00  Z# b. W$ L7 w; F
    dtype: datetime64[ns]
    + B8 o. [, C4 d( d5 w4 @2 S
    ! P/ c; B' |, h, Cs.dt.round('1H')
    % `: m  t& ~) M9 g! sOut[44]: 3 k6 P6 G1 E( o. O7 I& L* B
    0   2020-01-01 21:00:00
    , B/ W; X! s2 k+ ]8 Q1   2020-01-01 21:00:00
    " j: i# |! D8 m; Q2   2020-01-01 22:00:00' D7 J9 C/ y' D0 ^$ O9 Y
    dtype: datetime64[ns]
    ' y& o) i7 j, K1 R6 \
    % P3 B7 s4 v" h( }6 {6 b4 n4 }s.dt.ceil('1H')6 [, n6 y% z! v
    Out[45]:
    ; h7 [2 Z- S+ G% x! B$ ]1 V0   2020-01-01 21:00:00) A3 D/ K( h5 l
    1   2020-01-01 22:00:00. [: A* f$ p9 y6 s4 c
    2   2020-01-01 23:00:00
    + l6 S1 ~- H- Udtype: datetime64[ns]! e( |, M  g4 v
    2 ~6 C" \4 l" w$ ^. ^! M
    s.dt.floor('1H')
    # X" w7 u1 j) vOut[46]: 3 P2 |! t1 l1 I1 p, ^9 r5 E. ~
    0   2020-01-01 20:00:00
    5 ~! K- F, Q/ k# c3 ]; Y1   2020-01-01 21:00:005 @8 \5 T8 A( M# w; |
    2   2020-01-01 22:00:007 [4 V7 m9 \: q  C# X8 y
    dtype: datetime64[ns]- X% c. k" n  l1 _9 W0 L

    ; q% ^4 K6 j: f% V1: B+ o. C( h0 r, b) c" J
    2
    ; X* z6 i1 k, M& w& ~39 W% o1 p+ v2 N' X" P/ K+ l: z
    4
    ) D2 N7 j: [9 E3 ~/ G59 S9 u! Q% S! j+ z3 k! r# |
    6
    ( w- K# `6 P) n+ E9 D# p+ }7
    % S6 g" @" R, }8$ ?# F) O% v/ ?( Y
    9' D, z$ N. e; m) T' ?6 x
    10
    0 ]: l. ^" f. h, ?" u7 _! j4 c11, G9 N8 s0 e5 l/ X9 Q) y/ u
    12
    " Q# _7 p8 ]) \  H13) s1 j8 ~/ s" k, ^( H
    14
    ' t9 K6 x! Y2 t. E" R15
    ' w3 h$ j2 P+ ^% W( R16, D2 H7 \* }0 Z% @# X+ R
    173 l: |; j6 f; ^! d  F; v- w
    18+ z) U1 U7 Q8 ]& q) ^+ k
    19
    ' u) Q( W, {# s$ k! X1 D: d20! \0 c" m9 k; Y  t# N- N% \8 a
    21
    / q1 A, U6 a$ ]- y2 K, Y: q* C8 h227 K& X6 ^$ f* _" y- \7 v& `7 Y
    23
    + g7 R# y6 M+ J& w24
    / a2 A  A5 R' \+ @2 X2 U25
    * `$ D# h- O! t* \26
    % K8 p# G, B: \6 K& U27
    8 S9 \7 Y. J! w9 m) K" _6 W5 R! S28
    ; f3 b0 S: |1 L' n" a29
    * I# u; E9 w1 p: m, @9 g" _" l30' X, M& R$ q# w% s
    31
    # i" }( ]% W! s. d! ~3 T9 C& U32
    7 @, [- ^2 k, Y10.2.4 时间戳的切片与索引! z+ z; C5 p5 e! G; F, O
      一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
    * m6 `: a* D  Q  S0 g4 ?$ W$ F) d5 Y/ }
    利用dt对象和布尔条件联合使用
    , O6 U7 B# j0 r# _7 `- M0 @6 u! l利用切片,后者常用于连续时间戳。& l  f. p* d" w7 ~7 i0 [: F
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
    , V& x4 R+ t3 |. Q) r: jidx = pd.Series(s.index).dt
    , D& {$ q) k# x" X9 H! o% V+ H3 Z  as.head()
    : ]) B- z2 k5 t2 r, X/ d1 S4 Y# w, V3 w
    2020-01-01    0
    7 d$ r8 X% r0 G( x5 t% t; i% |2020-01-02    1
    , Q8 L* n- p7 i/ v" J/ p1 N2020-01-03    1
    $ P4 g% w& {6 H7 `: _0 d2020-01-04    0) v9 C7 g' N2 Q/ c/ X
    2020-01-05    0
    4 s. P! }' t/ D" n# `Freq: D, dtype: int32/ |2 U7 ]: W( Y7 [9 m; v( j
    1
    " \5 h5 d( K: n7 T& A2
    5 W- C5 L' i! C! l0 M6 W. L3
    7 \: h' l9 q& S) I. r4
    0 O' r3 V& v$ ~5
    ; M5 E0 B$ |3 O9 E: f0 c! Q- V6
    % k8 N! O( {+ N; ?, Y* S76 t  b8 _1 Y5 s5 K1 W, x1 a
    8; w& }8 N0 I$ s: |
    98 s# {+ b$ K) R/ S# L( Z# U. Y0 U
    10% H* N' ~! F2 x" w4 x8 q
    Example1:每月的第一天或者最后一天( Z, ?+ o. w9 g/ P8 X
    6 k2 c% b# ?7 p
    s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
    # m* l, Q& g" Y: }% qOut[50]: * @, ~, e* [% a: B! `, Z! k
    2020-01-01    1. b! d0 O; K( A: {7 J/ F& |' ]* G4 Y
    2020-01-31    0. }2 Y+ G, ~# x7 W
    2020-02-01    19 c. B9 r7 R* m/ u# v
    2020-02-29    17 ]* O. n, O) i0 `! Q$ H
    2020-03-01    0
    : Y: |$ O& L- i5 P. _! L7 }dtype: int32
    7 z! R: U* A+ X! p1
    ; j& s( z2 x# y' k24 k; D0 o% L( E( O  `, ?+ M7 ~
    3" D4 Z& Y0 `8 J$ s% h
    46 m6 E  ]- I, L5 j: L8 z3 D% }
    5; O: `  j1 `" U* n4 I$ q5 [
    6
    / u3 O' [5 h8 }' A% x9 G8 p73 y3 u$ z4 v* o2 ^, |2 d5 l; w
    8" {" ^/ @) _% z/ W* o/ Y
    Example2:双休日; v6 d, `# O$ g. M8 j

    7 z- ~  K# `5 S; h7 zs[idx.dayofweek.isin([5,6]).values].head()2 }! X% S0 i% M) D
    Out[51]: 8 _! a8 `8 e6 K9 `1 e& {. U
    2020-01-04    1
    * t5 g* h6 k* o' M7 s0 D2020-01-05    0
    8 S+ o) A( o! ?8 \- P2020-01-11    0
    . w4 h; P/ L+ T" W) D3 }4 L2020-01-12    13 m- n1 z* o$ U$ x9 ^) h( W
    2020-01-18    1
    4 u0 {6 \& D8 R& O3 z$ N7 Cdtype: int32
      a6 K! Y3 U, q0 ?1
    / h' A0 k4 c, Q9 ?/ O) g2
    9 h  |. [1 j! C: c! p; C: Z8 u( c3
    0 q' g, d! M# ^1 ^0 B$ D4
    # {2 ?8 E- Z( e. n5
    8 z  O/ s9 t3 X6
    7 e* L2 o! r! a; n' ]76 `' z, s6 L9 T: ^( c( u) U
    8
    $ r. J( U& T! wExample3:取出单日值
    0 Y9 g$ Z3 c3 S6 g/ w+ m3 A" w8 ]" _2 U6 m' ^& q! {0 m- z5 _! W
    s['2020-01-01']
    5 o: A% c3 f9 k3 p, U% iOut[52]: 16 t* W4 Q" g, f9 V( X
    8 Q6 o: [# s7 C7 E
    s['20200101'] # 自动转换标准格式0 n( o: {+ H, ]! k+ X7 V8 P
    Out[53]: 1* i7 `  ^" v* G; {
    1! L4 k" ?+ U3 ^+ ^; G
    23 J  T$ ]% a2 G6 r
    3
    " C1 b" G9 I+ n1 A4. T6 h, w) k5 F% D2 @
    5. D+ t, ?6 p" d% V3 U4 ?( @$ n
    Example4:取出七月
    / Z6 \, b' i4 G2 G% L8 Y* K; ]: M- c- I% x% I
    s['2020-07'].head()
    % o8 t7 z" i/ g7 k% X7 g& |Out[54]: 7 W" L3 z* B! F( Z6 g9 U& u0 ~
    2020-07-01    0* ~. L" H. ?9 H/ \
    2020-07-02    1" ^! X1 N: F; ?
    2020-07-03    0
    . h* v; s: Q8 `4 I# R/ w/ [/ R9 L. w# Y2020-07-04    0; n; Q8 x6 G8 Y; [5 h( q' L
    2020-07-05    0
    4 E$ {0 c* b8 o' S" s3 fFreq: D, dtype: int32
    4 l8 i* s8 f- k, h4 e+ w+ M4 L* H1
    3 N0 b8 H" S' v7 M1 Y% y2
    ! {& K- ]! v) a* i  E+ P: R3
    / \6 C" V( D( y5 b1 S46 Q- r3 L2 i) y$ F/ ^0 i
    5
    5 @! L$ C# H- }2 g. Z2 V) k64 E! Z  M( U  E  Z7 J6 j+ b6 O: {
    7
    0 n% U4 X8 N5 y2 Q! k+ X' L8
    ' m) l/ f/ a( ^8 x* o5 EExample5:取出5月初至7月15日
    : e, z. p1 q1 I/ [6 S( O6 T" o5 L& E0 f1 X
    s['2020-05':'2020-7-15'].head()0 t9 U& g# K  D% q6 Y9 d
    Out[55]: 4 D* |8 T( n, N  E' H7 }+ `  E  M3 B
    2020-05-01    09 w' S# |/ \( |  `& p# v: p
    2020-05-02    1
    - Z, u: g* u! P4 e2 ]5 f% v2020-05-03    0
    9 u0 B1 P$ T: b0 ^2020-05-04    1
    9 S0 c# ~2 |* ^. s4 E) B1 j  h2020-05-05    1
    / D1 q& I" a% V# N% o0 kFreq: D, dtype: int32
    2 a9 t6 h# h" H0 L; s+ L4 H2 O
    ( g4 p  ^% L; y: W0 H  ns['2020-05':'2020-7-15'].tail()8 [3 ~' g* p6 m' B* e9 F( I
    Out[56]: , G2 n9 N2 Z5 \' [
    2020-07-11    07 S: x) L/ Y3 p* Z
    2020-07-12    0
      K' n% x% I6 f- s# I0 @2020-07-13    16 R* L- S! @* Q! D4 \
    2020-07-14    0
    * B  j% |; _8 ]& P2020-07-15    1" X: l. k" g  t; E# J2 Y5 ^
    Freq: D, dtype: int32
    ( C1 d6 h6 U; @$ Q
    3 ~* [2 X8 ?# M( b/ G1 I' p1
    & _/ I( v! Y0 A+ i' y% I$ c2. ^6 t: Z, e3 u5 H( `& P% j$ b
    37 f% D) W8 Z4 j
    4) s3 i) ^0 }! {4 e$ d/ `
    5
    4 f% w+ s6 M' c2 I+ M1 a60 V) Q" h- G; Y
    7; D' V" Z1 T+ n. p0 \" Z: G
    8% v5 G% l3 z: `0 ]5 W4 ?/ @1 f
    9% Y0 ^( L* A- l( d; _
    10
    ; O/ Q0 V" H; _11/ `% ?1 y8 J  B% Y) r6 H
    12
    2 l1 \% m1 b, X13
    7 Y1 T* ^0 J8 v2 _14
    % K' d$ Q5 \" [( P15
    . F0 B% @. M: v  K3 K8 R16
    - N3 c) `3 b2 q, z4 x17- D, x2 @) {% V6 C! y+ N
    10.3 时间差
      X+ s8 ~5 T! P7 R10.3.1 Timedelta的生成( i! s) C% P7 _  D5 @& j: F2 _+ i, O4 u
    pandas.Timedelta(value=<object object>, unit=None, **kwargs)/ ^) {" J& Y- [8 ]/ s6 P
      unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
    2 V# G" k/ Q. f3 a  _& [, B( b  可能的值有:4 d. W0 G; b  M$ W" r
    / B' L) j* q/ n  M( s2 D2 r) C
    ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
    ; a# }0 t# c: _6 v# j5 f‘days’ or ‘day’
    7 V5 j0 w8 P7 G$ @# N3 @‘hours’, ‘hour’, ‘hr’, or ‘h’
    0 l% C$ C* d- ]# n+ P1 U3 k6 W+ A‘minutes’, ‘minute’, ‘min’, or ‘m’
    % D7 [  T9 V" g& X1 l‘seconds’, ‘second’, or ‘sec’# w1 I" u- o9 D: i& M+ x4 C8 o
    毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
    4 q7 R/ G: @* O微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
      Y) L2 P3 {7 A' \0 L8 r" k" e# k8 A纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.1 ~1 b0 B. G2 A
    时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    " K: e' _" z) f4 E: p/ a* Xpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')1 U0 P2 {6 l) A/ j/ Y
    Out[57]: Timedelta('1 days 00:25:00'). I2 }7 R( I6 B* E& Z# b. f' O
    + W4 C" @& s6 b2 y' r" c
    pd.Timedelta(days=1, minutes=25) # 需要注意加s
    # ?% C3 o+ {" j" TOut[58]: Timedelta('1 days 00:25:00')
    % h& z# f" _( m! X  p; J+ H0 s: S5 C% T' c% W; q& o
    pd.Timedelta('1 days 25 minutes') # 字符串生成* b4 u+ i# L+ @
    Out[59]: Timedelta('1 days 00:25:00')
    8 {1 f4 ]/ H+ v, ~+ P8 o" f3 s* p# U! y, }
    pd.Timedelta(1, "d")
    , z0 s, s5 Z  ]1 u( V) `3 E; ROut[58]: Timedelta('1 days 00:00:00')9 s! I) ]! c, B% W5 h1 b
    17 c! R% y4 |1 J( V  {
    2
    # S4 P" |& b' S3( a" j' }. s# m
    4
    0 U, O3 Z  n& [. c5
    2 k5 P' O% U( Z  c6& S# y% P1 u0 D" A  P) k3 F+ X
    7# `4 d+ }& }% O1 l; C& }+ ?
    8
    4 W9 C4 u/ G' W0 }9 D8 {9! H) w8 {; l7 Q4 O/ u9 a
    10
    " _( R/ g( v; _1 S11
    + n* P! ?' a0 W生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    / L! u0 t% y- as = pd.to_timedelta(df.Time_Record)# a! N0 H" f7 \3 K3 [

    . l/ v& G$ P' T" I( N1 _/ t) {! s; Ms.head()2 n* ~; G  I5 i* p
    Out[61]:
    # ^- a  J6 ]0 G8 Q0   0 days 00:04:34! j2 L* [& Z* W7 M) L+ ~
    1   0 days 00:04:20
    7 W1 I6 d3 q# n* h9 q2   0 days 00:05:224 s2 o+ }8 \, o
    3   0 days 00:04:08
    . {* t) H) x0 V4   0 days 00:05:22
    ( l0 o; W" `  M, n5 p# fName: Time_Record, dtype: timedelta64[ns]2 U5 Y( d4 j$ Z) C0 i5 G! |
    11 Y8 B# \) x( z/ g: Y, @
    2
    9 x! x( h, K* Y7 p7 @; I0 j8 x36 M0 d( i, v- o4 S4 Q
    4
    3 C% p* W  y* Y  v5 T- A5
    , o1 e2 h7 V; F  S& e) J6
    2 O, q+ ?0 s8 ]7
    ! @2 |3 I8 q0 {; E5 w8 q84 C- [, }+ R0 _1 s
    9" G  g6 l; n6 V# j) x( _# s2 `
    10
    ) `0 r* X/ j7 W( ?- p! q与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:' n6 }8 q9 n: |) a2 j3 y$ S
    pd.timedelta_range('0s', '1000s', freq='6min'), r: K2 O$ E) n" k# R0 i- ~3 @
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')! Y& _: ]2 I# B$ B) \: Y
    4 G6 w' h7 ~5 k; k  R5 S; |/ ^
    pd.timedelta_range('0s', '1000s', periods=3)
    ' K* e2 w  `: Y2 MOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    9 |! k0 [! C- I/ ]' V/ T- s1. b. q; f+ s/ h3 M
    2) y2 K% J* E$ \' b. r! \1 }4 j
    3
    1 K& _+ x- |& i8 }4% ?6 V# j2 Y( Y" o, K. c
    5
    % _6 @  s' F2 z8 s对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    $ ]3 v) O8 y! f1 _; }, us.dt.seconds.head()
    ( F  E, V2 S8 G% O6 ~( u; VOut[64]:
    8 t* G8 [' w. l. s; X9 h: Y' g0    274' Z! I, }1 E* N! |6 A! C
    1    260
    ; w. Y' q1 P: n1 Z2    322
    + n" E, X' G$ Q: }3    248
    $ L+ I; [$ f" o3 K* C- z3 A4    322. G# J' i" l7 P  g6 i
    Name: Time_Record, dtype: int644 f1 f) Z, Y6 x' H) i
    1: J% c: k: H, t
    2
    3 F: H9 f! n" {6 T. s* B2 f3
    ; j- D6 R4 g9 Z3 r0 L- W( B/ c4
    6 q! _5 a  E. H0 H9 D- F5
    3 }, G: E: O4 d- L# H# q: F67 X  ]' d( m9 y
    7
    " c$ k; k! x5 s" u$ r8 x9 P8 X8
    2 Z' n0 b6 ~8 U& Y如果不想对天数取余而直接对应秒数,可以使用total_seconds! n( n: W* ~8 u* P0 L

    : H* f1 N$ n$ _' P! f+ Ls.dt.total_seconds().head()* d& {6 X9 m. F/ z5 r! R9 L: e
    Out[65]:
    . ~6 k+ G2 P! e& J; j( c0 b, H+ d0    274.0
    6 c& [, [% ~5 X( f$ k8 I, r6 s1    260.0  G& [0 |1 |+ x) P- T( m3 e
    2    322.0
    9 a/ F3 H% h6 @' t9 A; ?# w3    248.0( ^9 }0 j. w: {! T! V# m) Q% l
    4    322.0; q$ m  d+ h" D5 U% i9 b# h
    Name: Time_Record, dtype: float643 h" M. p; X9 ^+ y! }! L; N
    1
    8 p0 H( k- J, F4 G, f2
    : F+ ~; u7 s' a7 l3
    ) T2 y: C, J3 g% Z# G4
    8 L: t% O. P2 K/ u2 ^1 T( l: C5
    2 i1 V& ]9 U- V% Y% @9 N7 c+ q6
    9 c1 D% Z0 v- T5 [* R5 U5 \71 R6 s/ b8 x$ b# I1 f
    8
    + B* F2 A9 C# a, ]# v: c与时间戳序列类似,取整函数也是可以在dt对象上使用的:
      ~+ z  E/ w1 g
    2 T0 y5 R. E$ l5 Q. Tpd.to_timedelta(df.Time_Record).dt.round('min').head()
    2 @% a4 H0 a3 ^Out[66]:
    6 [/ O+ B' h2 r% t5 p0   0 days 00:05:00+ q7 j4 J, R+ o9 N3 T
    1   0 days 00:04:00
    0 ]! N6 s3 p  v0 {( e1 r8 Y/ h2   0 days 00:05:003 l7 Y+ G2 @$ I9 d6 V8 L, u2 y
    3   0 days 00:04:00) Q6 E5 W" ]! L( ?5 w$ P* E. U
    4   0 days 00:05:00
    ' y& {5 |$ r% d: I/ R6 m$ pName: Time_Record, dtype: timedelta64[ns]+ Q3 V. Z* T% x# Q  I" L+ L+ D
    1# b, C7 Y' Z' Y. a5 Y5 w8 b. g
    2
    / `+ p6 [4 F, ?3
    ) Y9 P7 h. o" e5 l, [49 a: Z' @: U9 h. A9 M
    5
    - l! z/ r$ s( a$ d6
    5 ~8 h7 x" @0 M9 C- g  i7
    2 w4 G7 k2 J% C, A0 {  M+ ?85 b* b" s7 H6 }/ u" B9 y0 |7 z
    10.2.2 Timedelta的运算
    1 N! e% I. I" s! S. l单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
    & P2 X  x" l. @, _. S3 Rtd1 = pd.Timedelta(days=1)4 G$ B) m- @  _- T4 z0 I" n/ B
    td2 = pd.Timedelta(days=3)% E6 @) t7 v1 O3 M
    ts = pd.Timestamp('20200101')4 A( H. u7 z/ y2 i5 n+ v( K" f* ?
    - h2 R1 v7 l: ~( J7 ?( O+ m
    td1 * 2
    . X( _5 p7 o% f" R( _9 ]Out[70]: Timedelta('2 days 00:00:00')" K% z; x$ q$ [* R4 G$ J; M+ ~

    ; X; F' K4 W% I) M6 r3 G2 htd2 - td11 J3 m  Q4 J8 G) p$ T  p+ B
    Out[71]: Timedelta('2 days 00:00:00')
    1 g2 L2 W& w" D7 K- m" \& F
    & [1 [- n: x6 A; F" rts + td1; ?9 Q$ O) b% X; R7 M- {8 X6 _
    Out[72]: Timestamp('2020-01-02 00:00:00')% R& E4 n2 D. p( @( s
    7 f9 u( r: ~- s: G& ^5 i" ?
    ts - td1
    6 ?- W! ^( l) {, HOut[73]: Timestamp('2019-12-31 00:00:00'), j$ \) Q2 Y, [0 R( U! }3 k3 H. S7 A! S
    1( [& I& c, }+ l9 x" b3 G! k" C
    20 n9 q: t; H, b0 W2 G  \5 [+ o
    3
    5 R+ B. F9 k! n4" q1 h8 w- V+ k8 D, T' i
    58 _9 v. y4 K% x: A+ g. ^
    6
    5 U2 L9 W8 e& U' h7
    # Q$ C% V4 _; b. G8 f! {- d! {: V8- q' X5 R7 Q: y" E8 g) ?
    96 t& f3 `- @2 W* @+ u8 \
    109 G# q8 c: d/ ?% m' ~% D1 p7 x0 T
    11& }( F4 r6 ?/ D( n  [
    12$ |3 z/ t+ |& e" l5 v
    13- ]3 Q% k, o- U9 a5 ~
    14
    # ]& c" \0 i( Z8 n15
    ; A; f; d/ u9 f5 W- D时间差的序列的运算,和上面方法相同:
    4 s' h) F& i/ g- S8 ?/ Z" Mtd1 = pd.timedelta_range(start='1 days', periods=5)( U& J- F/ v: a% d
    td2 = pd.timedelta_range(start='12 hours',. j% t5 o$ a  O. }
                             freq='2H',, Q+ j$ b! Q; F- F6 x5 \( c( m
                             periods=5)
    7 E. J' C$ o3 A8 z% Vts = pd.date_range('20200101', '20200105')
    * B' h2 ?. X5 q7 E5 w2 Ltd1,td2,ts
    ) Z5 l% X/ M/ s) A$ O4 N5 b
    " L$ ^- y4 p; _6 z. U- sTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')8 n$ `& I& [% j, S4 F$ _* @, ]
    TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',# @7 l8 V1 p6 a) p7 u$ \5 {1 B7 U# A( W
                    '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
    : p8 O8 i- F$ ~DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
    $ w( W; e- w4 G- a* `. r: X               '2020-01-05'],, G( I  {( F2 t% z3 |, ?/ v
                  dtype='datetime64[ns]', freq='D')' P: g: N+ x. ]! [% w; l  O
    1, z+ e  |( v, y7 R: d" j0 ^
    24 \; r& P& w3 X# ?
    3  J( u3 q7 Y( L( U: h! ^
    4+ x& H' \) x* ~! d9 [( n- Q0 n+ p
    51 Y# P# q4 Y" x: Z  D! ]+ `! |; Q
    6" v" D" t3 D+ j& a0 M5 K  v
    72 \' Y* M9 E) l) m( c. f) J: Y9 X
    8
    ; C4 x0 ]4 D- ~' b0 O8 u9, W5 v+ h' m5 t8 U* `$ {4 o
    10
    1 p1 V7 D, d1 Z3 l11  L( ]8 Y: N. V7 X4 c% t1 |( S
    12' o1 e  L  a& s! B# Q
    137 n/ i- a$ y9 C9 c, b$ V! e* H
    td1 * 5; a0 |- h: r2 u: M
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')8 R# z' o; W- O9 x+ s

    / F/ h' ?3 i- V6 Itd1 * pd.Series(list(range(5))) # 逐个相乘
    * ]+ L1 `8 B, F& }( h+ V! JOut[78]: " @2 [5 k# c+ O- @: Q+ _
    0    0 days
    6 t: H% l) x5 J1    2 days
    ! {2 M& T5 w, W- J2    6 days
    9 x2 u4 `9 X. u2 ?3   12 days
    6 R* t) G0 P  @/ I! J8 O1 C1 h4   20 days
    : `/ i3 w7 J3 W1 ~6 a' pdtype: timedelta64[ns], s% D# n2 W1 T+ |# }

    1 {' P. v# X2 a+ }* L, V1 }. dtd1 - td2' Y6 ^, L7 K# ^- F* ]2 m
    Out[79]: $ [6 Z9 P* Z/ D9 S. `7 S+ \- W
    TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
    ; C/ m5 H5 A7 v  s7 M7 p                '3 days 06:00:00', '4 days 04:00:00'],
    ) ?4 J5 c; C' y               dtype='timedelta64[ns]', freq=None)
    8 v( n6 S, k! F+ c: N0 T4 p
    4 F3 f; g% t- @% w% W4 w% M' y! Gtd1 + pd.Timestamp('20200101')
    , O& x7 J+ H6 l/ Z2 GOut[80]: + d, x/ O5 X0 r  w1 g& {: I
    DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    " V$ R# `- B* P- v               '2020-01-06'],dtype='datetime64[ns]', freq='D')
    , e- i: {, m) C( A4 i. g" D/ z9 g. X: D: y
    td1 + ts # 逐个相加
    9 u9 h- x7 H) A# s( k# R3 T  ZOut[81]:
    ; c1 g- G" A' e; ~' n8 t& X" j  _DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
    ; z2 m  t2 N. p) K               '2020-01-10'],6 ?, S7 x! D) ^, n9 [
                  dtype='datetime64[ns]', freq=None)
    * t0 p( i7 f9 q1 r% y2 M
    ; L& t% E! v* x$ o! E3 u% T2 i1. I- g" ^0 f5 c5 [" C
    2
    , y. r/ M+ w" H3
    , n- Y! q" D& i0 ?, V4
    8 Q+ q, b" V2 z5 ?9 g% G# i. v5 P5
    0 `; w: ^% H4 T/ V2 F- Q65 _0 W% Y4 \* b
    7
    + t4 J0 s" N' [& M8 K7 t8" {, j5 O6 `  `! c1 H. O
    93 a& H7 N& M# K& c/ ~* E
    10
    5 W5 {, N) Q6 k* e2 w. C5 Z! `, ~# ^# s& T11* y! J, l% W7 E- M
    12; a% `  o1 o7 L8 [. g) y
    13+ o9 c" a1 R- t4 l* j- N" U$ k! S
    14
    , `  v& q) r2 y. s( r6 P- Z- P, H157 O" d4 s9 w& R
    16
    " i* z' A0 j& g2 w17
    ' P- s" a! _1 S. m18
      n" ~8 C  g9 g/ ^4 }9 o) Z# w19
    ) y4 T* E% k, w. }* k7 y4 A" c4 ]4 e. [20% _1 S9 X3 s+ }8 `. d
    218 H/ T4 e5 v9 S! {
    22  H8 \9 J0 g, h: R# n& U
    231 W) b. h2 C# h% T. Z* w- g) X2 N
    24
    1 R4 `% ~7 v9 |. }2 N25/ U* i/ M9 p" r/ h
    26- t1 Y$ ]; D+ c+ ~7 s
    275 O- h! O7 U) B, H8 x! v1 @) |
    28
    / \' o; g$ W* @! G8 ~2 b10.4 日期偏置
    & q+ a  r9 [3 v! C7 S: j' v+ P10.4.1 Offset对象& q* m' }& v6 Q. a
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
    , y- }& W5 @% x! P1 l& J2 W+ @' v( i8 L2 P* a5 E# ^
    DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
    $ h" O3 B; e( B- H4 a, @+ C% U5 ?4 G7 {0 W; _. \
    s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本% V% X- m3 K2 b% ]& V; Q
    s.kwds:{‘week’: 0, ‘weekday’: 0}
    $ B) ?" Y: v: P1 |s.wek/s.weekday:顾名思义& L3 s: ?7 n2 g0 e7 M! j7 E+ c; W
    有14个方法,包括:2 ]# {4 A6 X& S  V% E2 q
    $ d, P8 s& K* ?( W/ ^
    DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    / N% [9 E- H/ Y: y% A: h3 cpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
    - \: a& K8 b& I/ U; K- ^% e1 l& H6 X* b+ v  ^. Y( ?' S, [
    有两个参数:
    . _) I& T9 ?9 q$ N8 Sweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。/ Y) B* E" N9 S8 L! K# [( ~
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)3 Y0 j1 f. n2 o6 T
    pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。. M' h( d. O7 j6 F

    $ x/ [  p+ F4 E$ L2 h  ]pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
    $ t, ]% \1 p( d  n* U5 KOut[82]: Timestamp('2020-09-07 00:00:00')
    ; X. x: M9 j: w, a  I: u
    ) I" Z3 C" C5 k1 a4 Zpd.Timestamp('20200907') + pd.offsets.BDay(30)3 X, r1 x: z5 D! k& f7 j
    Out[83]: Timestamp('2020-10-19 00:00:00')
    - _/ _! E/ ~8 R- Y1' i) V' @/ {( r; a; K( P
    22 s6 S9 F) T' n7 g+ y+ z9 k
    37 j3 B1 ?, K0 {7 w- b& y, V' D
    4! z4 _) T  A& d3 u
    5
    . D; O9 _6 I4 M3 M/ c# E: L  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:. h9 ~6 L8 s4 M7 a- S

    , C7 R8 H2 A6 _. Epd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
    . ^3 b, u( J, OOut[84]: Timestamp('2020-08-03 00:00:00')- w6 ~  X+ u! Z6 ~9 o* ?

    ) J2 `) w. x- f* e  t6 p  Vpd.Timestamp('20200907') - pd.offsets.BDay(30)$ y2 E: A# S" g# e- S6 z4 n, L
    Out[85]: Timestamp('2020-07-27 00:00:00')
    $ K) @9 O0 M6 I* [7 S  s5 n
    + w8 I1 V( f- u% Upd.Timestamp('20200907') + pd.offsets.MonthEnd()# L! b% e3 W! y& v" j4 D
    Out[86]: Timestamp('2020-09-30 00:00:00')1 H$ s: h0 Q0 h" u1 c( o& V
    1* p% j& C# H" K: e: }
    2: L# ?3 I0 X3 C5 t( {0 W1 }
    3
    0 y+ i' i+ L/ Q( u7 }+ f2 b+ O4
    2 H' N  X$ n! t# G# s5* C7 p5 ~- G! ^8 n
    63 S9 F* P0 E) R& h4 v- r
    79 g- p( J+ Q' Y; e. x0 N5 G1 @- W
    8
    + H. \- ?$ Y( N7 u1 `2 N3 R) A) r% r  常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。9 W" _* [5 b2 b
      其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:, `# `/ {% b8 d) c9 H

    0 l& R# S7 ]- Z7 n5 ~0 k9 Nmy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])" F) ^9 c# x$ b$ W+ j
    dr = pd.date_range('20200108', '20200111')
    ) O4 y% k: T% l; B7 R( |( h4 G: ^. L; I, v+ q
    dr.to_series().dt.dayofweek
    ( D! U1 J, z/ R' H, OOut[89]:
    2 F9 b% F' ~- `4 k5 M& o7 @. d2020-01-08    2
    2 g" |: b+ B$ ^) {: B3 F, L8 s2020-01-09    30 a' T2 M% O/ X. N4 s
    2020-01-10    4! \# P+ h/ F/ \6 I% R8 D" ]
    2020-01-11    5
    ' o- V" w# j5 I- }0 s/ rFreq: D, dtype: int641 w( Q! G: u! n, {# E5 v
    ( c/ t  s0 a4 l+ w7 S
    [i + my_filter for i in dr]$ P/ {& ]$ m6 K, P/ G
    Out[90]: 8 i, }) O" u2 u/ V3 u0 [% x
    [Timestamp('2020-01-10 00:00:00'),3 j5 c& D+ G" v4 j' v- Q
    Timestamp('2020-01-10 00:00:00'),
    ; P* o9 f6 C; e3 J  ~  ~. K! o# F Timestamp('2020-01-15 00:00:00'),
    ; {  _7 j, l. |9 ?% A7 A Timestamp('2020-01-15 00:00:00')]8 R; ]' m( d; T/ o! ]

    4 |  C: W1 T" V7 l/ ~$ X* V1
    ( Y0 P4 B, s9 ^6 x2
    ; H7 Z" S5 N, \9 h36 c9 E' P0 r+ R0 H/ ?7 k+ D5 k
    4! Z7 O# X& A' K# C5 X* V* ^
    5: m2 B6 W- k) R3 q8 v
    6" f- M5 W7 I+ T5 m/ ~3 [* B1 i6 u
    7
    3 g. r. a) i3 s8
    / ^- r. v5 X/ u2 E+ H/ E. k* Q90 q' u& P$ _6 P% N2 k( l
    10
    ' g8 c) J: g6 N* m) k0 \. a1 S% _11
    " ~; B7 R$ ?: _+ N! n& X! _12/ |3 |, e2 M5 X" P$ L
    13
    2 n# T' T2 @4 r- D. I( i% O; @14
    ) [' V9 H* \4 l6 A+ B15
    5 a, K3 j0 H) v9 K1 ]* G; D162 N  r( g, p+ ]5 Q, z
    177 B+ W/ h, `7 C8 t, y
      上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
      F& _' I/ U2 P: K5 L' S. w+ x  S
    ' S& @9 P6 }# M6 }【CAUTION】不要使用部分Offset( `' m4 w9 i% J3 C' ?) w7 {' g
    在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
    9 Z8 \9 x" x4 Q. v& R& J, X3 l/ ?7 w' V5 ]2 M
    10.4.2 偏置字符串
    ) B4 u  [% J3 I7 d6 l# i" j  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。$ R8 r7 M$ {8 a; V4 @

    # c4 ?# }+ h& d: F& ?  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
    3 `8 k7 F: ^' l' |- V! I1 \: @! V7 P" X. s/ A
    pd.date_range('20200101','20200331', freq='MS') # 月初7 T; v" j5 j5 X2 ~
    Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'): E$ i$ f! K( A  ^
    : w0 q4 Y0 W9 L" n
    pd.date_range('20200101','20200331', freq='M') # 月末
    ! K! n  a5 v2 v8 o0 }# C6 qOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    % s' T* a) ^; L7 h$ C! J7 N! T, {/ s* E5 j! }! U! B
    pd.date_range('20200101','20200110', freq='B') # 工作日
    / C) Y/ Y, n3 z. A9 y2 m6 eOut[93]:
    9 n5 i) j: e: @& I- e  ^2 ^+ B3 bDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    & N1 p6 Q) i- _               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    3 M9 n6 \5 h. N  q2 X) R              dtype='datetime64[ns]', freq='B')+ ]7 [3 `: C# ^; z8 }
    ' U6 t4 `& J! U3 U5 ?! ]# i2 A
    pd.date_range('20200101','20200201', freq='W-MON') # 周一
    ' s) k3 n$ T0 w% _' WOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    ; e4 L4 O2 @: v7 W6 S+ Q. M+ E! g
    3 q" g& s! G3 }) A: u# ~pd.date_range('20200101','20200201',. \) o) F; P6 A. q5 ]" Y# x3 q
                  freq='WOM-1MON') # 每月第一个周一
    * m0 ^: @1 `) G; m! V( \
    2 i% J( f# g4 _4 r4 rOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')  Z, B4 o" F5 X2 m$ x  T% _, I

    ! O/ C. ^9 p) {& W3 l6 [- o8 p11 X3 x% Z9 J: K4 A- ^
    2
    ( d, c' k8 [  U0 p30 X: s4 |2 @; O  S
    4
    " x8 G1 e3 c  {' Y5 N5; f1 w) _) L7 H$ O8 L
    6
    / \3 k2 [4 E1 a/ a/ Z& M/ g7& O* H0 |# q) H' b8 ]
    81 p  Y) O" m) b5 J- A9 ]6 ?
    9
    5 y& m+ g2 V; b' l  S: K101 F0 B6 A9 g3 E
    114 X2 E% D8 q! g) d& x/ i5 W
    12
    ; G8 c8 C$ A8 V2 L$ q$ k13
    * \2 A: y# G% t/ D14
    ( P. U) V( F: C/ l; K/ L15. O# T  c% C- A# M& K, c
    163 r  z+ O% O  u
    17" h, g$ _  ]& H2 q: c
    18
    - m9 _6 o/ C% H! r0 F19' t6 b9 E# i" D9 Y
    上面的这些字符串,等价于使用如下的 Offset 对象:
    3 \+ ?2 O+ g2 U/ U1 I) p) m4 r9 E; n4 c' h
    pd.date_range('20200101','20200331',
    5 S' h' N9 S8 i              freq=pd.offsets.MonthBegin())! ~) \1 S" V9 {. ~# I/ Q
    : Y5 C- l- ~; S
    Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    & H; I+ h0 v- `% ~0 z/ w1 K! ~" b# b5 B. b# V7 l+ @, U6 {
    pd.date_range('20200101','20200331',
    ) m2 R! y/ s$ L1 X6 C: T              freq=pd.offsets.MonthEnd())$ C" j" ~! n" x% U

      F6 @) n! R* E. w( j3 NOut[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    " Z& T6 o8 v) N+ A' U1 u: x3 C- q6 Q- T5 P. s5 @
    pd.date_range('20200101','20200110', freq=pd.offsets.BDay())! ^' P3 q0 P/ ^& R2 x1 h. J. p7 s+ H
    Out[98]:
    % u% P9 N8 m6 K, X( o' R# Y- q5 c; MDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',* d2 f) E8 t/ }+ d% r8 I
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],2 G0 p6 o  \1 P- i, Q& p+ T
                  dtype='datetime64[ns]', freq='B')  T6 @) u- j' _1 n- p5 W' @

    8 B, t& d( P% Z/ [8 V) h- bpd.date_range('20200101','20200201',
    % q5 J# ~: [2 N. f: O4 E: W: ~              freq=pd.offsets.CDay(weekmask='Mon'))
    , M; H- l8 L! G
    0 M& \3 ^, E6 M8 \$ tOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    3 a0 [7 T! f: p' s2 j2 L  {1 e% O* C/ i& b! C7 [7 x
    pd.date_range('20200101','20200201',
    & b1 }. m, K: z: t+ z% N              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
    : v! X; _  W# w. \, t2 d7 i4 A
    ; K, q8 p' S1 c1 q2 h3 ^3 Z. C9 R0 N; ~Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    0 I: x$ n' Q  E0 Q6 D: O) b
    9 V6 A: }6 j3 |0 w: n  m& B1
    & c* D+ J5 T) ~, x# d. ]5 Q+ f: Q2
    " u% u1 B( \* m! v1 [' d4 |. X6 O9 [3
    ; F' }5 O9 o& x4 I! S4
    & v5 C2 U: \2 }2 G2 y5
    ! [/ x" W9 b1 ^6 c( c, A2 |1 U6
    * O( q8 W- w3 o7* u$ o2 b5 X# G9 I5 l8 K  z. Y0 H
    8# p; h& {. |3 Z) A  s. o1 F9 c- I
    9
    ( W% F  v4 s* Y6 E+ \100 [; B1 J4 D6 Y* v  y9 h
    114 N# A# F) x3 y9 l( @, q8 t) L: X
    12) P$ R* ]* O) z, r/ v$ m4 `5 W% ?
    13
    - X. H2 g- a% T% t1 ~  l14; ?, P2 }6 r- Y  O8 z5 m7 a
    15
    2 z- _8 o! Y3 }! }16
    5 r# }, k8 |2 W% ?0 i17
    7 `. N' p$ [3 N' q* Z18: D  B( f) D% f3 n
    19
    / X/ G- ~+ M! @0 N( j6 e& |20
    0 ~9 c" p0 l4 d4 i& `+ }" H; ^0 l21
    0 b# Z# [0 [9 @) \. ~1 c221 c# A1 o2 _* w* X. O
    23& E4 ?6 G8 \. W% Z5 Y, t6 @
    24
    5 {' p8 q% Z! [: r25# n/ D- Z4 `  r  ~: V
    【CAUTION】关于时区问题的说明
    6 c, D& a5 T3 C- ]. \- l1 z+ ]  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。& [* G: z! J/ t" |4 t) R
    , M7 J6 O3 W8 A2 m2 T: Y. b& ^3 y
    10.5、时序中的滑窗与分组
    3 ]( }* u! b# u  |' L10.5.1 滑动窗口
    9 T8 t, B* f( I* j) u8 v6 s5 D  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
    ; E( J# O, S3 V1 g0 N0 Q# y: i2 K- ?4 d, B, |4 `' |7 v3 y
    import matplotlib.pyplot as plt1 T- M7 X; ^# B3 `' U' D
    idx = pd.date_range('20200101', '20201231', freq='B')
    % ~( U' I$ M% T$ w7 gnp.random.seed(2020)5 ^: d) F" y# [: m% x' @( j. V
    + P, @7 T- u) c0 T" J
    data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加/ N: v3 Q9 P0 m0 Q
    s = pd.Series(data,index=idx)
    ' v3 l; W: g3 h" b7 u: K" gs.head()" R1 q) _: i- R9 ]* U
    Out[106]: ' Q8 j! h+ d  n% H
    2020-01-01   -1. d, g: Y$ n" t
    2020-01-02   -2
    5 P5 u# ?/ b' {0 R2020-01-03   -1) ?, L2 F$ K! D
    2020-01-06   -1# r3 C# |0 S& r4 J- ^& ]0 [
    2020-01-07   -2( x5 P! `% [6 A" l3 }/ J4 D* a  f! m
    Freq: B, dtype: int328 E+ O, V& i4 L4 @* n; P% h
    r = s.rolling('30D')# rolling可以指定freq或者offset对象
    % `6 g4 y4 o7 K& l# M+ H  }1 w+ x$ |
    ' |3 C. X/ G, U; e6 fplt.plot(s) # 蓝色线, g; ~9 @" u. k  G( E5 P: d
    Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
    $ `) n  m' B3 F' K$ C' P/ v- Mplt.title('BOLL LINES')
    , y2 ^" A- t* y" g  K% |" l/ kOut[109]: Text(0.5, 1.0, 'BOLL LINES')
    % c2 E* `/ e  E- U$ \# T' _/ U" j: P8 {0 E  ]
    plt.plot(r.mean()) #橙色线& Y- w  t5 a; e& _4 ~, P
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]' c, n5 I. H' K; v2 `9 _

    - }: X. H9 N7 `$ x  D2 c( [3 d( _1 Fplt.plot(r.mean()+r.std()*2) # 绿色线
    4 R" v# u& r, E# }Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]3 `( V" Z2 u2 G  G0 `% X! V) Q
    . o3 o% X" g/ Z- b
    plt.plot(r.mean()-r.std()*2) # 红色线) X3 c% t+ Q7 \" i( w" h, Y1 v2 t
    Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
    + I$ z. s  f0 K/ h8 b+ G8 n6 U' x2 V5 \
    1/ N- w* N1 l4 E2 o1 D# |
    2! d& q0 k# _5 ]2 y
    3
    ( ?6 z/ V( P& z5 H4 F. g4, Y2 m7 d* Z/ Z- _. H
    52 s% Z5 D2 e4 M3 U8 x
    6
    : @2 ?; m' n: d8 }7
    6 O+ }! i* \+ L$ ]3 {- N8+ \  w1 G; N% b+ G
    9
    - K; R, B* `, D3 q; c6 C$ v6 }9 g10
    : G: }! a6 D# K! k- K2 [5 R11: @* P/ C5 T. w" U8 a2 c$ ?
    12
    ; Q9 ]4 o' ?9 x0 a; q+ [! d$ I) H; W: ~138 K- V8 p8 o+ O3 l
    14+ f  b; j, ]$ C
    155 H" |2 X+ g# J. o3 v/ K
    16
    & |) [: s8 y- g& |7 t# ~17& I, [4 F1 L, `5 }  C4 h
    186 H; b1 n1 \9 q6 O3 }) M; u
    19* I( c' ~* q% {! ~* O
    20* b  l+ l) E- Y, c2 p7 G
    21
    1 o& w  O% s# f2 R2 w22* W- v' a( B  p' w/ y: M
    23- u+ W1 |1 ^/ A2 N! h- J/ U8 [2 X8 e: T
    24
    . X" ?. _; h1 U! M  |25
    7 D& \& r% r3 |& ^- x' s. a: W) f% u* A% e. i26; M$ n% z* [2 x6 ^# z  Q1 G. Y& z
    27
    % t+ O/ ]4 ^5 r/ Q4 |- x28
    # [$ ]# S. B; S; c6 ?: k: D; q29: X- m- `# ~! A4 Q: I

    8 ?" X2 Z# m5 V& h2 g3 H   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。  ]) r0 W- t5 A6 v, u. v+ ~
       首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。) `4 c! K* B4 ^( l
    ! a0 J4 _. O$ R3 a' o
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
    : w) Z0 o+ G# h$ @7 ebday_sum=select_bday.rolling(7,min_periods=1).sum()$ h1 Z- D# k( w1 @" {7 w" v% l
    result=bday_sum.reindex().ffill()
    5 W* C- ^* H% iresult
    ( Z: J% N1 }" V2 I& t% d
    : C: M! ]3 L0 |2 i" b8 W2020-01-01     -1.0
    & U+ U( }+ L. f" g2020-01-02     -3.0
    . I" C4 y0 b% o2020-01-03     -4.0
    " a. j' q8 L7 i2 C5 R: [8 L2020-01-06     -5.0
    / ?; b. D. T/ b. y0 h( I2020-01-07     -7.0$ T$ |" [# q0 U5 r+ d
                  ...  
      i3 [- Z+ n( b2020-12-25    136.0/ i& A' K6 L3 w
    2020-12-28    133.06 Y8 a/ y  k. E6 N. Y- T  h
    2020-12-29    131.06 ]  y# ?$ d. s& y- o
    2020-12-30    130.0% F: ^; w  D' v7 W2 i/ f9 }
    2020-12-31    128.0
    & r. p; t# K6 T: Q* W& M0 R6 a" DFreq: B, Length: 262, dtype: float64' L1 _& y' f2 t

    ' U/ q$ B( W6 d' m1, ]0 Y6 r% i! @7 Q
    26 M+ k# l$ Q( p/ U8 S
    3
    $ E% }. z1 ~* K4& B) j+ L3 \* y; P4 z1 U* T
    5
    + d2 [1 Z! }- P5 z5 o6
      _' c6 K& V' y- Z5 l$ P# C7
    1 k- o) C1 E5 x* z0 q8
    ! R4 V* F1 k0 f* K/ w' r" J9
    ; [* ^' v5 Y1 E* B10, O# m/ ~' X* x% n  T! d+ d
    11+ b; q& p; v8 w1 _4 l
    12% s2 \4 ^0 O! T. W' p6 r5 F
    13$ I/ G5 |1 M  Q# ]- h; T
    14* O6 ]* }# Z& q# ?& }, J
    15
    , @) L3 O+ O, t3 H7 E16* S+ n4 I) H4 q! v( g3 j
    17
    ! r7 {2 W# R0 Y9 l( D/ c  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。5 u; d/ F6 ^7 `  i4 B4 {) n3 X7 \9 M) q
    ) S6 Y2 N1 c, e) U7 r' U9 w- [) f
      对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    7 H6 `7 x7 F6 D! a7 X" u1 j8 Z9 M4 ~" F5 _
    s.shift(freq='50D').head()
    / H5 W8 X0 A7 ^3 s, wOut[113]:
    ' }2 b0 P0 I( E+ h3 e2020-02-20   -1
    $ f/ D6 F! O: W' ^  t$ h2020-02-21   -2
    4 h/ f! f. ~6 z" q) }; J- x, K# P2020-02-22   -1. G* Y  y  l3 D) t
    2020-02-25   -1+ Q' }% `) v6 o# x" [/ G
    2020-02-26   -25 q3 ]. W7 O4 A0 x! M
    dtype: int32
    4 v0 @9 q) o0 v2 |$ t. {* [# L1) H' m$ E+ B7 O$ |# ^5 q/ f
    2
    9 t, a& N. s5 z1 m) ]0 m3
    $ w. }8 Y) P; T0 T4 k9 n0 c: T6 q  Y9 K4
    . f# F7 Z& @* s6 G; r; S2 K5
    # U$ N% q1 u( W$ i" Y" G7 k, g6# U$ e, d$ g8 q: q; v: z
    7
    4 g$ N- i' J8 H- V# C# H( d8
    9 C2 p5 m8 N" |  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:4 i) u; R* x6 ^& l
    # H% @2 B' J3 p" L8 e) q$ H9 R
    my_series = pd.Series(s.index)
    : f, l- F- O- ^& K! L, o4 Rmy_series.head()# `4 W7 T  }; k! x
    Out[115]: 8 `7 `4 C1 v( @) h4 v4 u9 P% J
    0   2020-01-01
    . V3 n- K( a8 C+ S% p: R! {1   2020-01-02) M1 J5 z6 E8 U7 M
    2   2020-01-03
    4 E' u- h3 I( Y. @* Y' C3   2020-01-06" W$ \/ @6 Z( p& o
    4   2020-01-07$ H+ ^8 u+ W$ x* |& H) V
    dtype: datetime64[ns]3 Y$ T3 R# @9 w0 v, R9 i6 k

    & v7 f1 L$ ]; A8 t8 ]my_series.diff(1).head()5 L7 F+ t) v, e
    Out[116]:   D/ n! t% c: N$ J7 ]( D
    0      NaT" c* l/ F' v; j# W4 ~' k4 k
    1   1 days
    9 f% S2 l" {8 G( _2   1 days& \5 z/ t" D4 S! Z6 d" t9 t5 ?, }, Y
    3   3 days% m& z! K1 W, o; ^' p* a5 \
    4   1 days1 T3 i2 @5 S3 }# n& Y
    dtype: timedelta64[ns]0 Y9 f* ~8 v2 z( K

    . C/ i. L$ k0 X; h% i1
    " r7 m% C* v8 H" P25 e/ D4 ]9 y7 ^6 u* c
    3
      G' e+ u1 A. i' z40 y0 }  d' m  W# P+ h! ^1 C# T' n
    5. ]7 d- x) q$ z1 {
    6* x/ [9 n) x# D3 i8 S! T: Q8 o
    7
    # U& b) ]* B) X88 h  z- s6 s, z& r8 _, t
    94 J# A4 m0 o. A3 G
    10+ e  K. }/ G) ~; ?4 U
    11/ Y* @: Q. y6 e$ h# I& H
    12
    + T2 ^0 L- X9 ~/ P0 N* x" w13
    8 Z7 Z* u- W0 h) B/ W14) @. e0 A- ^, [, n
    157 N& u) P& _! c
    16
    % z. e/ C% v4 v2 A7 g# y173 l0 d; u' a0 I' ^
    18
    7 M6 }" V( P& }' m$ z8 E$ M( i10.5.2 重采样, U4 ]1 ~  h8 n2 n; Z
      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)* L/ J2 X% ]2 a4 F; F1 j
    常用参数有:, n! `- H: M/ b- M$ Z$ R) C
    : z9 j1 e* K7 h. M
    rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    $ Z5 G) Z! r' u% {5 a# A! Maxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样- D+ ]* D+ ~9 Z+ p
    closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。& X, T8 V) o; y1 }1 N* [: |
    label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。! G! X" t5 `1 a
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
    5 Q& E0 [6 f3 ]8 |$ e) {on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
    0 ]! d* t; v+ Z; A5 g3 A0 w$ W% flevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
    ; U4 o7 C' L5 q/ `+ f( E# W& Sorigin参数有5种取值:
    7 B; d; U0 l4 D+ E1 |( V‘epoch’:从 1970-01-01开始算起
    3 d1 H2 O  D7 L- l" ~( s‘start’:原点是时间序列的第一个值! O0 h* j  U/ N% t
    ‘start_day’:默认值,表示原点是时间序列第一天的午夜。
    * ~3 R0 S6 ?# |$ A  w1 V2 b'end':原点是时间序列的最后一个值(1.3.0版本才有)
    $ {8 t; r& N; i8 B5 y‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)" K5 `: _. @7 }/ C4 d* x# O
    offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。7 m2 g; k7 X' \8 |6 ~# s0 ], k
      closed和计算有关,label和显示有关,closed才有开闭。
    - L) H& D& l3 h% O6 k% `  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
    , k# ?6 x0 M, _5 F: r
    & p+ W5 ]( F* L/ n重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:3 V, s9 V( b- E8 m2 a
    s.resample('10D').mean().head()
    7 N8 q  E3 a/ X5 S9 d% `. EOut[117]: ' S+ f, C5 a" W3 x4 S8 W
    2020-01-01   -2.000000
    7 y0 i- H2 S* X& c9 T2020-01-11   -3.1666678 M4 G% L" L6 Q+ q& d! b! u
    2020-01-21   -3.625000
    ( Y& m6 M# y, ^& z4 n4 }8 i2 ?  @2020-01-31   -4.0000006 q) d8 A9 \  y  \8 }) r. V
    2020-02-10   -0.375000
    6 J) M, C  l; n- MFreq: 10D, dtype: float64
    $ ~: ^: ?1 @0 `% P1
    6 @& x3 ~1 f+ O+ N9 M5 |. n+ l27 i! K4 {2 w7 _8 F
    36 D* ?) @8 q/ Q  B7 N  ?, j
    4) x5 n9 M" ~+ N- _
    5
    1 X7 ?! K, n) v1 ~7 F+ s# c& c" h66 G0 T' ?% u/ }" G7 H" k& L
    73 V" r( W2 a- F
    8
    ) v: i) X6 m5 A' U8 S4 h% j0 Z$ o' K可以通过apply方法自定义处理函数:
    - }, B. O% ]2 k& v" t4 R3 f  ms.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差- d: J/ V& S* o7 I3 ~) D0 O

    3 n* ^9 K2 w5 Y0 b: L1 }, e4 Q5 KOut[118]: / T; Q9 M  v" n
    2020-01-01    3% g6 k4 a* L, j
    2020-01-11    4
    : b& ?+ Z- k1 p+ e; X2020-01-21    4/ W5 \! G& y5 V  v9 V* }; U+ j
    2020-01-31    25 y9 x6 e6 M. i
    2020-02-10    4( Q) D9 v+ Q, q( j4 y! A& X- K
    Freq: 10D, dtype: int32
    . D" ^6 \/ a! i' A1
    " U0 J( |5 u& E. L! W; d2+ j% `; P5 I5 \9 v4 S9 x3 R# d
    3, o) F1 ^0 v0 K2 D0 j6 L: t
    4
    / u# R( j3 R$ t8 Y1 P5
    3 t" }7 ~' U) [3 Q( V6 l% ~6
    3 N6 u% H3 d4 _- s6 F7 C. K7+ K" n: D$ }8 d: K' [# e* \
    8
    # }+ j7 G4 x( E* \7 O4 o: k9
    3 |, d& t$ K* H; U2 ~  在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:- u* D# T, d( i; k6 v2 A
    & Z- h+ W3 o3 p* ]
    idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')1 v% h0 ^, y6 J2 x, q
    data = np.random.randint(-1,2,len(idx)).cumsum()4 {3 q) U0 B" Q: @$ i) a! @
    s = pd.Series(data,index=idx). y' B- q' g, W5 V! P' m4 a$ u
    s.head()
    ( K6 S3 Z& K* ~8 o$ r& z9 v! F6 C$ C& h1 ?0 ~
    Out[122]: & [% ]4 T; D- z- @  R+ y! G$ D2 ?
    2020-01-01 08:26:35   -1! L* ^$ P. |( K) B, i; j
    2020-01-01 08:27:52   -13 c. I& u0 A2 \& b; j3 y% p- H
    2020-01-01 08:29:09   -2' B- k* O. {; i& h2 x
    2020-01-01 08:30:26   -36 y& c: \* g+ f8 D! g3 m; M
    2020-01-01 08:31:43   -47 p+ |6 K- E2 u% V
    Freq: 77S, dtype: int32$ D" v3 z+ j0 }3 ^4 x
    10 q" t! s% V, e$ A
    2
    2 N6 P  }8 }; ?( C& J) t, u3
    8 t& _9 |8 I. H' w) ^0 j. c0 [4
    ( ^& o+ j( v0 X2 g1 P  i' |: s5# I# i  D  I4 e2 E) `" C/ ?+ F
    6/ P) N) _  g: N, \
    7" B" J0 o- B7 g+ `5 ^
    8
    1 D3 f% _- }  W9. J& P% m, u* C8 Z8 [  ^; L! S
    10. Q7 E( J5 M2 z
    11: I* Y5 U+ o  p5 [. s
    124 k3 f, g0 U7 R, x# C* {
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
    + d7 J8 X4 ^- T) g0 [
    6 V3 V  T" I% S% Bs.resample('7min').mean().head()5 ~* r- r" d# q. o. P/ ?
    Out[123]:
    1 k  k0 U7 E$ ]0 L* O2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值4 s. S) y) I; V5 c
    2020-01-01 08:31:00   -2.600000  Q5 v2 f. z! S( _) X
    2020-01-01 08:38:00   -2.166667. |$ ?/ }1 |1 p$ b4 J# X" l
    2020-01-01 08:45:00    0.200000( H( U4 k2 p# t0 o1 j! A7 E
    2020-01-01 08:52:00    2.833333
    . f" x. w9 C' hFreq: 7T, dtype: float645 C  h; f3 Y; c; F0 i% A3 T
    1& ]/ U! K& h2 v  f0 f, A
    2
    * G) e* n; u8 k, }3 q5 t3: _/ m1 b0 ]& W" [8 W
    4  t* K. }! P0 k+ Z: F9 P
    5
    2 ]/ Z$ c% u6 P% q. V) ^/ S6 F6
    " n& k0 @! y) E: c7
    % n  I! X0 Q3 X, W- J6 Q8
    4 W  e* @9 z7 t; |* D# A5 ~* j  有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
    ) b4 P- |* I+ k( {5 `# P! m/ i7 {0 b
    * x+ W" Q* W6 c8 ]  Os.resample('7min', origin='start').mean().head()4 H  k3 `6 E& q. n9 I' Z
    Out[124]: ! W8 i* R6 W" N/ f/ f$ I7 p% F7 n
    2020-01-01 08:26:35   -2.333333( q6 ~/ P& ?* G" o/ z
    2020-01-01 08:33:35   -2.400000
    2 |/ M3 k$ R% m$ ^2020-01-01 08:40:35   -1.3333339 w# `& x5 K3 _2 {) C
    2020-01-01 08:47:35    1.200000
    8 `6 V% [9 A2 d5 G& m/ x( W1 o2020-01-01 08:54:35    3.166667
    0 O+ O2 q/ k8 |: G) Q* qFreq: 7T, dtype: float64
    # E2 y$ o: h$ K& c/ w4 t1
    + Z) \( h- u7 E+ s" @2; [& \1 v( O$ F, a5 ~
    38 ~, l- d) r1 E1 w4 E
    4
    6 U! A& {; |: }: X, C. W/ p5# X1 c' E! G1 F9 ~# V  ~
    6# n) M0 i: ?' k# G- ]- F' a& o
    7
    ' H% `/ q) i; N6 t5 B- _89 b4 T  r! |1 t- ]* q0 a
      在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
    $ _3 J- j# d* X0 h2 E$ z2 a% [2 V& a  w' O* ]
    s = pd.Series(np.random.randint(2,size=366),) U, h+ `, l3 Y. G
                  index=pd.date_range('2020-01-01',
    ( L, G$ L; e7 H, y# m9 A                                  '2020-12-31'))" r+ A- X- L# T, k
    : N$ D. M  r3 y% n, w7 ~4 K

    $ T- X6 o: M* y0 R1 q$ q# hs.resample('M').mean().head()
    9 F7 O3 }* J1 n0 Q$ M$ Y: R2 WOut[126]:
    & T; i8 ~- v  b/ q3 h& W( I  l2020-01-31    0.451613' U/ p5 N& |. N- H2 W% C
    2020-02-29    0.448276
    ' o  F- g% @# `& f2020-03-31    0.516129+ ~, f6 h! h6 B' u7 `' l7 a% E
    2020-04-30    0.566667' ^/ b" W; }8 j- n  P: R
    2020-05-31    0.451613
    + Y# K& K  V" ]* OFreq: M, dtype: float647 i+ T7 \! r& H% R1 m

    6 a$ t- J2 E1 C* Cs.resample('MS').mean().head() # 结果一样,但索引是跟正常一样) b+ A3 l. E9 s3 M. P
    Out[127]: " O) ]6 _7 r5 C  D( z, \0 ^
    2020-01-01    0.451613
    $ j  Q6 u3 o! @1 C2020-02-01    0.4482760 q9 u5 n. E- Q& J3 i$ I
    2020-03-01    0.516129! U- \3 D' N% ^$ x, Z3 U
    2020-04-01    0.566667/ O$ r! i$ q  N& x
    2020-05-01    0.451613: p# W$ E5 i9 z" i6 C
    Freq: MS, dtype: float645 o$ S6 T& u4 P. M; a3 g5 L
    3 l( f9 p/ }5 {& {, d' y' t5 G6 w' T
    16 P3 l& [6 D" p
    2
    : I& a% O6 W. J2 D, T3
    3 S9 y/ Z: G, V+ P, S! V* n41 @4 p: u& m! @3 x% z
    5( Q' Q. q/ v5 O# C8 `" w$ X% g
    6
    ' z; F- `7 {) ^9 ?6 T" l7' s6 W  F0 g( ^. \
    82 X; _+ ^4 e4 @4 C( x5 k) v+ s8 L, s
    9" ^3 I) Z7 X% z
    10
    & _# d' e' ^# ]9 l! u* Q" U11
    ; h2 e- J/ B  ^0 X7 I126 d! Q/ t+ @. K  ?+ s
    13
    / e( f8 y9 M: _1 A% d& F7 ]3 t14
    , V4 _8 P! D. I: k5 S9 E158 `" k* v2 Z6 y# l
    16) Z0 ]4 m1 d5 f3 U
    17
    ) N# ?% U; Z) f. J18( G: a0 ~  p8 P. z
    19
    % s! ~; O/ b0 L7 m' k2 t20
    " d' ?: {$ n. {0 T- @1 R) `21
    $ f( h% s+ F! S( P1 K221 E2 h% [; m. |/ {/ v
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:( e' C# A% O, ^
    d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    % `6 ?. f0 ~; K- r$ X  o     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
      ?; T6 Q; g) h, s% l# z8 J; ~& Edf = pd.DataFrame(d); R& m; ]6 m, u# m, K8 [3 ^) D% |
    df['week_starting'] = pd.date_range('01/01/2018',9 N& X: [9 d% r) o1 a) x
                                        periods=8,/ w! n+ h) H0 B' x2 S
                                        freq='W')4 Q8 c. p4 p5 F7 u) ^1 V
    df& o  v  H! |) V1 v
       price  volume week_starting2 k$ _9 F0 n% Y. f0 s% |
    0     10      50    2018-01-07' m" Y! B  `/ O" K
    1     11      60    2018-01-14
    3 W1 x* G/ _) {6 k6 I) _* m2      9      40    2018-01-21
    3 s4 h" s1 Y) a& h/ G3     13     100    2018-01-280 X; D0 R! t2 W2 Z/ q# c' W4 V! l
    4     14      50    2018-02-04) H5 a6 i* w: j; r% M% P& W
    5     18     100    2018-02-11
    - `$ Q- n) u* W6     17      40    2018-02-18
    0 b$ S: o/ S' V0 Y7     19      50    2018-02-25, G  S7 [) f# r' g# o* n; {
    df.resample('M', on='week_starting').mean()7 `: Z( u, g' z
                   price  volume
    6 W) w7 z) i7 @) `" {3 ~3 N( t6 Y4 {week_starting$ \# H8 w* a* I
    2018-01-31     10.75    62.5
    ( ^4 Y2 l: T7 t5 e+ n4 P+ C2018-02-28     17.00    60.0
    8 t+ V4 L7 Z' |2 G' w$ H
    # Y. y0 L# |- B6 @1
    : C9 U) m" L( `" ?2
    / I+ Z9 q  t% T3$ f  L/ ~" c) w: s
    4- k, y9 V9 w' _) f5 ]& U: ]" c
    59 e+ n& f1 Y2 p5 I) d9 d
    69 [( S3 P. n) ?. p
    7! B8 c8 {1 g$ m3 A
    8
    + j! Y" {" }# B* P4 T96 i& x6 }5 v3 [% g" K
    10
    % Y: Z" G1 S8 N11
    3 E! \$ ?+ `1 `1 z12
    & e  x/ ^1 j9 Q, l13
      [) {6 [4 c6 Y* f& _, F* {14# ?7 h8 _; }% L! O" D# f
    15& A  b1 E9 l1 T, i, |0 r0 b
    162 n: N7 L. U$ z
    17
    ( p2 }! Q. c6 `. B5 @2 i9 E; J18& I) |$ E0 Y# M; U1 G, ~5 x
    19
    9 Y/ g( q1 t! o3 U20
    8 w' `0 l1 L5 a  m21
    3 Y0 `9 [* b. w5 s6 Q8 s对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
    ; \* }0 C  k! b5 [, @1 l- t, Zdays = pd.date_range('1/1/2000', periods=4, freq='D'). m& u- T  S/ P/ O# A
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
      t& i$ i9 J0 T, r6 Y& z8 j9 b$ C. `# |      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    - o7 Y  d; {) l+ ?' o5 v, tdf2 = pd.DataFrame(
    $ J  O% u- w# q) W, q    d2,/ I: d! v5 _7 E& V) l: T, l
        index=pd.MultiIndex.from_product(0 N6 ?, @1 {% I! y" O& J
            [days, ['morning', 'afternoon']]: V" x% o6 H) U- N
        )* t( x/ _: [0 q
    )
    ' e' S. ^% z# L) d  g" udf2
    , N" ?, Y/ H1 N& _8 Q3 ~                      price  volume8 u* k8 D5 I8 c
    2000-01-01 morning       10      50' K7 q( g8 ~. \9 B$ o' C
               afternoon     11      60
    % v$ _$ g- `5 Y! |- |2000-01-02 morning        9      406 f3 u0 |, j3 V0 K$ Z/ v& A
               afternoon     13     100# d; r+ c$ X2 s$ Q! n4 h
    2000-01-03 morning       14      50. b. U( m* `# T- S) T
               afternoon     18     100
    2 C, X, z6 M( d7 O+ n; @0 d( R2000-01-04 morning       17      405 F# Q( W7 k! ~
               afternoon     19      50
    3 T% Q, {' \5 }df2.resample('D', level=0).sum(). a; i; E! ~/ e: e# b
                price  volume( t  n( M+ m; ]. P& W2 f
    2000-01-01     21     110
    ; f  k+ D0 ~3 j. [3 s4 Z4 Y- @2000-01-02     22     140
    5 t8 t8 [7 V1 |# ^  A2000-01-03     32     150
    , W7 g4 I5 I  N+ U& z+ V2000-01-04     36      90& s; s6 _  p+ r

    1 ~: w0 m  G7 I$ Q( ?1
    $ \4 |3 E& L6 f2
    * ^( u0 X$ D- h  N( i1 a1 q36 I/ g! r- d. A( q
    4
    + b" O6 i- x: R) k# V5
    1 B) G' W5 ]6 _6 e( a6" R3 j2 o6 d- s- s4 i- u- f8 F
    7& t% U* w" J9 O$ Y0 v
    86 _0 o, S( V% U( Z( V
    9* Q1 N" D# _4 w/ b/ q, e5 p
    100 @9 Y; n+ g. q6 N  s) o
    11
    9 H& O5 M  k) c6 E! [% M! b12  {& K0 Z+ i2 u
    13
    , ?2 T$ x3 h6 Q: \0 `14
    ' e, z4 `% U" q3 m6 v# y15
    # d0 g% p  m% I0 w' R16
    ) Q; B3 R5 z: T: d) Z) e2 ?17/ c4 M: i/ A. I$ @0 p
    18+ m! Q+ e7 z  Y2 v: [+ A" O
    196 }# l! y4 [. g& s. a
    20
    # c3 t# Q1 a2 ^. h0 K3 ~/ l21
    ( X( g; y$ m- d  G% U' M22+ r9 A5 j/ K) U: ~/ ]  U! G8 _
    23
    , }+ o- g6 I% T  |5 U# B- W/ O242 y. T5 p! r" e4 M
    25% p# _# }0 _' J) P  d- ?1 H
    根据固定时间戳调整 bin 的开始:0 A% }: X, e- j1 l# P* q
    start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'& P0 _/ y/ D7 t8 Z
    rng = pd.date_range(start, end, freq='7min')
    7 x6 t. w- m( n  N0 U9 \; cts = pd.Series(np.arange(len(rng)) * 3, index=rng)
    4 Y% Q, `- [5 x0 s2 Tts' G4 k8 p( d9 n6 d0 D9 c7 E
    2000-10-01 23:30:00     0
    & v* A2 o: u% s6 M8 q! ]2 I3 R2000-10-01 23:37:00     3
    3 L5 ~$ ?7 M# v2000-10-01 23:44:00     6
    5 U4 i8 B8 e' K: l! u2000-10-01 23:51:00     9
    : a$ R+ [! J6 z; U2 ]6 r2000-10-01 23:58:00    12& U2 O7 i; P9 Z& x' y$ I
    2000-10-02 00:05:00    15
    : I3 u+ j6 z" F4 D  H, t) c+ O# m2000-10-02 00:12:00    18+ {- B1 a; G; a* L# q8 v
    2000-10-02 00:19:00    21* x3 O7 i- s& }  d' [
    2000-10-02 00:26:00    240 b& ?& C! J2 ?# e2 @% K: S
    Freq: 7T, dtype: int64. k% u# i, e% D6 X9 Y
    ; o  K+ q; o7 @& }; x1 V
    ts.resample('17min').sum(), o( }- H* H+ q
    2000-10-01 23:14:00     0
    # B+ R4 ~, _! B- r3 A2000-10-01 23:31:00     9
    / l6 J8 r- W" U5 e5 z! J+ Q5 h; p2000-10-01 23:48:00    21
    1 l. o8 H( `! w, ~6 Q& m2000-10-02 00:05:00    54+ b1 L9 w* Q8 j( Y5 X- k! Y
    2000-10-02 00:22:00    24, J* [$ Z, u# H0 M3 t
    Freq: 17T, dtype: int64
    6 T' {6 F- x1 r/ Z) p' J  }: m* K4 I1 q+ k, e
    ts.resample('17min', origin='epoch').sum()
      r( U& l: b: h6 k4 X- v6 z0 N% V2000-10-01 23:18:00     0
    7 P, g- w$ a; F/ [2000-10-01 23:35:00    18
    0 r/ Q4 \, S4 N5 @2000-10-01 23:52:00    27
    : M% N( I. }+ c' U3 p# z2000-10-02 00:09:00    39
    ! [' u2 d! j8 P! U9 `& v& D$ Y2000-10-02 00:26:00    24/ P4 {, |7 c1 V3 X; {1 E
    Freq: 17T, dtype: int64
    : r' p6 \" c* C4 j0 z3 h) Y0 v2 `0 B) l! f" v, F2 z
    ts.resample('17min', origin='2000-01-01').sum()
    1 g" z0 y9 }0 N$ o9 e- E2000-10-01 23:24:00     3% m- B* \  T& ?+ {$ R7 R/ M
    2000-10-01 23:41:00    15
    . J7 h) J  ]; i2000-10-01 23:58:00    452 ]  K2 u* F  i& Z/ z
    2000-10-02 00:15:00    452 q( U8 r& ], r+ I1 J* ?- J0 M5 @
    Freq: 17T, dtype: int64( H0 R  J' [0 A: E! j& f
    8 \# ^# b! U, ^* s0 q- y3 ~
    1  _% a6 \* M, l$ V
    24 A3 W* I6 _& ]/ y0 S5 X& O! @: |
    3$ H5 D/ v1 n+ w. f
    45 f' b2 s$ o+ `" }3 J& @
    5
    % C0 \2 c% f  Q7 y' f: o# s6- D1 H& j* q2 `$ e$ E- i
    7* h# u; _# y2 I( h/ T
    86 Q9 A' W, i& z1 r8 X4 N
    9
    / x3 B1 Y1 O" U4 \8 @& E: m% u) z10
    ( x. Q( T) M5 W, [7 y11' q3 J" P7 z) X' M1 F1 l+ m
    128 M) Y# R) J/ O/ Y# c- {8 I" }
    138 U; @/ h. ]3 j( ^! O: t2 H
    142 R9 E6 \8 ~) r( K" a
    15
    2 r! `) o. n* N% O2 M- [16
    $ B. N5 d5 J& u$ D, |17
    . c) u( t5 w1 R: @2 I18
    / [- e. u" U  P- O! n. P# _19
    : ?2 I0 c6 W* }; a6 B: V20
    ( R* G+ b% O& v1 n6 v% I21
    / @: w/ p1 o) B1 |9 D5 Q+ N222 S  r( B4 w! x- _# Z( ]. T3 W4 [
    235 r" J2 b$ S, x$ B8 H( U
    24; w/ c/ K6 n# {1 y" O, T
    25
    8 ]& b; k2 p* ~0 `- ?, a! l- S26
    " w9 r& Q! s3 E) v27! z! N' w5 K) X8 t4 A0 L7 J
    28
    7 u# l7 n* E  y3 Y" u0 s9 Q29& R& H# a) G0 @4 a% q8 ^
    300 \4 W2 l: u5 B4 i& N1 b# b4 s
    31
    % o- E- S: I& A. W0 X) T* Y2 D32
    ( @. ]" b6 a5 n& b1 D5 k/ k, B33
    1 L& x% I% q4 {8 v* @34
    ' y9 J( l& e8 r6 N$ N0 ?: \354 c" w  S5 X+ I
    36
    4 D4 q# q8 K' T7 K6 {8 Q: W+ g37
      r$ V, V/ W( I7 c如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
    # k3 \3 V$ e! g/ o3 i! d& \ts.resample('17min', origin='start').sum()8 n9 ]# |* g& N* ^5 [
    ts.resample('17min', offset='23h30min').sum()
    " D* {3 ^( T# V; |, Q2000-10-01 23:30:00     9
    - t9 }; H- i! j+ c, \2000-10-01 23:47:00    21
    / ^0 Z0 j( a/ `: f) F2000-10-02 00:04:00    54& b& x0 q( Q: c2 w* H  v; m/ E
    2000-10-02 00:21:00    24
    9 p* i+ I& V5 T2 ^  N- N) d& N! nFreq: 17T, dtype: int64
    * y9 e& R6 Y' e  C% ~: }$ p! i  _1
    ( W9 h( f5 R8 j5 m8 P- q6 x2
    2 d/ P" S4 v' |% @+ W+ E# O3" x! Y+ r. o. i+ ^+ |
    4
    6 V. K; u+ C( \- X5- k# g" y  F' e8 M4 }( r
    6
    * V/ u3 S: Z7 }* c! Q7
    / i2 r! _( o4 _/ i: A4 d10.6 练习
    6 I0 O3 S5 v) k# L+ YEx1:太阳辐射数据集( `% z/ q# Q' q3 N+ B! h; q
    现有一份关于太阳辐射的数据集:6 ^4 j; x* i2 {+ {& T

    ( n3 ^' F% ]5 Z" t: ?df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])- }/ k$ h0 Q9 P8 Z0 U
    df.head(3)
    2 @" N7 X; J$ c' T
    4 C/ N0 k7 {$ TOut[129]: ' O9 p4 J0 P% O) {! ~! H' u  d
                        Data      Time  Radiation  Temperature
    0 w6 ^+ g" s+ ?9 b0  9/29/2016 12:00:00 AM  23:55:26       1.21           488 J# M0 `, j' U  u  W- a. \
    1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    + W: M- _+ c5 g: g3 g2  9/29/2016 12:00:00 AM  23:45:26       1.23           48! S- y6 [" `! z+ X4 r5 V2 p
    1( J5 U4 ^. x3 `2 t( t
    2* n+ L1 q1 J# l8 \+ o. \
    33 W. n* j8 w9 @: l5 _, d* D
    4
    ! w% |) L( @* B' _5
    ( c" {3 e" {. Q, s) g6
    , a& D9 X% J$ [3 ^7
    3 O6 T  ^0 {9 H1 @+ Y! \8
    . Q1 J5 @- }: |! `将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。- [# i$ ]8 h+ f* ~* ~' Z
    每条记录时间的间隔显然并不一致,请解决如下问题:
    8 K: J4 f0 K4 B2 z) J0 v找出间隔时间的前三个最大值所对应的三组时间戳。
    2 P% ]' h4 G& e( H! p是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。3 g: d! S' F, ]! q! u7 r9 D) ^
    求如下指标对应的Series:
    9 b0 k9 Q+ c5 e- G  i6 ^$ ^* S温度与辐射量的6小时滑动相关系数
    & t' b7 Y5 Q8 w6 O* s# K以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列+ _: E( O9 C& q! Y+ G( A, V, M
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)# Y1 ?! U9 U0 h. O- x; k
    import numpy as np* j2 U4 ~. ^7 Z3 j8 j2 {* `3 P
    import pandas as pd
    - p0 b$ z4 i) M3 }1
    , Z+ c/ e' n1 d% }( e: w% T( h2
    0 p' W6 _( y8 P- Q将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。5 ?0 b" u' V1 N! P$ r1 [9 ^
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
    6 [; t! ?8 F; x, |, |. ptimes=pd.to_timedelta(df.Time)
    9 h; b4 F: G0 q- u2 U" }+ l8 b1 bdf.Data=data+times5 J, h3 J4 P! N$ r
    del df['Time']
    - E! S" R9 l# n9 Wdf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
    , U. Q& ^5 D" m1 i; O# ^1 pdf3 G- A/ r# p$ V4 o
                                            Radiation        Temperature, s& @$ @# T; }+ D% X8 U
    Data               
    - W9 ^4 o3 D" A1 z: h2016-09-01 00:00:08                2.58                51
    8 L  {' M5 t5 X  l9 I' s6 {8 E2016-09-01 00:05:10                2.83                51
    6 m: `2 h" m+ ^2 G8 a9 a2016-09-01 00:20:06                2.16                51
    + ~! q& r# z% M- E1 l  w2016-09-01 00:25:05                2.21                51  P, n$ x7 r/ V8 O5 x
    2016-09-01 00:30:09                2.25                51
    $ }- s, S, a9 y# @4 x...        ...        ...
    1 e0 F" G, U% ^! U4 G" t2016-12-31 23:35:02                1.22                41
    ; d6 J3 l$ e% W2016-12-31 23:40:01                1.21                415 R8 c: S- L# D) s2 Q( z4 p- X( r
    2016-12-31 23:45:04                1.21                42
    % V. B8 W& P; z& l  @! z2016-12-31 23:50:03                1.19                41
    * ]( ~% H: p2 ]8 ^8 e2016-12-31 23:55:01                1.21                41$ X/ r) Z- O. ]) W2 W: Z2 k( s
    , K9 Q' N9 {. R' S9 s
    1% }" v8 L3 t: k! z
    24 B9 m$ H% o$ S, ^1 \
    3- }. E4 x3 y" A& w/ E( P$ O& m
    41 h+ I8 e0 B; P
    5
    ( C9 h. V( f# Y2 M4 A- N6
    : r. [) G: h- ?7
    % G- S+ [. }3 P4 k7 k9 I3 C" p8, W  W4 H: a! t0 M7 `1 W" M
    9
    ; n3 r' w1 r7 H3 |5 I107 a( V( q7 V2 t8 s+ A3 C8 o$ t
    111 s  F1 m0 \/ K6 L; |2 C
    12# ^9 w* z5 L" F4 q+ A
    13
    , j' D0 n/ t: U/ t14* Y- x4 y( v& W( E# P. i
    15" w# S0 j/ q; D  b; d- k# K( ]
    16$ C4 T/ p' p7 M
    17
    % M7 U3 H; i% B  R* R, I. y18
    ; ?$ {) v( h5 p8 z, x& m19
    " X7 y1 g5 L9 d每条记录时间的间隔显然并不一致,请解决如下问题:
    + \) @3 c! p' ]5 V9 f. y+ W5 N找出间隔时间的前三个最大值所对应的三组时间戳。0 J, c  `) I+ v) y
    # 第一次做错了,不是找三组时间戳) e4 F) X: K8 X& O& Y
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    - I: G, U& ]6 i, A( ^df.reset_index().Data[idxmax3,idxmax3-1]
    0 X, z  d& G) I$ ~) V- P1 c+ m+ l8 i
    25923   2016-12-08 11:10:42" U5 Q3 G1 l  F( y* h- y9 P8 N0 t) B1 a
    24522   2016-12-01 00:00:02
    8 S: x8 F7 M9 o  z' L7417    2016-10-01 00:00:19
    ! ~3 J5 L& ~6 r9 RName: Data, dtype: datetime64[ns]
    ; M9 ]  E7 ~) B$ `$ |- m4 ~1
    ( B- O9 v- |* q2
    0 {0 H4 m+ b3 n/ a3, q6 h8 }4 g8 D8 r
    47 Y( H' C& S* w
    5
    . ?; d8 l+ A! M7 y1 ~6
    & Z2 O# R: n" M3 L75 O8 d/ i% i) ]1 i( z0 x6 r
    8
    9 G6 |, Z$ C! l8 O3 V' e* Q$ }idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    - u, \: W! I  Slist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
    ! r2 ~0 ?' b' Z5 O
    4 P6 o9 V' X1 o" @[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),, n4 }) j4 Q" P. k( g- {4 q7 Z# X
    (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),+ K  q# u" U7 ~  O7 q* ?0 v  @
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]" W$ ~) P" _1 _1 a( c* N# r+ }
    1
    + h2 }5 b4 t  L  }) U+ b3 D2" N% x% a: ?% f( v/ m; u
    3
    4 R- C7 z6 x( w4+ b7 N- t3 M* w6 G8 Y3 C6 J
    5
    5 X+ a6 g2 X6 X0 U6
    : p* H. Q$ T+ B9 L/ @1 ]1 O( e' Z参考答案:8 E6 a) a$ a" N% L7 m; a
    5 Y& F! B# a0 N
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()+ {: _2 T# D. y& }4 O, ]# z) y
    max_3 = s.nlargest(3).index
    # Y0 W3 }- x  c/ _3 O% S4 Tdf.index[max_3.union(max_3-1)]7 n- s! O( z! \7 O
    ; `- A/ u. n& b# a
    Out[215]: 2 p( P: ]- p4 _: ]4 r; T# Z
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
    : E6 x# z0 f/ n4 O, D) W               '2016-11-29 19:05:02', '2016-12-01 00:00:02',% ?0 K% c# {* a
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],9 E$ k+ D; z1 f5 K
                  dtype='datetime64[ns]', name='Datetime', freq=None)+ u( f( Z1 R" C( }6 S7 I! c
    1  d, i+ S2 a- W3 }0 B/ @
    2' ?, G% v. `# H, R3 ~$ m; x" \+ K
    3" p+ o- z8 I$ ^4 ?4 u
    4
    ) z4 G" b# ~/ i  O0 {! I" f5
    5 F7 O0 Q6 C6 e3 n66 ~% P3 o4 |5 n5 x& C8 }  r5 }
    7. f2 `/ A$ k, R& u( d6 c
    86 p- w' l6 G. E$ `0 u0 |
    9
    - |8 ~+ D4 Y+ I: L& A9 G* P* p是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。  k& N" t! y( B# y3 c' W) T
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间$ y6 o1 O- k  g9 f+ v
    s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)9 F7 I" N4 B* p. F
    s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
    + j  L1 M8 Y) ~3 G; C( e( M
    . s; |" {' G8 `, w(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
    * g/ v6 K6 O" ~+ v1
    & W) x) ?1 g! |7 R23 e! d& `' t* p9 B8 }- L7 V
    3* y3 M& l0 Z* D3 ?1 i! Y. e
    4' e  Y5 j- l9 N& ^) g
    5
    1 v; ]& \' a" p; ~7 @& i%pylab inline4 `6 m5 m$ h& u& r5 @/ ?: U
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    - p* n: ?' {# k: |  eplt.xlabel(' Timedelta')% a& X+ w. O/ r4 z8 \  r, V" _
    plt.title(" Timedelta of solar")
    ( m6 Y( d1 ~" Q1
    ) r/ a( j  g) ?6 \! D2
    + O2 \- A$ r! s) M( V) S/ A% _3# r9 d2 X( v0 ?2 t1 W; V$ ?/ t, m+ D
    4% c- K' f* g) h6 C4 F, u; `( ?/ D

    5 P3 u2 u) V6 }" U# }
    + \4 E; r- C0 N  l6 h8 X' F求如下指标对应的Series:
    7 x# p  P% A' j温度与辐射量的6小时滑动相关系数; P3 l4 x  j+ T
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列' \  ?; K. t$ \4 f
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)1 t) N7 F7 b6 j5 Z' x
    df.Radiation.rolling('6H').corr(df.Temperature).tail()
    # G' q$ `# ^# W7 ^; A# y; M3 V! J! }0 ^! \- l( d; F& a# y
    Data
    ! D; X2 U% d5 Y/ E; N3 m4 q# N8 i: [2016-12-31 23:35:02    0.416187
    : o) R# ^7 J& j2016-12-31 23:40:01    0.4165655 x6 S3 i0 `5 Z, x
    2016-12-31 23:45:04    0.328574: O9 E, g+ b6 ?9 x) C6 u
    2016-12-31 23:50:03    0.261883
    $ {2 ?- t( s' m2016-12-31 23:55:01    0.2624069 b3 h/ Q) X& O' h: q: l2 S1 [
    dtype: float643 q8 F3 L: z# A" g+ q" u, w
    1# l; b# M1 y: S% p# W. g- e+ r
    2
    ( y: E- |/ ~2 a& l  z3
    , H. j, Y0 Q! X) a0 g9 p. h4, r; p4 E' K' V5 b
    5, _5 R1 y* S7 k( T* w
    6$ d3 y6 t4 u& H: i. ~5 `% m
    7- i# T0 W9 p2 Z1 \- X
    85 D# K" j5 Y& @9 K& v
    9
    # l9 E, a/ {# _) [9 x, t+ Rdf['Temperature'].resample('6H',offset='3H').mean().head()
    " b8 R( A5 m2 B; w5 Q5 Q) X0 y; C8 ^* R( P, s7 j
    Data* T& z6 |) C$ f  F; E( t
    2016-08-31 21:00:00    51.218750
    ( P! C" x1 T/ e3 B2016-09-01 03:00:00    50.033333& d' z9 F* s  H& \9 t: z
    2016-09-01 09:00:00    59.379310' J1 ~" ~9 F) g8 D) [
    2016-09-01 15:00:00    57.984375
    ) i- ]: k% a/ q( i2016-09-01 21:00:00    51.393939
    2 g- m4 z8 c& Q6 o, a% z1 vFreq: 6H, Name: Temperature, dtype: float64& E9 e& ^6 X( v* T' K* S
    1; t: M3 a" R1 R( f6 Y
    2/ }* l1 t# D9 s
    3
    8 n# \2 o0 l2 r; ?( n4; c# ?2 {" Z) p, [2 a" `" _1 }" u; o
    5) l3 T8 V7 H9 [5 N6 ^, X
    6
    ! D! s6 o+ A2 {) z3 J" ?! W# a7
    5 k+ [9 S3 t& X0 A8* b" M0 o8 T) ]
    9) R; d& Y3 Q6 _, Y* b" w
    最后一题参考答案:
    1 S( u' D  W2 u4 K8 @5 W) X! N$ ~  N+ J2 v8 D' }3 @0 I
    # 非常慢( Y; |6 G+ t( R0 s/ \
    my_dt = df.index.shift(freq='-6H')
    + \" S% _; ?% Z1 `& g: Sint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]5 F8 o/ z" d4 C% J+ [; B9 v) u& N; F1 a
    int_loc = np.array(int_loc).reshape(-1)
    : b+ e" @! J/ m- Q- R7 Bres = df.Radiation.iloc[int_loc]
    3 p& y! B) l$ R# s7 F% O+ ores.index = df.index
    8 i0 Q# `. K: n# a0 C' D, S" b7 Rres.tail(3)
    1 R4 k5 [2 L- s7 U7 b1
    $ c6 T" {& r9 _& h% x4 N! G22 ]1 ]; @3 k& U8 i0 N
    31 p# Q6 U: L$ o" V
    47 Z* D/ e& z1 J3 n/ o" ~4 S2 q; D& n
    5
    # P& v* Q' \6 E7 \1 w/ t4 ]5 |6
    0 `+ B6 K9 o6 l3 m7 S7
    3 {9 y; u* w  y; k# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级  U, U( E  L. h
    target = pd.DataFrame(# W9 ^$ p/ |7 g0 \2 N
        {: e0 j( M9 G/ I" _7 c3 [2 N
            "Time": df.index.shift(freq='-6H'),
    , u9 @4 y; l  ]: u, R$ V        "Datetime": df.index," g  [0 e2 I5 ?2 K1 N$ [$ m  U
        }
    - @' I" J- z% e+ y/ `8 y)% t- e3 K' H' u

    8 [% ]# ]8 Y% Dres = pd.merge_asof($ k$ y4 Q% B8 d
        target,
    0 O7 ^& b1 g! u1 O. S    df.reset_index().rename(columns={"Datetime": "Time"}),
    * @5 l( [; h; _& J+ t    left_on="Time",
    ' b! T( s" {9 r7 W& |4 R' }    right_on="Time",
    1 q' }/ w- \9 e1 d  N4 t0 d    direction="nearest"
    % `1 {5 e! q9 y! U+ _1 b).set_index("Datetime").Radiation2 _3 {; r! g2 J. w

    1 x! ], J' }% v; \res.tail(3)
    % w0 Q" p1 Z2 K. vOut[224]: 2 z6 I4 x/ v3 b- d5 q! D
    Datetime
    : B+ V" ~- |$ r! B2016-12-31 23:45:04    9.332 j: _/ V! g% b6 v, Q* O
    2016-12-31 23:50:03    8.49
    4 i7 D8 V8 Q3 N! ~' ?5 A- P2016-12-31 23:55:01    5.847 I. l, w, E" m: `8 K# W2 J8 t
    Name: Radiation, dtype: float64, n  l, [0 w6 l4 V$ U4 L/ q
    1 Y0 s" V8 T# ]  A" ]
    13 ^+ P) q) D2 l7 g7 @3 J9 b
    2
    # w  q6 _, }5 a, C) W; i3% o, H, J& ?0 D* Z2 T) h! U
    4
    1 Q# D/ s/ H( q# O: n& e5
    1 d% s! [3 I7 [4 Y' I! d7 [4 @6
    6 v+ G1 y0 f7 K' a7! z3 J! U9 [1 \
    8+ l' r5 b! c: |$ ?# T$ R* b# N
    9
    ) P5 b# ^* a4 p( s10
    % l, P. u+ R( n/ x" d* ^11
    8 b; b$ Z7 J. m126 d) ~* ?# K$ s  l2 _1 Y
    13/ D9 K! M& G& @% }$ w0 a
    14
    . W" g; B% q% R' A7 M  \& V15
    ! G2 u0 R% y4 A) e% P16
    8 d6 N2 {, ?; ]$ k1 e; R% N0 t17
    ) e4 p+ z3 a9 m6 p7 Y18
    ( A  \/ }) R4 X8 V19( C% ~4 V1 E2 \; ^/ I
    203 [% B! Z/ @* R. J/ g- h) u
    211 Q2 Y/ d( q, U; l" N9 ~
    22) @. B1 v2 z5 r
    23
    7 ?% F4 h, l# F+ m1 qEx2:水果销量数据集
    - g0 @. K' t: L* r: ^3 S现有一份2019年每日水果销量记录表:! r" i- T, V) w( s7 y5 {6 k" o4 R
    " b& {; v" X2 K2 D. G6 H& T* V
    df = pd.read_csv('../data/fruit.csv')
    , R0 t# V2 c3 m. Q9 E8 Xdf.head(3)$ X  h" W4 D3 X

    & c2 g! A8 X( m) t, ^7 YOut[131]:
    5 L, }+ {' b3 g         Date  Fruit  Sale3 i# s8 F1 [9 }5 K) [5 A' v
    0  2019-04-18  Peach    15
    6 \6 z$ W6 A+ |! [3 r* H3 Y1  2019-12-29  Peach    15
    # a: O: C1 `; H6 w2  2019-06-05  Peach    19
    $ E7 q; J7 W1 T6 b1$ Z- q* ]1 `& B0 m2 ?, i) {+ M7 T
    2. X: K, n* a/ D& O6 d
    3
    ) }. G1 G7 o+ O7 L  V4
    7 q2 Z) [2 s- o2 e! \, ^5
    3 N6 w) G8 ]( \& u) b6
    2 g+ k/ L& W  V, Z: ^$ X0 Z1 G1 s0 j70 F0 O2 B, a9 e& M7 _" {
    8
    * C" `7 h  ^1 v统计如下指标:+ R7 g4 Q; B0 z
    每月上半月(15号及之前)与下半月葡萄销量的比值
    0 O+ m/ r; ^7 G$ f. z+ n+ x每月最后一天的生梨销量总和
    5 W) o% d" J( s' D每月最后一天工作日的生梨销量总和
    1 e9 r# Q1 B1 F每月最后五天的苹果销量均值
    % N) U  X' m- t按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。& F$ b  g: C3 ?: \7 j1 P
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    1 V& i# ^& Y9 {5 q: Simport numpy as np
    1 a8 }/ P0 y. rimport pandas as pd
      Q" V( o( U5 t; U; N1
      \8 G; P1 w$ o0 i) x' [0 ^23 [2 Z; l0 M$ F! ]9 f0 ?1 V
    统计如下指标:
    , [3 t. k7 I6 o- G3 E6 |每月上半月(15号及之前)与下半月葡萄销量的比值) ?, l" O% v4 [1 P0 R
    每月最后一天的生梨销量总和
    . p$ f& [5 d9 y3 g每月最后一天工作日的生梨销量总和/ e  d$ E' z" h( R; y; p$ U
    每月最后五天的苹果销量均值
    : i/ G2 V. I/ ]7 h& z7 l9 G# 每月上半月(15号及之前)与下半月葡萄销量的比值% f  `8 U% i6 T+ B  `1 G
    df.Date=pd.to_datetime(df.Date)! `; G" u# ~8 {# Z4 l( c& R
    sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    0 {4 m  t  z7 \+ \! ~0 Qsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    6 ~. {& u9 I4 C4 [( J2 ysale=pd.DataFrame(sale)
    ( l* B( b7 z- b2 F- Z' e5 Usale=sale.unstack(1).rename_axis(index={'Date':'Month'},
    , G) \! X  }: E; d$ c                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名- t3 T' y' r9 `9 A! l
    sale.head() # 每个月上下半月的销量
    ' v4 O; x' Y# s8 k  z5 C, E0 x1 r* `% t) v5 J( P, E  O- H8 X
      Month        15Days        Sale
    8 c! E# e) w7 M4 a( c6 l0        1        False        10503: B5 [, K6 ~' r+ `5 l
    1        1        True        123412 j; V7 r8 I( Z1 f
    2        2        False        10001
    ! M. @! |/ S4 ~  S+ V9 ]% M9 y3        2        True        10106
    7 N. h- C7 ]' C) T! q, r( {. O4        3        False        12814
    & c: t" L! l' i7 M5 m8 m
    ' |+ i* G7 {0 i: w# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
    3 ?* U. n' u. H! esale.groupby(sale['Month'])['Sale'].agg(
      }) K% w0 U1 C, f! E+ B: p6 M                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    # {( K: @  r# p2 r& e1 V! t2 Y0 V0 H# t5 q3 R2 @% U& e, D. B
    Month
    & B. i- J  Y8 S  E1     1.174998$ k% f# C# J7 U" @, T& }1 ?
    2     1.010499
    $ B5 Q3 o! u& ]# Y* Y5 r3     0.776338
    + z  u8 l1 s4 @) P6 k' C( U4     1.026345
    - R; w6 y7 t: Q5     0.9005344 O8 j. K2 K( v2 {: o
    6     0.980136
    ' J6 T1 \1 Y2 m) E- O( W7     1.350960
    4 x* F) h5 ^5 Z. ~! D8     1.091584* i+ ?' I; E  w" ?0 c2 I: ^! S
    9     1.116508
    2 X1 o2 u. D1 |+ l* a10    1.020784
    - P8 |' G* G3 j& Q, v7 g11    1.2759116 D3 [& g" y8 u3 ]  l1 Z/ Y
    12    0.989662
    ( s0 p% O, |3 w( H/ v# q, e8 SName: Sale, dtype: float64" T! }) c- A* I
    . f: J) {4 O. E
    14 G! J2 M$ P8 b: H& l& _+ f
    2$ a# }( m8 C3 [8 f' H) r3 q0 K
    36 s& O( J" G) `+ m/ T# Y( h' c. o
    4$ o9 p7 Y- E" G5 V4 @; W$ X) ?. @3 ]8 y
    5+ Z% K8 t# u0 U! r+ K( J
    6" ^, m4 R) h- i
    7
    ( T; U0 K) a* }4 g, n2 W0 T86 U, K8 l% V9 ~4 T
    95 i" G9 Y% h& i+ p$ \# Z
    10- E$ g* u( q% l; Z+ m' e
    11
    + s/ ?0 M% G  d' y% {% q3 ]/ T. W) Q( [12% w% W- r: l8 f8 @0 W
    13, X9 B' J7 r9 n) i/ c1 x  ?
    14
    . }  @4 [! ^/ D( L" M& j15$ ^+ |/ X, ?1 E9 ]7 A$ J+ K' m
    161 V  n& ^0 i0 S( B& a, `" `
    17
    % c" G& U$ X) C, B/ z4 ]3 E18
    * |7 b& f3 l, [7 E19
    0 t& q( G/ N! C: n& i" n9 o, x  ?20$ S/ Y2 }" r$ i4 f+ T( n
    21
    5 A9 J; I4 G5 s* B- }6 H22
    - _5 M; U1 x; j23
    $ }2 A6 R/ @! u244 d3 V/ n% e# e! m' P( z) |3 x3 B
    25
    4 {/ J% n( x: f5 K8 [26
    8 g9 k+ b+ h* k7 F/ o271 }; p+ p- A# P4 {; d6 u/ _
    282 Y# X* |% f2 g+ q9 o* ~/ ~/ n
    29
    0 [9 m/ l9 p. m' B30* s9 k& Z/ h: h/ G8 n
    315 g! ]! o& R, `4 P) S) ]4 [! k+ H
    32
    ( g7 Q$ s% p* F8 H* J% I3 y33
    " l% ]! o  Y" ^5 ?  B( e34
    - |+ V+ o0 G# a& m# 每月最后一天的生梨销量总和1 v4 `6 b5 ]0 v1 W
    df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    # X  U* E% q( f! Q) b9 q# A; C
    7 F( R, I4 E) L5 o2 hDate
      }6 o/ D. j1 R: C/ G5 F2019-01-31    847+ S" {7 R1 F! S
    2019-02-28    774
      g3 h+ p, m: b$ X8 F2019-03-31    761. x$ x) l6 _6 {0 m
    2019-04-30    648+ a  N1 \6 I" G
    2019-05-31    616
    . S! y& G& C2 x' h& g  L1: W1 t/ }& F6 h- O/ e
    21 |6 e4 d% }% h+ ^8 V; Z0 u+ M
    3
    % G9 Y( M& M" |) r0 a48 |- X) F' V6 Z: r  c4 u
    5
    ! Q2 q% D- @5 c6
    " b' d* M( f; g' D' P/ Z- d$ p" K4 N7
    : o9 L" U6 T( H1 |; L8 T8. p$ Y' f- q( x8 S
    9; M( S7 ^% [3 p3 g+ B' f
    # 每月最后一天工作日的生梨销量总和
    + {/ x+ G6 t# n+ J3 k4 g5 sls=df.Date+pd.offsets.BMonthEnd()
    " |2 r! _  i0 Y7 K& Umy_filter=pd.to_datetime(ls.unique())
    0 n0 K6 f2 I4 @5 r' A) J. }: {4 t& s5 Edf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    1 o0 Y( n, ^, o: r# v* `5 `  b5 F2 H% j, I8 C0 z9 s! @' v: z
    Date% Z- f% E0 K( H0 e2 I
    2019-01-31     847
    0 y! ^3 K" |- L6 U6 p2019-02-28     774
    & X$ S! [: a& Q! q) C2019-03-29     510% E. f5 G2 @" x
    2019-04-30     648
    / u6 E% p9 y4 s0 S/ C. O* b% ^2019-05-31     616" q: e% J" M/ l- _
    16 R, r  @4 t8 G! I8 P
    2! z- L/ z1 \; M
    3$ `& u/ u* H9 P: G8 T0 \* p
    46 T2 x  t  L. Q1 G& z9 b
    55 `8 ], s, ^/ m5 X# W5 O: Z
    6" g9 K9 c( @3 c) D7 k4 a7 e* y
    7* P% v6 t# z5 ~" g; v, U, Z+ d, k
    8
    + R2 f1 m3 d& b' T: G  a5 U3 C: j9& s. @. h3 B- r: U1 d6 f
    10# E9 x# Q( Q0 i: m- m
    112 a, u$ n' i: t
    # 每月最后五天的苹果销量均值1 N7 D+ q1 e$ i4 r$ p. [* l7 D. m
    start, end = '2019-01-01', '2019-12-31'. a, V6 i1 a4 T
    end = pd.date_range(start, end, freq='M'): B$ O7 s- B# b! Q+ ]4 I
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
    . n- k; r, Z0 U, U. l4 {
    1 y* ]5 R! N! e5 h0 X- n! Etd= pd.Series(pd.timedelta_range(start='0 days', periods=5),)3 y7 T, _4 d& |; L7 I% G
    td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
    5 c! u2 ?+ t  m7 yend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    : L* }' J; {0 V4 @, l, m* p0 t# h0 ^0 c6 J! X% ?6 q8 o) N
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量& X; U$ g! [! N, k6 l; y9 t6 S" P
    apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()  J$ c' n3 i& x6 o- n0 j8 M

    ' M0 ~# f" x" E9 y0 @Date: P% r/ `4 A! J3 ?( j! p5 c
    1     65.313725' h* n1 W$ @! g, y% i2 l
    2     54.061538
    * S, A3 U4 }; l1 m2 v5 T6 h0 s3     59.3255813 h) q+ W2 F  T! R5 O' ^" N$ J
    4     65.795455
    * v$ R: ~, }9 \* N1 V+ m! M5     57.465116
    5 ~. d* D. t- B" C1 H: q
    # I9 Y: J5 b, l: b1# \# ^6 |; M" M+ F; y
    2
    ; D( [0 C8 U4 w( \+ j% Z, N  b3
    - R1 ^( A# c; R7 Q4
    . J0 k& V4 m# E0 r3 L5: |9 e' R/ ~2 L4 ?: D9 {
    6
    ( M& o! e# Q; P2 A0 P6 K7
    3 ]6 @: k: r6 o! E, _8$ d  n, F% a1 K3 e+ G1 @
    9
    3 Y6 z, O. u) s# [; j( ?+ B' A; }* @10
    " q2 _. j, Y! z) ~" {+ M2 W, g11
    # Y  j, e/ b9 L. D0 B. e$ T120 L  s1 q0 B& D4 b3 B
    13/ ]. I+ a, o5 T; _/ c
    14$ b' T5 \( b( S( n4 g
    15" r& E$ F2 J; r5 l( U3 E
    168 ^# g- U/ y. R0 v
    17, z# j2 G. e) j- C1 f; L* ~( f
    185 l. |6 v1 r( y+ P% v
    # 参考答案:
    4 @3 S, J( x5 ~9 l- |target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(3 G1 c' T5 M+ j
                ).dt.month)['Date'].nlargest(5).reset_index(drop=True)+ x8 q+ z9 i# k1 Y( d  K" l& R

    , f6 s# @, m* Z7 Vres = df.set_index('Date').loc[target_dt].reset_index(
    4 j% Y0 C5 Z7 d  w            ).query("Fruit == 'Apple'")0 \4 }% x2 p' Z7 _" h4 K+ u

    / B, k# s# [9 Q+ Qres = res.groupby(res.Date.dt.month)['Sale'].mean(
    ! d" b# H1 T4 Y, \; Q, _2 ~            ).rename_axis('Month')
    % u7 C7 [7 X2 _- g( h' W: L
    : c0 o3 m9 [7 Z$ P$ q  V- p. L0 k- j3 S. V  ?1 U# @* e0 z
    res.head()% t( c3 z9 U, \
    Out[236]: " L6 ?" w3 I; s) _/ ~2 C
    Month
    ; z$ }) y6 }( K, x; \1    65.313725
    . S1 w$ k2 N; `$ _4 D" ~2    54.061538, G2 H/ R% a- M$ r: Z! S
    3    59.3255813 Q7 k0 C! o  M
    4    65.795455
    5 m+ A* T, K) Y5 ?5    57.4651166 _- x! `, Z  X$ |- k- f1 j3 l
    Name: Sale, dtype: float64
    , w& e. _7 v; _7 o0 a- j7 L& ]# J& ]( K, Y
    1( }1 V$ x" d6 ~) `: g
    28 U: M6 g7 m5 X$ t. w0 w  B
    3
    $ I: c1 p9 Z- {# g- w4+ T% I- w( T% o% |1 i6 d! e* f6 \
    5% A% Y. a) E. }5 }. b- i' F. C. V
    6
    & X& p2 I5 i) x& T; w8 U  _7' n3 C( K. u; {5 |
    8
    3 V8 d7 A% r1 f( \" W91 P0 {, V7 |; M  _, L5 Y2 L+ p* O0 n
    10
    3 |5 ]2 u  f; n3 }11
    , a, q& k7 L& w$ s7 J! t% {126 |: p' L/ d/ p# W. _' |4 ?
    13/ a- m& t$ \6 w! J* t9 V* D
    14* D5 i* m, E5 B# Z
    15
    4 q$ s& a) N3 Q3 n8 h* l, f! X$ J: j16
    ' ]1 C( T% g  ?0 [+ P* i170 K5 ]9 X# X, ]4 [
    188 G1 U. H4 l3 ?4 Z2 {5 K, B
    19
    * n/ O: v' G6 Y9 W, X! Y" F20
    9 x4 X3 C/ z4 e按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    9 a& `9 Y8 R, e5 }; B" v* Q: v6 Cresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.& V9 ], B9 F9 g+ x. e8 J
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
    # s2 W" q9 \8 `5 M                                        7 s  K0 J+ H# N  G  H
    result=result.unstack(1).rename_axis(index={'Date':'Month'},
    + G; R- G+ I* k) Y, R8 y                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.. i6 u  s0 k" U( g2 |3 g2 ]  M5 S- m
    result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)* y: d' Z7 `* q2 P* d( _% P
    result.head() # 索引名有空再改吧
    * O  L5 @7 Y( b, }! S2 B4 U- O) o. D
    4 j1 v, r3 u, `" f          Week        0        1        2        3        4        5        6* `: s& [! x5 v& C7 N
    Fruit Month                                                        1 G" ^7 x+ Z  ^" u
    Apple        1        46        50        50        45        32        42        23
    * ^, p4 f. D( k! l2 D: GBanana        1        27        29        24        42        36        24        35$ B" \, S9 E+ ?* d' F  A
    Grape        1        42        75        53        63        36        57        46; \3 }" T* b0 F# U$ i! u+ l
    Peach        1        67        78        73        88        59        49        720 \0 K, g( x7 \+ ~; X
    Pear        1        39        69        51        54        48        36        40* O' m1 {5 l5 P# a0 y; T
    1" }: Q5 s3 }0 j. e( _/ R4 g
    2
    ( D5 I, @, p" x  j* D3
    % w4 G. Q1 s+ Y, U; V5 |4
    1 ^) j, B7 M/ Q" j6 U  m5  Q; q1 B. n0 j
    65 T1 N9 ^9 I* L4 Y7 z
    76 e7 A' {2 y$ d0 e5 f; F
    82 E# D- K1 j7 Q6 \* B6 y
    9
    2 b+ t9 |- x8 u8 p4 t: o* Q3 {105 ~& J% o1 o6 `: q' u
    11/ o# X* S& L! }. v* {
    12: N! k: b! k9 X! D4 L) V" @
    13# A& \1 U& N0 y
    140 C4 M) C- n9 S3 C' `
    152 M0 v( F/ F  w4 R
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    2 G  ]9 b, D) P0 e( u. j5 ~2 U# 工作日苹果销量按日期排序4 Z! @9 c& R: W) f" Z
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
    ; n5 n; X# d2 s: V2 q; c! M- ~8 {# wselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总3 T) v% |. J; Z3 l  ~7 c1 Z1 }
    select_bday.head()8 u2 Y7 w& v$ \8 V* [. a

    9 [$ C. @3 t# F7 k- ^1 [Date
    , E$ F" w5 C% \/ u2019-01-01    1892 I$ T9 |6 Z1 m' l4 ^! U
    2019-01-02    482% O4 A, P4 Q1 Q% l% `# s6 p( T& \
    2019-01-03    890
    8 O3 _% f: L0 C+ ~8 Y' R( n1 @2019-01-04    5505 h7 @1 R& G; R# `# {
    2019-01-07    4945 g0 |/ s" _  b
    9 ]- S: a$ x2 \) I8 v
    # 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。6 b7 t6 I: l, E
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
    ! Z, D' ^/ y; D  A5 |% R9 T& O6 M: W0 u& R0 {5 ?+ r
    Date1 O8 c" U' }! t  W2 q
    2019-01-01    189.000000
    : [5 H0 {' f# D6 l# h- `! Z2019-01-02    335.5000007 F, P5 e9 C) c8 ?6 R! I
    2019-01-03    520.333333( J' n1 g1 ]+ W$ V% x
    2019-01-04    527.750000
    ; X/ X* w% ?  U: c$ G2019-01-05    527.750000' f- P5 R3 M  b7 }9 k

    9 w* j. u+ _; o5 |————————————————
    $ S6 C" ?2 J8 k版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。% h! E5 V, n7 x3 a' A! y; B8 @, D
    原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    & s& V: J2 q; a6 D  V1 o9 z' \& B) H+ \. \5 d9 n  A
    ' p5 g5 R! S2 q9 Q
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-7-31 04:26 , Processed in 0.470301 second(s), 50 queries .

    回顶部