QQ登录

只需要一步,快速开始

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

    : c( u( R% e/ Z* |
    / w5 H1 j% [" R0 l& Y2 H% p3 l2 X4 @2 A$ e7 `* G
    文章目录
    9 Q% h: |" u1 u& G0 M2 `* Q- f第八章 文本数据
    $ X+ S" Y; n8 v8.1 str对象
    . k6 Y2 [* W$ ?. D+ n8.1.1 str对象的设计意图2 c! s. Q. N' g: R" k
    8.1.3 string类型) ^8 _6 Z% V7 j" P
    8.2 正则表达式基础
      Q7 W$ v  B5 R! z8.2.1 . 一般字符的匹配
    ) |* {( x0 d  c$ B( \4 V8.2.2 元字符基础' S! j" R8 }# [
    8.2.3 简写字符集
    , y) b7 g. u; v, B$ |* l8.3 文本处理的五类操作
    - p8 r+ f  L0 d! _8 n8.3.1 `str.split `拆分
    * i- E! c6 u1 v5 ^# S8.3.2 `str.join` 或 `str.cat `合并
    8 [1 o! a2 Q3 B8.3.3 匹配
    7 ]! V% Q$ |! V- r! j5 I: M8.3.5 提取( d" p: y9 J; s  Y$ _
    8.4、常用字符串函数
    + G- T6 T8 I" [4 e' K( u8.4.1 字母型函数
    ) ~& L. ^$ i5 w9 Z8.4.2 数值型函数
    - F( r. x6 S% p- I5 j8.4.3 统计型函数0 A5 ~- }  l* y- B6 T7 f+ \. V: G
    8.4.4 格式型函数9 Q4 ~; g: ^; b  Z1 [, n
    8.5 练习
    ; o* f" i5 X3 ]  i6 fEx1:房屋信息数据集
    " S+ _; i# s2 w2 KEx2:《权力的游戏》剧本数据集* S" X# \# m$ _. Y" m  s
    第九章 分类数据4 K0 g: E9 i$ M7 c
    9.1 cat对象0 n  p' Q, G6 i7 M2 ~/ ~6 p
    9.1.1 cat对象的属性% u# D) e2 D, j) e4 j
    9.1.2 类别的增加、删除和修改2 u+ i7 B6 @5 e1 M8 u% d
    9.2 有序分类( a  [4 d$ w+ d0 T) s* C8 ~: y0 Z
    9.2.1 序的建立! q; e  f- c2 e2 D
    9.2.2 排序和比较
    2 n- A1 ~* v3 p7 g; G1 {. l# K9.3 区间类别
    # M9 x" Z& D( y3 @0 h9.3.1 利用cut和qcut进行区间构造
    6 ]/ O, T# q: \9.3.2 一般区间的构造5 Q9 {) x$ K8 ?& {1 k$ s
    9.3.3 区间的属性与方法- M/ A5 I  ^% {2 U1 N2 i
    9.4 练习3 T) k6 K% n  r! d9 s6 n
    Ex1: 统计未出现的类别
    ! e$ S$ t$ N% S3 t4 |Ex2: 钻石数据集4 ^* j2 c6 r" y/ Y8 t
    第十章 时序数据
    2 o1 o4 f$ K9 ?/ v) E10.1 时序中的基本对象6 u$ M" O" G4 q
    10.2 时间戳
    + g: @$ j  R* c* Z10.2.1 Timestamp的构造与属性# t, S+ K; H. a6 Z* W
    10.2.2 Datetime序列的生成' S1 T* Q* \: B0 y
    10.2.3 dt对象. z( }# @2 O: X
    10.2.4 时间戳的切片与索引
    & Z! z+ N0 M  K& s10.3 时间差
    ; M1 H0 h; L: c10.3.1 Timedelta的生成! {* J; b7 ?5 P
    10.2.2 Timedelta的运算) M5 b4 I* w2 p: k0 Q+ [9 o4 w$ Q
    10.4 日期偏置+ s4 y* R" A. S6 t! k+ I: J
    10.4.1 Offset对象
    5 A6 T7 c, |0 H. S) Y4 q10.4.2 偏置字符串
    ( Y9 w& _" x8 w: c- [5 c2 T10.5、时序中的滑窗与分组& V5 D+ p& S* j& c# ?' X, k- C
    10.5.1 滑动窗口9 d" t3 @6 s( H* {. ^9 J
    10.5.2 重采样
    + I5 U0 O" y: u; c+ W+ y& _" Z7 M10.6 练习
    $ ^$ N& S0 A6 q# W% [) MEx1:太阳辐射数据集
    8 n2 V" a9 C% U+ C( _Ex2:水果销量数据集0 k7 q# P) X# j5 }
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网2 p/ [) P: U# c# F( ^: @* X
    传送门:8 ]4 S5 J+ H8 P$ b
    . k* q, i6 y% S$ a
    datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
    1 {2 f! e) J& u( A8 L) Ldatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
    . @/ a8 ~! X; b第八章 文本数据( H' e- |7 _7 e& M; }
    8.1 str对象: j8 e4 l3 I3 P% p6 w- ?5 {
    8.1.1 str对象的设计意图$ X) c5 `# R$ Z3 K( A: u: R- e
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:3 V' v9 K6 \' l$ z3 ^5 @) j
    ' U, L, U: y- U
    var = 'abcd'
    7 B" \* R4 L$ q4 C4 g! sstr.upper(var) # Python内置str模块
    ; n' V: ~. b! [. v8 GOut[4]: 'ABCD'
    $ ]' V# t+ y0 Z" j3 B/ A/ n  h5 z) e3 c) [1 O
    s = pd.Series(['abcd', 'efg', 'hi'])% F4 s2 b3 r, J) ^' |
    $ ]) w( ?( q* t7 l% C1 \
    s.str. t7 }) M% ~6 }7 ]$ e  l
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    9 F6 U% U4 x0 L9 F% U6 |) @7 X
    s.str.upper() # pandas中str对象上的upper方法
    9 t8 P" K3 ^0 M7 f& u3 [Out[7]: ; @+ }# j5 ^7 j$ E: a
    0    ABCD( C6 H1 T  H$ |! N& e0 S5 S( C
    1     EFG
    # i: ?  w" @4 ^# b5 l! C" I9 v3 }2      HI$ p1 [2 Q: q; l0 X3 h  W$ O
    dtype: object
    ' R7 n% q+ i, b1 n1
    ( c8 I+ |+ m/ z7 ]* r7 R" T; c* ~2" x/ q% O" U4 G4 D" r% P
    3
    9 R8 I" S, b8 L8 E41 A1 Y, J4 `' O8 r" q, q& M
    5
    0 u# i5 V; Q. m' l4 o6" k% k1 N! C3 a) F2 ?
    7# y7 s7 H+ I+ T5 C1 q
    8. k; g) Q/ Y8 \8 ^9 q
    9  ]7 G/ K5 P6 d5 S. e4 m: [+ R
    10
    7 {% R& d$ O6 C$ [5 R11* ?( v1 ]7 y4 N. W8 U, J' n0 R% x
    124 @7 t# w- L9 @! P% {
    13' R( {+ O% W. W, Z
    14+ g' i& v. G6 J" T, o: v
    150 ~4 q! O9 d, G7 Z+ [
    8.1.2 []索引器
    6 X" ^% f+ O* \  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
    / @- F% X3 W! N1 I  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:  t' a8 b, A( L7 T( ~

      ~: `( p5 `& @: b* ps.str[0]
    + n% P( z0 y# v" e% F2 S' POut[10]:
    " {. [# H' ]& I/ x0 E6 A0    a
    2 }* J7 A9 W3 q4 m1    e7 a; R# u6 K. ]& o
    2    h
    , ?  q9 l1 e' M. C) B* |dtype: object6 }8 V7 e1 y; e1 z
    5 O3 H) [' Q' v5 Y% ^5 F
    s.str[-1: 0: -2]
    % V! l# t( g1 |; [+ ?Out[11]:
    2 `3 m: U9 v1 I( _  s" l  x/ U2 t0    db2 {9 N$ N3 O: w; J" }
    1     g. W8 F' [( E) j* l) o( c
    2     i
    ( m1 C0 w( `1 Z! H9 sdtype: object" J' u7 H- e- ^5 X+ [' _7 d
    # |3 l8 J3 R* J$ i4 f( N) o8 ?
    s.str[2]
    % }5 s$ _' n) Z. G+ A  `Out[12]:
    " E7 r3 V+ z( h! w* `. U( j0      c( d) j4 J( l+ l* i! G, t
    1      g7 l8 L" `0 n0 D( A4 g& h
    2    NaN# E* V% M, R/ Y8 w$ v1 ?8 F% s; f
    dtype: object
    2 A3 \2 t* \7 z& r2 l# N+ v  R5 _
      r& R2 n- M9 i0 G1
    6 r$ `3 [& ^  ]) q  c- N$ H+ y2
    ( u; h5 L+ J4 B) H  m3
    + D. n. ?( A4 K4
    ! o0 y" \: x5 ]: }" W- Y4 w) \5
    / g7 Z" ?3 _$ D/ n" Z- R/ u6
    4 h) ]# k' u2 y7
    - M/ U* Q' q2 K, L% R& W. a" J8$ L' K/ Y0 `7 _3 f! R! \. T9 n
    9" b, X* h4 |" a9 p! G
    10" r: N6 E6 C! B8 l0 {2 b( p
    11' X8 z2 g) c4 @, H
    127 k/ _. Y' Y3 `3 T
    13: W) D: x8 z, e/ j. w
    14/ `7 p) N5 }$ z$ z
    15
    & f+ C) k7 J+ c1 d* l16
    3 {6 j" j1 F6 ?3 H9 m  m0 M1 i/ S' T17
    : N5 t/ X: I8 Y/ ]' W7 s+ g  c18* ~: C: k( ~" E  `! [
    19
      g9 T5 ]% u9 y4 F20: a* N# c; @( l, x5 c
    import numpy as np" K3 k: s. M: `: d
    import pandas as pd
    5 Z( L! r) p9 R
    & t% ~7 g( o8 x7 Xs = pd.Series(['abcd', 'efg', 'hi'])
    0 I* `3 I* q) _, xs.str[0]
    # a5 T  A  |% B# J1
    ! B' T9 s8 b2 p+ B; f+ ~2
    / ^, Z+ q) Y; l$ W% J! a* o3) D6 Q0 C6 ?$ J) Z& h: n
    4
    2 \  ^2 j% ~+ @7 \4 T5
    # |3 ~+ t! w% U0    a
    ; ?- P/ \  b* i- l- m1    e: n0 E9 D: k$ R8 ^6 D$ @6 h: h
    2    h
    % X7 ]+ b3 n6 h9 f9 v$ n1 H. bdtype: object& k2 x  W. H" g! G* z* w$ \
    1% j! k; i! D" `2 e0 s
    2  h; Q' Q2 _0 c' [
    3
    & q% R* a1 d3 w  W2 ^4
    8 V1 l+ A5 o6 }% ?) v: {$ Q) h! t8.1.3 string类型) b; ]& N) C" n2 k
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。. _: p: k9 D3 k1 D+ d
      总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
    - q" K5 Y' @( ^6 ?; L: R$ l9 N4 I! z0 V
    二者对于某些对象的 str 序列化方法不同。
    , Q8 }# g% n/ w7 }! V* I) a( l6 z, S可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
    ; v% B( }+ N# _s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    * ~1 R/ O2 T1 d4 |: v+ |s
    / g3 S% j0 }$ S7 a0 t9 B! K: \" K1! {( Y* m- t4 T$ h; t
    20 \* n8 U: W0 a1 ?
    0    {1: 'temp_1', 2: 'temp_2'}
    . u4 T% f1 {3 X9 Y$ r* P1                        [a, b]5 q$ Q5 U2 f" c
    2                           0.5$ O, f* W6 |5 v& @
    3                     my_string
    2 U2 K) T  f' Y* }: Edtype: object
    , ?! u& C5 Y6 M3 E& `5 [1
    2 H+ _$ ?5 N8 N* |7 g- p" |2
    6 }4 U% B  ~' r3/ Y3 b! p" F! A4 C
    4
    8 i) ]/ O' l2 g& K$ h5
    ' v- e3 q& [5 D1 `4 U# ?s.str[1] # 对每个元素取[1]的操作
    * f/ b. v3 |5 e: z6 S17 G' l6 S. K  c5 ^
    0    temp_17 u: N( h( a( K, N4 G1 ], i" v
    1         b
    1 o! k8 p. x; {% S  F3 F7 V9 ?* M2       NaN; ~4 _) x  T8 n$ Y5 ^8 {/ R$ s* C
    3         y$ H, y1 B: n  k2 ]: Y: O
    dtype: object
    1 Y9 S0 _5 H; \3 k6 v: T# t9 q10 J( \4 |/ f; W! Y
    21 q) U$ X! A" h8 W
    3! I& @% `1 w1 D6 U3 {& q* H, m9 ~
    48 ?! N  ~2 A- v% O3 c7 e. {/ \
    53 }/ ^$ `9 {6 X3 ?* K% h) L
    s.astype('string').str[1]" P  P+ w7 T1 a) T3 h; o8 ^
    1
    7 t* f% ]! U/ H& e0    1
    ( X# Y+ U2 {0 Q1    '
    1 E0 U8 v1 t# H& k2    .# S) S! @2 m) c% `( J% H
    3    y
    8 K" I: C) V5 G' B+ w* a+ {dtype: string2 k& O* s7 p( S/ x3 H
    1
    ) T8 n$ {8 m4 l; l1 Z5 b2
    5 U5 ]; v4 s- X% k% W4 `$ B3+ g1 q1 U) W4 T5 H+ C2 `
    4
    ( f/ ?5 A% L1 b: j9 Q( }% F5
    - O0 U/ ^3 g  ?2 i+ F除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    $ {' `0 d7 ?  v' D1 ^# z# W: J, H4 L9 z9 {% k9 }
    当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    6 [. z7 W9 Y- ]" mstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
    4 C/ x* e1 \( N: j8 V' t3 estring 类型是 Nullable 类型,但 object 不是# ^# V4 X! |: Y" c: P
      这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。' p( H  O' d0 x
      同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
    ( |* r7 |  U/ ~# Os = pd.Series(['a'])
    & \- z+ s3 @. ?/ ]6 L% a# P4 o9 y+ o2 m
    s.str.len()
    1 L6 E6 ~0 s  \& b" cOut[17]:
    & A7 _+ l3 h( K5 T  O0    13 ^0 A5 A% m* P( k$ n5 o  i
    dtype: int64) }5 t5 @8 _% `- g* G5 z$ Q- T# _
    * n: g$ u7 s. S6 w" X
    s.astype('string').str.len()
    6 B+ E" `5 k8 q* V, D2 XOut[18]:
    $ d, c7 w0 E+ {4 {" {8 `0    1* \$ B, w0 O9 K* G
    dtype: Int64
      b) l  s+ u% Z. ]! S6 g
    ) z5 G+ V0 B7 r  `6 F: u1 G  Ns == 'a'
    4 y3 R. d% E# ?4 t; C0 o- h& s6 O' YOut[19]: 7 L# C) q8 i5 n' z
    0    True
    1 [4 a0 |$ D( A" \dtype: bool
    ) x& I4 D  m6 n# \( ~. W0 ~2 Y7 v% M9 x5 S
    s.astype('string') == 'a'; i6 |3 l& N7 j4 w5 q/ ~0 o  Z# P
    Out[20]:   t* X( F2 U) c1 x. n/ R0 W6 e
    0    True
    , L4 i: y% u; o* V7 Sdtype: boolean
      f& _* H) ^8 U# |, e- }+ d& k5 h# ~! l  Q8 C/ l
    s = pd.Series(['a', np.nan]) # 带有缺失值
    - w# @  p5 W* d' r8 g
    0 R, j  s5 V) {9 ]) N* z5 v* O* Js.str.len()) F9 o3 t2 i- _# @9 c+ n- b
    Out[22]: 7 M, U, _9 J5 ~' w; V3 Z+ i
    0    1.0" D7 ~6 d( V3 T. ^
    1    NaN! s/ d# |0 v' V1 L  ~
    dtype: float646 K! f# b  @6 h; [& C( [! ?

    ' q2 j! X. |; |0 u0 d0 s5 Ys.astype('string').str.len()! h* V+ A# I6 x% l. Y5 K
    Out[23]:
    ( E; o5 c" \" C* u+ Q* D0       1  Z2 r! P0 k# Z& C
    1    <NA>' r5 f7 d( Z+ P0 F) P' {
    dtype: Int64
    4 v" R. Q2 M7 L) J. l8 A% T
    9 x" L! V8 ^& x) Ms == 'a'
    9 b: i# `( Q' ?5 t4 q' @5 p+ POut[24]: 3 X8 F+ X7 h9 H7 |% J! \
    0     True
    8 e: i& N* }* _4 @1    False3 z! a. g* E- h1 E
    dtype: bool! d8 f3 @" u& `7 x& ~

    " f$ |4 }! N! G% L6 E0 k5 \s.astype('string') == 'a'
    * \$ h( z6 u8 qOut[25]:
    3 f( A* Z: [8 T3 e: F6 N% p: @1 C0    True
    ( S2 I& [/ n6 D) [1 P/ u' P1    <NA>+ D+ f7 w+ w+ l$ a/ a
    dtype: boolean/ ?" F' |! O7 q4 r8 O1 H, d$ ~1 @
    - r, C6 r: s4 w; ^' T
    1
    0 s' h) R4 b* d) u" X% c29 a; V/ ]+ c+ h+ W  f% G
    37 p$ c3 H6 x- f; A/ b$ i# \$ J
    43 Q5 O& r: U' I
    50 i" \2 I# Z6 k8 S) f
    6
    9 s2 J8 r$ ]* d% ^7
    : D  C' Q3 _! S! p3 e+ @82 W6 S6 U5 E, q0 `% X
    9' {: _$ i# A: m  Z6 [: @
    10
    & E) B* P2 n, N+ B2 a11% ~5 p4 d% g  K$ ^4 ^0 j
    12% v/ v1 {5 V/ Z# Y9 f6 T
    13
    2 {, h: g: t5 `" V/ |14
    - x) K" A' ~1 z; y15
    4 j4 f" g( L8 s. W. U+ f' z4 R16
    : A$ r2 u  X3 c* d& i1 T17
    4 g' ]$ p2 @2 W- U' d8 j18
      J4 R1 b3 ^; E- j  o19
    3 K% i; ^: m: o# y0 ]# ^( m20
    8 |' \% O- K  ^214 z2 h+ d; r( |- k% Z
    22
    ! G/ `7 R) w6 n5 O8 j; `23
    " i( K2 V" Z- f, m2 R$ W. E24
    ) Q& `5 v% G: M255 X4 S8 {+ c" n) e% p
    26
    8 ^3 }5 }3 X& g278 O) N+ }  M+ A1 {9 s$ t, Z
    28: |6 D- }  h* ~' W# e
    296 N* j+ ?. E1 O- q
    300 P5 a& [2 J+ L$ a9 T) Y! O
    31
    ' a3 B4 D& t+ Q$ G- h! \+ o32; ?. z6 \& U4 A& [- s- O4 M5 J
    33
    7 N4 [. K0 m, @4 B9 Q, i  V  }- b% r  {34- G) C) U( `5 z4 C
    35% R9 m2 m6 `( x8 j' B% }% W
    36: I$ V6 e* |1 u3 W7 j
    37
    0 c4 i) h; W- n. M% |8 C38
    4 z, d0 E. d; K% A39
    5 Z# G7 }: {% z8 z5 ^40
    ' b+ \- F# L+ g5 w! k" |* h' r8 `41, W0 ?& m! Z: v. R: A/ i
    42, {2 Q5 j$ V9 H4 l' n
    430 x# L: L+ _- J2 W4 A
    44
      _! G- \9 a( J9 {$ t/ }5 L45
    7 q. q, Z6 T/ \& G6 h& U& ]46
    " B% e" |& @3 Y+ n7 K; G0 j47/ a: m" w0 g) r
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
    ( D/ i& j0 p* m: c- E& W
    ; t: H$ @9 E- @8 P( B. D5 Ys = pd.Series([12, 345, 6789]): L! A: A" S  q! T- ]
    & R; b4 Z  ~1 C0 v
    s.astype('string').str[1]
    $ @! K9 _9 V0 ]3 c' rOut[27]: / p: E- q) y) K# c- q
    0    2( y8 p& x' r5 G/ t* w$ _. ~% h. o
    1    4
    & X) G0 {4 i+ y/ N* t2    7; [( a4 Y" |" J3 k: T
    dtype: string
    & ]4 e/ @' s* {. z$ M1 ]1 r17 G% m% Y/ m, G. z7 j6 E% D
    2
    1 I9 F$ d( k4 l, S, B3
    6 g! w# v  S* x9 J3 v- z4 K+ |4
    * O- S3 W5 p. ~) v# t4 i0 p5 w) F5
    9 ?: I! R% ~$ W4 g/ H6
    / e5 _" t) _6 S% {9 Z7 o, P7
    : O  f9 R; p8 d7 c# T4 S8# q2 o) p: g/ w* O) W
    8.2 正则表达式基础3 z$ e2 X" ~2 K1 J% r% G& w  Z
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书+ L( B3 R) @& A. P

    , ^; \  I! k" I8.2.1 . 一般字符的匹配5 o: n- _% h$ M7 a" W% F" Z
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    5 d% H9 X9 g2 {3 E2 t" @' c1 _* `. `
    + P! g8 z. ]$ H4 e- s# qimport re
    3 o1 F: ?( ?) V8 l/ u' E0 j( T( @, e& U
    re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配7 R! O5 P: C8 C  P3 k5 D# W
    Out[29]: ['Apple', 'Apple']" b; z9 ^$ h% _/ U# }) y3 z
    1  C/ e) H5 O! l6 t9 A
    2" k" v1 V4 o/ [; M5 a! }4 ]
    3' ?' R: ?' L" q# X7 P" A7 Z
    4
    % u/ C- [3 |5 K' H. b$ P$ F8.2.2 元字符基础1 F. \) V+ E2 Q/ |; X
    元字符        描述
    ) C- y( ~$ R7 P" o.        匹配除换行符以外的任意字符
    0 [  ]" j: L& H5 s; e5 {# `[ ]        字符类,匹配方括号中包含的任意字符/ c8 q1 ~0 x. X& a1 Z6 k6 t1 U: g
    [^ ]        否定字符类,匹配方括号中不包含的任意字符
    * p$ r% Y0 z' \+ k% b*        匹配前面的子表达式零次或多次
    3 _( _: K0 R( v; K+        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字7 k0 W: a" P/ X# B
    ?        匹配前面的子表达式零次或一次,非贪婪方式9 h( |: B/ W: x) }
    {n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
    $ b3 n- P; n& z4 c0 g7 A0 v(xyz)        字符组,按照确切的顺序匹配字符xyz1 D3 W+ L  s& M+ ~; J
    |        分支结构,匹配符号之前的字符或后面的字符
    4 A. L/ `  D3 T2 q4 e! _0 h\        转义符,它可以还原元字符原来的含义$ W  m( N7 X( s7 D8 g
    ^        匹配行的开始. @& H1 B- u+ p8 L. x6 H% X
    $        匹配行的结束
    # h1 ~( U5 g% g3 `* j- y" X  L" |* nimport re1 y  Z% `, ^2 @$ ~% B) B
    re.findall(r'.', 'abc'): r. \7 k, R/ m8 l$ l
    Out[30]: ['a', 'b', 'c']# W3 C0 b+ ]/ |2 `& ^! m
    : C1 z1 Z" W" l# q1 v3 p' k
    re.findall(r'[ac]', 'abc') # []中有的子串都匹配
    # l9 S1 `; Z2 `1 y! ^3 ]# K( COut[31]: ['a', 'c']
    4 }# z) G4 {0 i$ \1 U: p: P- W& ?# P7 I# o+ ]+ s* @- g/ l2 a
    re.findall(r'[^ac]', 'abc') 4 k* w# @/ c1 V% \; p
    Out[32]: ['b']
    3 J9 H; W2 g& V" P. @' a" V" z) p0 T$ P, M' p5 M) I
    re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    ' _7 B/ I3 s8 _/ kOut[33]: ['aa', 'aa', 'bb', 'bb']
    8 N0 ^6 u* C0 I$ M- W9 s, Z9 j* V" G1 b$ h3 V
    re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串4 T9 m$ M; P/ l* P8 x1 P( F
    Out[34]: ['ca', 'bbc', 'bbc']2 ^  G7 l+ A9 v, p5 W2 I( g
    - ^3 q" z, x7 z& j
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。/ Q( r" i& K" R. c6 `' J& N0 D
    """. T% T7 Q6 q3 j! a- G" {4 I) z
    1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。/ P4 a' y# w7 f% z0 X
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边; q4 F3 x4 Y) W  T+ m
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,% }" v; H3 K/ O; b& X- a
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    9 i4 g# {8 r( y: i"""
    + O( E+ J# T7 [: n/ f% O9 Q- k& `# p# {! ]2 f
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。$ c+ O( }8 S1 B; W4 X9 O3 `# |6 M
    Out[35]: ['a', 'a', 'a', 'a']: v: Q# ]7 M) U1 \; Y# h

    % S* k% q  x- N6 v# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
    0 N: `) }# j2 J+ ?  e6 l# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
    - \, @. y0 q$ e, a( U) K  Q2 ~4 s% Ire.findall(r'\\', 'aa\a*a') 4 y2 b3 i& n( D( A
    []
    & [  p6 o6 Y+ j$ j, R
    & r  ^% W, W, N1 k7 r$ Kre.findall(r'a?.', 'abaacadaae')
    ' w, \( a0 V% `; N- e& vOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']+ c9 S$ u* r% S+ q7 d- A

    ' p& C2 _. s" e' q( {4 @- vre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
    6 p( \& Q0 I2 ?' C3 S3 b; A' u[('width', '20'), ('height', '10')]
    ) {6 G' h2 z5 l' b5 T7 e  u! }) ^0 l; w" Q! ?
    1
      Y$ [% {! @4 w27 \) K9 X$ [; h+ ^6 u
    3
    8 x6 u) d, k: m8 C+ N9 f4! y9 W$ i0 B, \% ?" b2 ]* T/ h
    58 z' O# k! q! U" ^
    6
    8 p8 B3 e; o% W; G/ @0 g' N7( Y1 r" u1 I* r2 J8 k
    8
    5 s6 f- r$ M9 t4 F9
    ; p9 c& X0 @: Q8 U: X108 K1 s% V8 ^: Q" D9 D  {
    11$ b: D+ D6 ?' Y
    124 L3 x6 F% i9 I9 K# A0 W0 F
    13& K* n# i+ V6 s, N; f3 U! s4 ~
    14
    & ]0 B6 `  E" W0 ]7 j8 s  g15
    9 m. x6 U& Y; |: O' Y* M162 R2 a, t2 R" f7 _+ s$ q
    176 n2 G  z$ u: s4 _: L/ e0 C
    184 u' H" ^7 I3 D+ ?, ?0 A- D
    19! S" s9 S8 L! m: N
    20! V1 b3 k. O, V4 N/ w7 a3 u, P; H
    21
    8 B) z$ z+ ?* W/ ~9 W! }; F22
    % O6 e9 R9 R6 w23
    ( T' V0 [  ~3 P5 |0 U3 _, h24* K& H4 X, x$ X1 a$ a
    25  b3 t8 g+ {/ u, n  c' U
    261 k  ]7 }. k3 t' B
    27' i, j" u! o, J9 O- M
    28
    7 q' Y  e9 c1 r1 e29- M+ A4 F/ M1 m- a( B, J) d
    30
    6 C8 ]- U3 g' z$ A. m317 c( O6 R/ |1 o; s. z
    32
    ; A) m# Y, A; L' p+ F33
    : H9 a! l) A2 o- c34
    $ [: z3 I* u. d7 r35
    0 b6 d! y! j) [. \3 {0 {36
    % M9 J5 R) X+ |37
    + w8 L5 @+ N$ O  m8.2.3 简写字符集/ h* c, q  I& z$ i- V
    则表达式中还有一类简写字符集,其等价于一组字符的集合:2 {8 G- X4 n2 S9 O

    : f! p/ k; D0 D3 `. Q简写        描述& X4 u- z$ f" U8 ]; ]3 k
    \w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]; Y/ S" o7 t& g. b6 i
    \W        匹配非字母和数字的字符: [^\w]
    2 s; W% I$ d, d0 J( `- {\d        匹配数字: [0-9]
    2 ~# |. J% B6 P" H# K/ y) }\D        匹配非数字: [^\d]
    * D7 F% R; W5 {8 F  V/ G4 c\s        匹配空格符: [\t\n\f\r\p{Z}]
    / H, K3 I5 ?! Q; D3 a" {5 s\S        匹配非空格符: [^\s]/ O3 z* T; }5 q9 [1 o. E
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    , R; d6 w" _! v6 D6 ^re.findall(r'.s', 'Apple! This Is an Apple!')
    ! C- `& K+ _( F1 |% k2 BOut[37]: ['is', 'Is']* V0 l! c8 K8 a6 T7 Y+ h" q. ?

    1 k& g9 l. }6 m' u. F4 zre.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
    % z1 f, N# Y4 O7 O4 f/ W# B3 xOut[38]: ['09', '7w', 'c_', '9q']# E( Y* p  y: a# L/ V- w
    6 }+ m, r1 P8 v' h0 h1 ~
    re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
    4 }; Y# u3 [1 y1 ROut[39]: ['8?', 'p@']9 T* ?8 r$ m5 @8 i4 ^( m

    1 U; L5 W5 h) ]$ V( ?re.findall(r'.\s.', 'Constant dropping wears the stone.')& C1 l5 V$ Y7 u  J' A7 J
    Out[40]: ['t d', 'g w', 's t', 'e s']' t$ i* ^$ f- |! u' f

    # X4 C% a' D* S/ y6 |4 H8 Gre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',/ \# d# w% @% Y# ]* X
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')0 r- y4 U+ R% Q1 M2 w" ?# V

    $ ~" n* h* `5 U4 e( q0 HOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
    6 r# C, [6 E8 H9 ?! \8 p
    ' n$ t, i5 J+ R5 t13 ]" u7 k. V; h4 Y
    2
    2 v/ i% \: Q6 m& ^" Z4 Z7 x; F3
    / `  w( M. x5 ?' r! H4
    . Y+ C  T9 d& H7 h1 L0 U' s8 \* Y5% z4 y- i7 [2 N. x4 ^4 L2 U
    6
    0 i% p& Z# R, Q4 z. G7
    2 ], Y4 _" i8 O7 S. S8
    ! c+ ?8 ^; i2 I91 `. Y6 w& L& K4 S
    10
    , [! T: l2 T" w2 v11
    . g; z! C/ r9 H8 Q3 ]127 I6 j/ |8 r6 ~+ I1 C
    13
    - `: J9 v* ]1 S' W0 R# Z% r3 X14  T* i  N: R$ T3 p  ~7 q0 n
    15
    - t7 I4 q6 `# g. |167 C& i5 E- n/ N
    8.3 文本处理的五类操作
    & G- u7 C, |+ g- ~; n8.3.1 str.split 拆分
    & r/ v5 X& ?4 O: n8 y  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
    - R) R5 N& [0 b+ J; M( x9 T/ d% a! ^. M; M
    s = pd.Series(['上海市黄浦区方浜中路249号',: V8 A0 r1 F; e" S
                '上海市宝山区密山路5号'])1 B6 `3 z* @/ r. |% t5 L" W

    # M* ]0 e8 k8 O, Y
    5 U2 y9 W# ?1 gs.str.split('[市区路]') # 每条结果为一行,相当于Series+ Z7 Z. v! v/ N  h
    Out[43]: , |% s7 ~" x0 a  U4 s
    0    [上海, 黄浦, 方浜中, 249号]  u1 Z& \2 v7 t; k- R0 q
    1       [上海, 宝山, 密山, 5号]
    2 L5 T- X* J* ?9 Bdtype: object, }9 T  M6 I3 K  Y7 `: y( {; f
      r2 M) K( o' P7 Z- w4 c
    s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
    8 j( x5 K: n  ^' T9 K  POut[44]:
    ; r4 B6 ?0 Y# D: H; p& P    0   1         2) K; O+ R) x8 I3 r: e9 C
    0  上海  黄浦  方浜中路249号
    4 p0 \* y1 U9 Z5 j  V" `1  上海  宝山     密山路5号
      P5 |' v* C9 l1
    ' P) s4 O2 p' S. j2
    5 m. h9 h6 K+ P. j7 B3
    ; k' V6 @. G$ S( T- w44 m2 T4 x3 d! f) K1 @
    5
    3 L6 [: z2 |2 l. Y* |; I9 T& G6( Z! I! K4 B3 I
    7  _" Y# S4 m, {8 S
    8
    6 ~7 o, T) Q3 J; `) d) R' ^9% H2 f" [6 P* ?  G6 G
    10. p: c9 X2 w3 ^
    112 t1 x8 A" G1 O
    12
    3 F! K9 s( J' D( R3 }, h13
    , Q% L8 g4 m6 ^" b9 R14
    ! M0 P! L* V" _$ I6 S15
    6 U4 L! }* c. F4 f  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
    ' C! [$ T' E7 t/ i
    5 G$ U- d6 t1 e1 G6 ]1 ls.str.rsplit('[市区路]', n=2, expand=True)
    + Y2 J- v1 C- [+ f  M/ G. o: g% vOut[45]:
    3 E+ o4 G/ ?3 N- q                0+ F/ c2 a: e" J8 N$ s& m% T
    0  上海市黄浦区方浜中路249号
    3 P% R" \( [  E1     上海市宝山区密山路5号# ~0 i. q7 I! g7 {9 t! a. k
    1
      d: {8 r! ^$ e  a21 n: [; q- r- Z: I2 C0 E0 q( H
    3) r8 V+ n2 ?- V" S9 Q; ?" `
    48 O. s$ p4 s+ Y7 a2 l) H  O
    5
    % g9 j( n' i2 t" ]3 |. s( C8 |8.3.2 str.join 或 str.cat 合并7 b- `1 J" N/ X" p
    str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    7 f3 i, L. Q( ystr.cat 用于合并两个序列,主要参数为:
      R) \! u" i& U1 P" q' b# t# psep:连接符、4 V# W5 k: U8 k& g- `  P' q
    join:连接形式默认为以索引为键的左连接. s! f( ?4 E0 U: F  J
    na_rep:缺失值替代符号2 ^+ _# \/ W1 i" d9 L$ l$ D; G' l
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])! u: S, |: t0 ^; W7 e0 ~
    s.str.join('-'). f( l' s3 l* m$ y, f% L  x5 U
    Out[47]: % Q" Z0 a) j! D
    0    a-b" Q% l* t# _. N# W1 j# l
    1    NaN
    ( `! B. A$ `: @- F2    NaN5 ?0 M) i6 Z' [  e3 k/ z5 K5 ~/ e( Q
    dtype: object: L- J' |7 V- e: D9 a7 I
    1
    ! T$ u: j' v! d2 {, w2
    3 f% q' d5 s' [; g. I3
    : d" T* }7 X6 z5 L9 I4
    2 w8 g3 [. ^% q$ I5) u) Q" F! ?  H: O
    6# q! J; e. [( M+ L9 g3 @7 Q- H' `/ b" R
    7
    $ @0 z/ J" r) P0 N* ~7 Q! x' ^s1 = pd.Series(['a','b'])) [+ i) G( o& ?3 y  o, ^' G8 g
    s2 = pd.Series(['cat','dog'])$ r. T! h! t8 [$ R1 M
    s1.str.cat(s2,sep='-')$ ~+ W. o# _  I0 m$ g5 B- a
    Out[50]: $ d$ Y& s) f7 p) y
    0    a-cat
    ! x  g+ y% z8 c* c1 b5 l. w2 A6 d1    b-dog
    : J  w  D% B5 Q& W) |+ F: ]; Tdtype: object! ?$ H. z: q" O  k

    - Z  v, Q8 ?1 b2 |3 }) n) vs2.index = [1, 2]
    - }$ G, x5 p; z. `( j- Vs1.str.cat(s2, sep='-', na_rep='?', join='outer')
    7 l, v( t/ U, v! u+ oOut[52]:
    $ t2 B; e% m4 k0      a-?
    * D& ]6 P$ @7 _6 V7 [- K1    b-cat
    6 f# a8 V  e7 F6 l; r- R2    ?-dog
    ; g0 |1 q2 M; l9 O8 v" F1 Sdtype: object) c0 F/ H. ~5 q7 z4 y2 }7 i
    1! g# v; B& B7 \- B9 v- M; }
    2
    ; O/ b, H" t2 l' d8 f% I3
    9 U& B, K  c+ m- h" a9 q4
    ; C+ p, i% [6 g- I5
    2 B& I/ Y) U1 ^+ \7 [6& |+ x: e1 w6 U& O8 R8 ^: y
    7
    . Y; e+ g( j* @1 E# R3 M8- {# ?" q/ X: j
    9
    3 c  d1 s* H: U8 F  \1 [10$ p' ^% g' i* Z* P  H: V2 V2 K
    116 [: Y* i4 y# i
    12
    4 Z  v+ Y! G4 _13
    9 m& }* E9 {6 T7 [14
    , K5 j& @; e& Z5 N- C) {. v% i158 X3 Y+ }3 e) J9 z% O7 U1 {
    8.3.3 匹配
    ) d% q, [$ P" D* Kstr.contains返回了每个字符串是否包含正则模式的布尔序列:
    # {$ c/ G* P% N0 r! [* T/ L  bs = pd.Series(['my cat', 'he is fat', 'railway station'])
    / Y- c6 Q& P' C+ q4 h8 N: u& A. \s.str.contains('\s\wat')0 N! o4 O% ?5 D3 j% ]! C
    % X5 c* b3 |$ e7 i0 Q
    0     True
    + U4 b# M- B9 j1     True( `3 J' ?% n+ G( H7 O& u9 c
    2    False( ^1 J) J# q5 ]) _! @' I, y
    dtype: bool+ J) e0 j# O: A) |& J6 k
    1. \  _( y4 @. F. M+ H
    2$ V5 g! y- z+ j  _) Q7 ~$ ~
    3
    - d' z2 B3 `' h3 I44 j4 f6 m5 D, Y" m. R
    5. k" @. C( R4 W+ Z" \: R
    6
    ; h% J8 |- J% Q7; B/ y7 G$ Y% y! `# r% `
    str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:9 V. R7 l8 g; e1 ~) Q9 U
    s.str.startswith('my')
    4 S+ X: [5 z3 F& C/ |& w7 \. E  Z
    0     True. b2 [5 u& [# [/ J& y3 U
    1    False
    : {( ?0 O% Y% }9 d$ D$ e3 P2 S8 O2    False8 d' j! k# |" u8 l3 P% `
    dtype: bool  b# n2 O, B) Y) W1 r. k- z6 S
    1$ n& n( B2 W: ~4 D  @
    2
    + M3 N5 t8 g9 N' T3
    / S2 H; I  f0 Y  L4* ?4 [7 I4 E/ g  H8 r% o  i
    5! w# ~- i, K4 n. _7 |- d, X
    6# W) f0 M8 O* `, ^, |1 }, @
    s.str.endswith('t')3 L/ S' Q/ R7 A1 O1 [0 O! S

    ! G+ p* }9 V% z7 x* k" [- O0     True: x1 k( y* h9 [& Y7 z9 U
    1     True
      j+ E5 |- ?  J, l3 j$ @2    False
    ( a# }" c$ `: `! wdtype: bool
    / x2 z* {4 m# j4 K* V19 ?! Z7 \" J4 U6 c1 X
    2. _  q/ I6 t4 f! s
    3
    ! e" p0 Q7 q2 K) Z: f; S3 h$ A4
    . p1 {* E: W8 c$ p9 Q; o% [# z6 D5
    . t' p- A5 s' N$ n! z8 [6# x1 c6 I- s& t
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)/ p- K5 V( ^0 @2 d- ^7 u
    s.str.match('m|h')
    + S2 [  v+ V2 }& as.str.contains('^[m|h]') # 二者等价
    7 `1 w; f7 K# L! s" e
    ! V# s; e# r/ z% O/ v( c4 K: N; y" ?0     True
    $ w  Y: D* w" P* a" Z1     True
    / @% z5 C. U7 Q7 s! u2    False9 [* X) k% r1 Z
    dtype: bool
    4 [7 w/ e' @0 \' i2 B3 L12 i9 U3 m- t3 O( H4 i8 m, [* x
    22 @8 R* [5 O( J, e' Q
    3! ~9 M! }  P& W- x+ c
    42 D& j+ {" e9 [) h( l5 M! @
    5) C# `3 u3 Q1 N4 ^1 ]5 n, h4 s
    6, o8 _) O4 s1 n
    7
    - o5 D4 v% _& es.str[::-1].str.match('ta[f|g]|n') # 反转后匹配. Y+ D" V. l  \/ T2 a) D
    s.str.contains('[f|g]at|n$')       # 二者等价
    3 @: s! k" W. c$ G1 Q( r* D0 c. l3 w/ [& `+ n0 D/ ^
    0    False: b1 J9 X8 ^* I  I& a5 b
    1     True2 D# U# w: L& z1 Q
    2     True6 S; V8 T! M; E9 ?
    dtype: bool9 J& V; i2 P; q9 `$ m  V
    1
    5 L$ `( o3 S* F; V4 a3 e' Q2
    9 C* _  N& w: O3& W1 O+ I6 s6 h( ]- {$ I7 `
    45 {% U5 ?+ s: n
    5, `5 ]* M; J6 O) [" c1 t+ M
    6& Q9 _+ @. U+ N/ ]
    71 A5 A# Z# E! Y6 g- X
    str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:% l& N$ @5 C) H$ L0 F
    s = pd.Series(['This is an apple. That is not an apple.'])
    ' z5 c* ~3 R" Z! Y" R- s: V
    * f' j% [) O3 X5 p. Q8 ss.str.find('apple')' D' B! S+ s+ X- d
    Out[62]: 9 k- _2 M0 \! r" ~9 o- ^
    0    11
    7 q, H0 @5 \' \! Sdtype: int64
    & a9 x' V$ L* R' K8 [7 [, V' ~5 U) R
    s.str.rfind('apple')
    , G4 l$ g0 v  n$ r. wOut[63]: 4 w! Y, U6 [; n* H4 U- w1 p
    0    33
    4 g' ?' q0 d: a8 h* A6 S9 \4 hdtype: int64, L+ G% z' K! O: {. o6 p
    1
    + |: B; a2 [+ u6 }2 V  H2
    ' F+ H  u& z0 Q3
    9 k8 J  M( G* _* X. |( v, x4
    + `9 f- t: l% @: y; l' n" N59 ?( i; N" i$ p# C5 s) U" r7 A5 C
    69 K& G) c* C" g$ D
    71 R& s7 e) f" _+ {, M7 j6 h4 G' q0 l1 j
    8' G8 M' k/ X' I: R; E
    9
      T3 l+ B" {" \5 B3 m' ~: d. w10
    2 b. p  l( a' e1 B& n+ H( m11
    7 m) r) {. Y# U1 t替换
    0 Z5 Y- @) F" j% j! Istr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。: s% B; ?% ?1 {- E/ e4 F
    s = pd.Series(['a_1_b','c_?'])/ \8 v0 Z0 E6 {8 ]
    # regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?% V1 V: D3 ^6 z- w* i3 `/ M
    s.str.replace('\d|\?', 'new', regex=True)
    # R0 k! o& g- V
    ; |  B( }0 Y% L% N+ H0    a_new_b
      d" h. \6 u% n: ~4 f4 m, ], c" Q/ N1      c_new  R: @# o0 G+ a4 }8 f
    dtype: object, p$ h. v8 z+ {
    1: W1 _/ r( v0 X
    2
    * o) s7 V' h; R. I2 r30 A5 e; ]3 c) U% Y2 P3 w! p
    41 {- }5 N. [6 X: s
    5; n( Z$ D& S  w% e- Q
    6* O7 V& {& K8 V0 L0 j! ^2 b7 W
    7
    ' J$ X! i* r# a1 ]: B! D, L4 p& t5 A  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):/ Q) _1 g$ ~- y

    ) w: y: }8 C3 H$ J% q/ B2 ~, us = pd.Series(['上海市黄浦区方浜中路249号'," h* ]& t: f9 q6 g+ @8 [% Z; f+ D# W" s
                    '上海市宝山区密山路5号',4 @4 f4 U( ^! i9 E0 B4 z, P3 p
                    '北京市昌平区北农路2号'])
    $ m7 a1 W6 v0 t6 B5 D2 Ipat = '(\w+市)(\w+区)(\w+路)(\d+号)'' X  M6 B' |3 x" W
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
    5 `( |7 `$ p& @! ~( ^district = {'昌平区': 'CP District',8 m$ H# J3 G$ P
                '黄浦区': 'HP District',
    - \" J0 y3 R5 r  y3 [            '宝山区': 'BS District'}1 M# z8 U1 {& x2 u& A) P/ c: w
    road = {'方浜中路': 'Mid Fangbin Road',% L6 A% F, N, g3 F, m
            '密山路': 'Mishan Road',5 y/ m) L% v7 s! S7 o" G
            '北农路': 'Beinong Road'}* K  W( w2 h( l) z$ W8 u+ x1 \
    def my_func(m):( u; G: |5 j0 P3 C+ d7 [0 ?7 U( _
        str_city = city[m.group(1)]
    5 _) H, x8 q1 a2 {+ k  X    str_district = district[m.group(2)], ~4 D  o# K  x% c
        str_road = road[m.group(3)]
    * A; b8 ]* [, m+ `9 {" U    str_no = 'No. ' + m.group(4)[:-1]
    ; r  s7 @/ t3 M    return ' '.join([str_city,
    % @: h  _7 Q" E& g4 c9 L  |% D                     str_district,
    ( ^- D( y  L& J$ r                     str_road,/ D& e9 k" S$ s) y. F9 d' y
                         str_no])% o8 v: }2 a" t! w
    s.str.replace(pat, my_func, regex=True)
    , t) H2 a3 a! l* Q& E8 j$ ^) f: y) a& p; {5 ?1 ?5 f* j
    1
    ; c5 y( b% ], v, ?- H( F! P2
    + ?. i$ U+ A- h7 m+ x3
    . p- ~5 _" b  _; w4& c. j; _1 x3 T
    5
    ) \' Q: i$ W, @8 X6
    & Z' @3 L/ z# A' a' n7 E+ `$ }/ l: G! i7* u; ^7 F, b* v( H- R
    8, O( K- C4 A$ Q: f! Y) `* Z' _
    93 _+ j4 c- a. I3 }
    10
    $ ?& O2 r6 d5 K  y11
    + P* y+ U' N- j( T- b12
      L: w; w- _) ^. s133 n% A& q% _, X4 I
    14# K0 @2 A9 Z! f2 ]5 U
    15
    & D' ~$ x  a+ a: N9 x6 q& A16$ S2 Y( [( q+ r0 e8 s
    178 X' |* U% Q- t2 s* c- Z
    18
    6 Y2 Q' ^# Q& Y0 w19
    ' Y- u2 L) a$ [201 |  ]) B8 q" d/ `
    21) Y9 N4 U( {9 a0 M1 x. r1 ~
    0    Shanghai HP District Mid Fangbin Road No. 249& m6 \2 I' O5 {: H  ]
    1           Shanghai BS District Mishan Road No. 5
    - k2 s; ^' C& B, L( N2           Beijing CP District Beinong Road No. 2
    ( @4 o) w: |. t  Gdtype: object0 W# k2 j  p/ \2 Z: E+ }
    1
    3 f& \% a! k6 l, D21 a: g2 A7 \' |' {/ ?9 ?) n
    31 y) {+ b5 A* ?" G
    4
    $ ~$ F. K* M8 z  u0 D* `- H$ m这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:, w: m+ [  s& d6 |

    4 D0 O; D6 }) N& ^: X- R2 s/ Q) I# 将各个子组进行命名( M. T+ G3 z2 b0 x
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    : u/ b2 S9 m$ i4 Y% m( |& pdef my_func(m):9 l% p# T5 A7 c+ R
        str_city = city[m.group('市名')]$ ?. a5 ]3 a5 T0 h4 R. q
        str_district = district[m.group('区名')]$ u" u1 m" ^/ d$ u
        str_road = road[m.group('路名')]$ W2 d. R# C" X3 L
        str_no = 'No. ' + m.group('编号')[:-1]. u, ]  E/ U  n
        return ' '.join([str_city,# ~1 ]* t/ s7 d2 p& e  Y
                         str_district,
    5 F% [+ y% U/ ^. T1 S                     str_road,% F( b% Y8 E5 [9 A  ]5 L
                         str_no])3 K8 s& E: k) C" K
    s.str.replace(pat, my_func, regex=True)
    , m$ a8 S# p* F: s  s& t1
    0 V+ J! m: T3 s3 U2  k% D4 M$ n: p: g, }% I7 m. ?
    34 r% v4 E( Q% t/ k/ S4 l# v% b& ~3 D
    4
    ) T/ e7 e! R  R& L$ _5! C* S: J! h' ^$ Z; r7 v3 `1 p
    6% u) T4 C$ r+ @+ ~  u2 I- j) |7 m
    7, ]4 n0 a& o5 x  I
    80 @4 v' a8 S+ L5 ]7 N+ o2 p
    9
    / ~. b" L0 v; }, C10" R' i4 C" l) ?. H) ?
    11
    ( U1 @2 h) C4 w12
    % P( @% b, l  b) D) ~: y0    Shanghai HP District Mid Fangbin Road No. 249
    3 I6 e3 w0 b  Y( y* S) b1 x( E1           Shanghai BS District Mishan Road No. 5! Y8 E6 t3 _3 }4 k2 L
    2           Beijing CP District Beinong Road No. 20 G# [' K, }1 W* m  O/ O
    dtype: object' j# l6 \  V  ~- x% w
    1
    ; g/ q- \0 l2 ~* P+ |. ~; J2
    * A5 q: H% O2 }: }9 [+ o0 H3
    $ _1 ?) J; k3 Q. v7 A8 t( @  p9 A4
    3 ?+ Y, N" N- X: C! @; Q  这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。) a: `7 R' C8 a9 ?9 h

    - X' P8 f  b" x5 `  ^7 i8.3.5 提取
    # P5 E& a! B8 e1 {& o- ]- estr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:! I. T/ |4 ~: \$ ~1 a8 \
    s.str.split('[市区路]')  p5 ^* \, l9 d/ V1 O0 g( x
    Out[43]: ( S% x4 `* E- W8 A$ ?$ s
    0    [上海, 黄浦, 方浜中, 249号]
    4 v: S! {2 J4 @2 ?1       [上海, 宝山, 密山, 5号]
    5 \% h. o4 z, `3 b' U  Z. @/ bdtype: object) c0 p- A& Y9 s1 |
    ; n- H5 v/ `# g& ?4 N6 a4 f
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    / C( q- b: \" t2 q; Vs.str.extract(pat). b4 F2 K$ Q8 p$ c7 F0 t
    Out[78]:
    ; y" Z, s  Y1 O! x3 s% p    0    1     2     3
    $ t1 b, A% o4 i0 r1 C5 G0 S0  上海市  黄浦区  方浜中路  249号: U2 ~1 T* h  ~4 o
    1  上海市  宝山区   密山路    5号
    + ~& Y: a5 E) R# m& B2 y( U% S+ @2  北京市  昌平区   北农路    2号
    $ x6 @( j3 H* C4 |' q1
    7 M2 r3 |0 C% Z1 }2" j- ], T, r+ G1 B7 Y* F+ a  z: l
    3
    + S/ ^8 X; j4 s9 j# e/ f& y5 r! l45 e1 l, }3 a  v6 U* D8 I
    5% S( g& d0 H1 A
    6
    5 p" Q& }% o* _3 A$ r( d0 ~7. i; x4 X: |. O8 |' v: f
    8
    0 W( G& {  x- |91 x8 s1 M/ A5 e3 g
    104 _3 \/ g3 U* E4 m% P+ K8 C
    11( |0 }, E7 y% H8 z0 H
    12) P* g4 R- @( T
    13& z5 M* r  v. G, y
    通过子组的命名,可以直接对新生成DataFrame的列命名:
    1 Y+ A2 M2 r$ u: i* Z+ _; i; \
    % F$ N: {+ ]% apat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    * c$ }' z9 I9 R2 u/ w! |  zs.str.extract(pat)
    & X! y6 b& s  [; h" {' @4 `Out[79]: % E6 A* `% Y$ R) s  r$ N
        市名   区名    路名    编号
    $ c. {5 q$ U. }3 o0  上海市  黄浦区  方浜中路  249号
    : U5 ?3 K8 v  M* [# H1  上海市  宝山区   密山路    5号) `  E* E$ O& ~0 M+ [
    2  北京市  昌平区   北农路    2号& O, q5 _1 s! B5 }  L- x8 E8 J
    1, [9 J, W) i, P0 C* ^
    2& X8 ]8 N7 S+ M. E  Y# T9 G% P
    3
    & w6 S0 j6 Z6 G* v2 j9 ~- a* b4: \5 r/ K: Y( s0 X0 w
    5+ M5 x8 }7 P% g) J/ g% V
    6
    & j2 y" I. W( x0 S74 ^$ k6 }5 r. A
    str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
    9 G# d; c2 O5 w9 E5 K& H+ ?s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
    + M% Y6 _7 f1 [pat = '[A|B](\d+)[T|S](\d+)'! h) ?* X# U* I5 f# r
    s.str.extractall(pat)8 s. P0 p: T- G* T
    Out[83]:
    * S' a1 a( |( i5 G1 X: E; J       0   13 w, d1 L) n' m. x9 ~) ]! V
         match         " ~2 G+ l. T/ E  T! k  K8 {% w. z
    my_A 0      135  154 R( [7 X. l6 }7 \
         1       26   54 M* |4 H8 I1 |
    my_B 0      674   2
    ) g+ S2 k+ q: [     1       25   6
    6 q+ v% T& q: {& p" |1( S& c- K( g* Y' E- f* A* L
    2
    " r7 X# ]0 a  w, y- H4 `3, O3 t- d/ l0 ?) l7 f  A
    4/ i. p) u1 L, u2 v/ B
    5
    $ O; k4 H3 }* m1 |8 }6( s, ], Z! _& Z! d% }2 A
    7
    ! X1 ?  z2 g2 {# b2 @8
    6 t# R% p$ G2 i; v( o0 R9- P% j$ ?5 m6 f/ z+ k. S
    10
    ( I! [5 Z$ z) D3 f8 x0 o  Mpat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    7 m: {) _4 e+ W9 [" ?3 z0 Is.str.extractall(pat_with_name)
    3 W/ t9 J: H  B; E7 X5 I6 Z& G1 ?Out[84]:
    % [" ]; w+ ?5 m6 x# e- C8 e  W+ g2 _           name1 name2" ?; Q* y! p  H. V2 k- O9 Q
         match            ; C" B6 M* J0 O
    my_A 0       135    15) m+ S" i- Z5 m6 u$ G, i
         1        26     5
    3 h$ l1 x9 x. {3 {& ^1 fmy_B 0       674     28 R5 a* k2 ~3 m7 k/ w: R( g, u9 Q$ ]
         1        25     6
    ; ?  W# s$ `; Z1% @3 b+ m  F7 A
    2
    / |3 i6 Y/ G" R" ]4 f3+ c3 _( y$ E( n9 Z" ?$ u
    4
    5 Q: T: g, Z: r5
    9 [8 a1 Y3 S& `& R# M6
    ! T; ^" S6 h. D' O7. B7 ^9 V! ?$ r5 Y# t: X
    8
    ! q2 s! |( ]: E5 L9
    ( A! u! D6 h3 u8 D4 Astr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。0 t* S* m4 M# @0 c' _1 @# T
    s.str.findall(pat)/ Q3 ?+ C7 S8 ~
    1
    & G! R) N2 B1 v7 Gmy_A    [(135, 15), (26, 5)]
    # N2 M7 n- l  e8 d8 ?my_B     [(674, 2), (25, 6)]! {8 n7 {1 k1 A; K3 I9 m" z/ Z
    dtype: object8 N% U) n3 ?6 u* }# c/ q( g& m
    1
    ' X9 X0 j  c$ E. t* v) q# l2
    2 ]/ z: F6 I% t0 `' G3
    # X6 x0 N- E8 ]4 Q8.4、常用字符串函数
    4 }9 ?8 E8 q9 |0 u- c- S5 B5 o  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    * N, L( R/ e# m/ B  U4 j* y
    : Y. x  X. u. r/ O& S8.4.1 字母型函数
    $ `# u/ l+ k1 \9 c/ q  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:% t( _' O; Q5 }* M* |  ]: O

    + x( Z6 _; i  {* h+ f1 X, |7 z: ~7 ^s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    - Q. z. R/ _# N, C/ N" C# P; @2 [  H
    s.str.upper()' z( p* Z" D) X2 Z! B) R5 S
    Out[87]:
    4 S# H$ G( z2 U* [: ?0                 LOWER" R4 i) N4 G9 S9 A! r7 X  G
    1              CAPITALS
    / `, B9 g/ @8 T, x- u- a5 Z2    THIS IS A SENTENCE
    ; Z% F: v% ^1 V3              SWAPCASE
    % H: i0 x5 m& I1 i9 O2 J3 hdtype: object
    " b# k2 J+ T* A; V/ ~% L: k
    - D% o' Y; c# l3 e5 A  J  js.str.lower()7 {4 x0 Y+ `! S+ A( \
    Out[88]: % h. V" l5 d5 V
    0                 lower
    $ F3 Y( v0 G  V3 f9 @% }1 }1              capitals
    ) ~5 |& i8 w* Q- t1 j& D9 V8 e+ B2    this is a sentence
    7 B" H) [' T& w4 o" \; F3              swapcase
    8 G$ R& C: v: {% \6 Udtype: object
    , k0 T1 [* a+ {# C0 A3 e9 [$ _3 {' j* r1 p# F7 [
    s.str.title()  # 首字母大写
    ! ?& g8 K. {$ q. w* qOut[89]: ( @5 M1 {/ T) W" n8 k9 ^2 A
    0                 Lower
    + o2 V( V" w& d; a1              Capitals; s) l/ h% [5 o) M9 }' z- O
    2    This Is A Sentence
    ! l9 {- e5 Y/ q* n0 V3              Swapcase
    6 n6 Q5 u7 t! X  G; w& udtype: object; M) a, J, q! V- ?7 }

    4 u. t% _; S/ Z' f! ]/ @- i7 js.str.capitalize()  # 句首大写* ]9 i6 {; N9 z
    Out[90]: ; w6 k7 n% A5 I' h% U
    0                 Lower
    3 O3 I, R- `; M# T3 D( M- }( t* C1              Capitals; Y' \0 F4 t3 X9 g1 Y8 ]
    2    This is a sentence  _: B% J8 a' ~
    3              Swapcase
    ( J- ]- I/ x1 K& z7 {& `dtype: object
    0 ?6 H7 M! b: t; c5 ?$ t: N: z2 C  S
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
    8 J" v5 c8 T2 y7 |3 X1 \' j5 yOut[91]: : A! X" g+ x& h& w
    0                 LOWER1 U, b: ]9 L5 E/ A  W/ o
    1              capitals0 u+ H" M- d* n6 ?5 z' e( p" z
    2    THIS IS A SENTENCE
    3 w8 Z8 S! h3 ^0 K( ~: ]3              sWaPcAsE
    8 @3 y5 X! Y9 `; a2 ldtype: object
    & l' B% i! w* ^3 w: f" \: L7 {( n3 K& b1 i" T* J
    s.str.casefold()  # 去除字符串中所有大小写区别0 P$ b1 n* ^5 s) [. ?. V

    7 s8 d% y9 G) W! p0                 lower
    7 Y0 ^7 _5 L3 v3 `% K1              capitals$ o$ N& W8 ?( |& p* Y, w
    2    this is a sentence: w$ ?( _- X7 p7 ]
    3              swapcase  s# X) o! Z+ b" |! f9 ^3 V

    ; {1 E7 L! d. w6 f5 {! }6 _1
    6 Q" g' q! W4 f! g+ p! `- r2
    " ^9 P/ ]% ^' q" k$ f8 Z# w7 ~31 @1 X2 ]/ a, t9 E  B* d
    4, h2 p$ k# ^  L, Z5 I! ?: Y
    5
    5 y$ D# I1 M# E% W6 Q6
    # n6 D/ P. d, F: ]71 H9 [  ]* ~  r9 P% o" k
    8- s7 T) y  w, p# H- w
    9
    $ Z9 ]3 O& x# u" [5 s# s102 J, M. h! P! l4 ~! J! `3 Y& [
    119 D* \' _2 K+ W3 d
    12
    3 }) `" B8 u" r$ j6 w13
    1 k) A. S5 k% u" U) Z, Y/ C14& T- t0 i- U% n4 Y+ r2 B+ l: C3 `- F1 R
    15
    ; J0 L' D, S! [, [4 o6 l163 _' }, K; ~, F& M8 n$ T
    17$ Q- w& j' S* p: i0 L
    18
    - b" [! E& `8 e4 m19
    " u# o$ W" Q' ], i4 N* ^* ?' V20
    % Y+ P3 ?/ Z1 v7 n0 M# j7 i! E7 e21
    " J' V" v% I2 L8 j2 D! g7 {- v  H2 m22  |: J+ v% H# e' K* X3 w! K
    23
    1 k/ d0 ~: J' D$ S24
    ; j- s3 M0 C$ Z5 V25
    4 Z, A2 U) Q) t' z9 }26
      E* }% G. v* s: A4 A; E27
    # w8 H+ J6 Z) t7 Z& p# X5 G4 w28
    * m% ^0 h3 S6 s, U( x! y& X29
    . o$ O7 n5 ], n  Z- G0 D0 T/ `30
    ( B" S+ v3 }" s31
    7 Y4 B. x) k  Y# _7 e; q; m3 `32
    0 t9 R# h) Q: j. `335 P: R, Q+ [, f8 v
    34/ N0 M- Y' A( [5 V4 e( y
    355 ~. s- {3 Q6 z$ ~  [! Z0 j0 M
    36! R7 L; T/ G0 M" ?: m
    37
    $ i, f/ L: t7 p  ?; V9 f38! U, X( ?: M- _0 r$ o5 W
    39
    0 ~9 T, {5 @+ A1 x# b0 C0 z40' u. d; Y  R  U0 K" j# a: ]
    41
    0 Q5 a! i0 ^0 a+ Q1 u; s7 r$ ~42
    ; S; p. F$ t8 K) h6 W$ k3 m( e43
    - U' @" x. o1 I: u447 p  [% r% @( L
    45
    0 n9 V7 h( Q& N7 W0 L46$ P. p+ @8 [' a) S- M+ }6 X
    47
    3 o1 W7 p5 @& ]. p. O% q5 u% w48
    8 T7 d% k) v' S% D6 @0 ~4 J8.4.2 数值型函数5 A& A+ h+ h6 H7 C% r" |/ O
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:% j7 P0 n2 z) I$ Z% x) ]( W: w
    + D8 v. B4 j+ ~' A. q1 e* `  _
    errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
    + }  s+ B' l& j  d9 w2 c3 \raise:直接报错,默认选项/ ?& Q! R: i( T: K" w
    coerce:设为缺失值
      s. R! ]0 w# H0 O; f. }" b# eignore:保持原来的字符串。
    . C+ H9 J0 d* x: l) T" ddowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
    & N4 H( f) o9 Y( x. ]s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    & O8 S3 a+ G% O- W3 y  X3 J* [" k5 k$ u8 g, l
    pd.to_numeric(s, errors='ignore')0 x, Q$ Q5 B. z4 F
    Out[93]:
    : \: p* B1 R5 ?5 |/ m0       1
    * M% i! G. Q; Q, W# }+ n1     2.2
    ' J  r" g3 S1 j, R6 ]6 f3 M2      2e6 r9 r) h) h5 @% T! ~  x
    3      ??2 h3 G9 T5 s; l% ?8 L
    4    -2.1
    4 r) W8 }" g, @, l& _5       0
    8 _* X* k; Z1 o/ ~9 V, ^4 Y4 Ydtype: object
    * y- ]  D* `2 [: T3 i; V# R
    1 U5 r. ~7 c2 Z  t' ypd.to_numeric(s, errors='coerce')
    ! U* d, E5 H9 t& ^7 f5 a  \Out[94]:
    ) n; X! T, _9 R/ \6 i0    1.05 `1 O" o2 n- b9 q+ U" _& g
    1    2.2
    + o: M0 s' }4 ^/ k1 D$ x" T2    NaN
    2 X% Y* b# p: t  O8 j& M1 y3    NaN
    " S) ?& O) g9 T& P" @: m" a4   -2.1+ P3 Y6 J1 D' T# e- B0 f
    5    0.0
    : Z$ P/ B$ U$ t. ndtype: float64* D1 d8 r7 a) |' d+ m+ K' C
    + Q& g# o( p( Y/ b! P, D1 `
    1( {6 n& K/ [. x8 F$ J
    2
    ) c( \* Q- _5 Y/ q) l" x2 K3
    ! B- }5 _+ k7 n6 P  ^* m4& {9 O* A7 p7 Y( M- L
    53 L' F: l' `6 k! G9 |
    63 v/ t: ^  ]. [) e
    7
    # y9 |/ d# m1 Q4 Z9 K- B3 ^8/ U2 \0 p; ~: W; {: [8 j
    9
    9 `$ i! Q# W1 C) o10
    9 e8 j# R3 B: K, t6 q11* X/ f. k5 h0 x
    12
    ) j* t# T; L1 u% ]1 k13
    ) z  `0 c1 B% H" _" t$ U; q14
    7 ^0 [  S. H- t$ L( X! {15
    / s- Q. q# S& ~! a. j16
    9 f; ?' p  x- c* N5 w17+ ~- i" a. e# |9 [7 S2 p7 O
    188 g$ r0 P  J1 ]* ]7 D
    19% [8 p( J) M, n: ?  n5 ^2 r1 f
    208 T3 b0 c* O/ k% B) L* R+ O
    21
    2 Z9 h2 H8 P! G7 Y" o. `  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:! E1 X$ V1 H* H' w- u9 _) H

    " F+ J0 I5 Y2 S  h1 `s[pd.to_numeric(s, errors='coerce').isna()]
    ; q' O* N" a' m" Q+ ~$ {3 A  z5 I! FOut[95]: 9 ~: E1 i. }# j  v3 a4 V+ K8 @! c
    2    2e( i$ S# b; i' ~, x- ]
    3    ??, I- [/ A- e/ V
    dtype: object
    * k1 S+ u& M1 D3 I8 j" s# y2 U18 u7 c3 [) F' I4 |! Z  b$ z
    20 k, o/ Y4 h3 N1 D& \
    3
    * {+ u5 k" J+ ]7 n5 q1 u& ^42 C7 G; T7 e0 B
    5+ A" j2 m& Q' p
    8.4.3 统计型函数0 z1 R1 c: C; ]5 J' K+ u
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:
    & |) M! s0 g: K$ ~
    ; R3 G& [# \& v0 Q9 L  _9 u; ms = pd.Series(['cat rat fat at', 'get feed sheet heat'])" O  n# H5 d9 L2 r
    ) m: f6 m, [5 j9 @3 L$ q8 d  c% m% a
    s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次/ s, n& B2 h1 ?% Q
    Out[97]:
    # T: v1 G4 j& V7 U; x5 a0 r2 W0    2
    ' q& e& [- |- t' Z1    2
    . u- V2 C" n$ Z+ p% qdtype: int64
    ; q; J% J7 b7 o9 \3 j, a' q# M; @& |6 {8 I6 S+ g2 u/ M1 s
    s.str.len()
    2 Q( s4 _# y3 S6 H2 x# K1 k1 zOut[98]:
    5 S, M; ^) a$ h0 y8 \9 t# ^0    14" c# r& Z1 j5 o+ G) \' p* l' D
    1    19
    " g0 C; D( l: F, Y; t; v0 V$ h- gdtype: int64
    % r3 [- B; q8 F/ N8 B3 V* d11 n  y/ K7 o3 D' v" n$ n9 C
    2
    & e' K  W; J& ^0 N, Z) U3* N5 V6 f6 M& b7 R. ^( R6 v
    4
    8 |. \, @3 U7 K5
    . m1 ^/ y; Z" O" \3 I3 s6
    1 {( p4 s. L. f, C8 ~0 M7& n' l& m9 s! }+ J7 c: ^4 Z. j
    8% m  M" I+ {6 {6 U' |3 }" r. w  C
    9
    ! t! C" T3 X: j/ p0 d* h% ^3 t' @10
    6 x6 Q9 W# @' D# _+ G! F! _11" m9 n6 v4 L! R2 y; f
    12& ~8 p0 k% \: w
    13
    / {+ @& {; G8 L8.4.4 格式型函数
    ( v' C$ F% e, q$ m  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。/ v$ H* Q8 W! F' ^' w, L" n; v
    . U& L" W+ y7 f$ \- n
    my_index = pd.Index([' col1', 'col2 ', ' col3 '])
    + q& B) d' c! e* H' d: |7 l7 `5 r( ^4 T
    my_index.str.strip().str.len()! A/ |* ^. f# q0 n. {# n0 \
    Out[100]: Int64Index([4, 4, 4], dtype='int64')2 z" J; ?9 \) d

    7 z% H! u& N. X( o1 d6 @- Pmy_index.str.rstrip().str.len()! d, U! s% i$ A3 Y% r9 I2 _
    Out[101]: Int64Index([5, 4, 5], dtype='int64')
    / S, t0 y2 v) L/ E
    + O8 H$ F  N5 \5 G: ~my_index.str.lstrip().str.len()
    5 l$ W3 ~& a! Y% k6 C% a) |Out[102]: Int64Index([4, 5, 5], dtype='int64')- I6 s* R" Q* m
    15 L+ S5 @, x) ]  b4 b0 S
    2* p4 L$ M1 C3 B1 n6 _& W. P/ x9 s
    3
    0 t( y% @5 w' M, {49 ~' f, Y3 S8 u( ?7 Y/ f
    53 M( O( j: L& e- e2 [+ e- V: y9 L
    6; r; A! R. G; }/ B/ C
    7
    ! N- _" |$ I: e; x+ A. g0 H86 w7 W* ~0 }* H1 J
    9
    - J! t$ x: O- f& n/ x7 l. n10
    4 a  b% Z( m  Q/ P; N" K; f  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
    ) F) W% ]% H* @8 L' H. g
    & c* V! q; Y. R/ O8 K1 P& R' d3 f5 I% Ws = pd.Series(['a','b','c'])
    7 A9 R& [5 g: t' k# V/ O4 u$ i& M# {# s* w; |* ~# e
    s.str.pad(5,'left','*')2 I* _/ H; r$ U/ s% ^
    Out[104]: % k1 y/ Y& w+ {4 z
    0    ****a- Y9 Y" G. M. E2 c0 m; O) U: z
    1    ****b
    " u1 _: R4 T/ W; f1 T1 B, N2 O2    ****c
    ! s1 l6 W( K# Wdtype: object4 N5 e! p% k8 y; H

    4 b! M+ S1 W/ V' e9 [s.str.pad(5,'right','*')% A: t- V1 V9 _: Z% X
    Out[105]: . q4 A+ @0 ]# O: A7 V" V! w4 ^
    0    a****
    + M3 d6 A: F. y4 Y1    b****
    ' z7 v& p+ R8 M& j2    c****3 G- |; F! c- o' k4 e0 p2 [
    dtype: object
    , H# u+ a- Q3 ?0 ^' f5 d- ~( a
    " f* A: F' x# w. M( S! j+ Ws.str.pad(5,'both','*')% X7 q; j5 O' W6 B
    Out[106]: ; I' h1 L! J  b/ b8 Z
    0    **a**
    ) b4 J. P; L7 G$ w6 `: d1    **b**
    + \7 t, D4 b2 N" O2    **c**7 e" e  B! x  q4 f" l. ^
    dtype: object
    1 l3 L" ?. V: x8 v/ L6 v1 K- w* l9 \$ m% M9 C$ g+ d
    1
    + B8 P- V7 H$ c8 T2
    $ d0 l. q. d' M3
    - @: n1 m; \+ t% ^' i' E4
    3 c2 l/ _$ |( q- c  |5$ E. r7 g1 ^% Q* C5 h1 F
    6
    , p* c, k0 d7 K/ x" ~/ k0 s7' X  x* I+ M5 k  ^! |, \3 B. e
    8& [: }* O3 J+ w$ d: z+ k, q7 f
    9. ^5 i. H$ M" M) R
    10; U, `7 W$ P/ A. e& `, O
    11
    + f6 R0 N/ S. y* I12
    / o& C9 A* f# c5 z" U0 \13- @% Z7 K* e  V* l  i# D' u
    14
    2 i6 y& W, s* k! E6 x' b15
    8 E' U& E+ A6 T/ j166 Z) z7 W. L: i& @( o  D1 H( E0 o
    179 n: ?- m: W4 [1 }$ {1 C- b6 Q
    18  s4 G) \4 P, L
    19
    # B3 G8 G! O& Z0 E" I20& B7 O6 l( o3 \3 J  q
    21& ^& B0 B/ E) c) E" V
    22
    5 o4 `; d  ]! `# H, Q  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:- Z% k- c4 j' p: B/ f3 Z

    3 B- s; ~# _4 U7 Ks.str.rjust(5, '*')1 `1 t$ ~, l( F% I5 d% t
    Out[107]:
    , K$ O+ F, Z+ T4 `0    ****a
    $ {% h, @; |5 t/ k9 L) R1    ****b
    , V  Q8 Y8 E* t2    ****c
    - N( {7 e6 i* d4 Odtype: object8 o; ]: p( y4 o' W$ w; I8 G- V
    : \2 j' ?- C* p4 @: C, }' |5 w4 G
    s.str.ljust(5, '*')  u) x; h8 J- C+ \9 d( W
    Out[108]: 5 j$ h# M1 |$ Y) A- t
    0    a****2 v( s0 S& A9 v( e3 L- }9 i
    1    b****
    ) y4 G+ S3 ?/ v( t8 ]) F, Y2    c****( a( ]; z( B2 j& @4 Y6 X/ e' G
    dtype: object
    . C1 ]4 w7 y: T
    & {; E8 S& N6 Y2 k1 {/ f7 F! H. u; ls.str.center(5, '*'), ^7 F* d$ s" \! ]8 r; D
    Out[109]:
    0 U, y! u  l. P: r/ u" L7 q1 A0    **a**3 |9 W# g& f( U
    1    **b**8 T8 D% L. ^+ d) X
    2    **c**
    $ C+ Z$ W- a; R0 C+ \5 _3 ]6 Qdtype: object
    ) L( J' [# C1 }! H; _
    ; F2 E0 b0 @# ^1
    8 j6 t/ x, \4 h. n& x$ E2
    . O! S$ [  M* J6 ~, x6 z- p7 V3
    5 |$ o6 y8 ^6 a5 z" p4' ]2 F/ ~  q: {9 D0 k
    5
    1 V. j* H% c# j1 Q6
    5 j, R0 _5 L! |9 S5 ^: ]5 O7
    4 A- N% o# e0 q8, U8 s0 n6 u1 H; B# {
    9: y0 j4 P0 P& _1 V% g$ r# m/ H/ r
    10
    4 R2 ]8 p6 f  r4 f" i, G8 z11) l, I2 q3 `) s! }& ~
    126 }- H0 R- h- G8 I( k& c0 ~$ a
    135 D/ v! N5 ^4 S  S- Y/ n8 U6 T- S
    140 J. c8 }, i7 v/ d6 |/ M
    15
    : ^4 N: V2 ?2 ^2 f2 `$ L; g16$ F  a; @5 I" y" B6 x. X& ?5 z
    17  X4 X1 p$ e. B* ^
    18
    ) K2 J8 B, D3 b' W% R193 ?9 n' [  P" A5 W/ [
    20
    ) p% Z: d' v; R# G. e4 w  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
    - g* m& S* {' }/ e- P( B. G; k3 R7 F( n- Z8 l* H
    s = pd.Series([7, 155, 303000]).astype('string')
    , M% {5 [/ S1 z( Y( E
    , E4 k3 ~& X- p$ u/ c9 w- V3 K9 Ds.str.pad(6,'left','0')
    ' r; F% s  [5 {& Z6 WOut[111]: ! \( h" O' ?1 J2 x
    0    0000079 `( k0 O  I: C+ U- s3 n& E  p
    1    000155* D0 i. M  P& }: o1 a0 b- L
    2    303000/ H  d0 @3 V& U  f3 D
    dtype: string/ U: s1 J- J2 z: @: t
    + f1 x3 l( Q2 h! A
    s.str.rjust(6,'0')+ g) \: i' ?# h
    Out[112]:
    5 e% f2 x; W' |4 N, y' h4 a0    000007$ r! I+ T' v* Y/ i3 J5 `( y
    1    000155
    , q% @, }9 `8 p/ d6 g. h) B& F2    303000
    : a& h7 ?) g( y4 B" `dtype: string
    7 w' _9 d# E) @4 Q, I& M/ v0 b, M4 K. [% w
    s.str.zfill(6)" r9 ]+ L& O" z1 ]' R0 x2 r6 ^
    Out[113]: ! l6 ?1 Z  t7 O9 b& e
    0    000007. L, B* I' D1 Z1 [3 |" b  r9 ?+ M
    1    000155
    / g% G6 ?3 b. p1 H! V2    303000) U+ M  }2 z6 e
    dtype: string
    ! }4 S- F7 h( B9 N8 T4 r7 |7 \- s8 _7 ]: H3 v, G: m; F9 k$ v
    1
    - N% ^4 i- w' G! p# z4 ^2
    8 O; G( {" n) g3 \7 `: P3
    ) w8 k; l" }: \" k$ M4
    " h  K) b) p( A% E1 x5
    " l9 j( o9 {/ X! O+ ?7 f2 t67 W  }6 V3 o9 y( c
    70 e, \4 e8 h) D/ O$ `1 P
    8
    7 ~+ b# `* S* Z; J% C4 O: C9
    # Q7 s& ^3 l5 x10
    % k0 v" p) a: |/ M11
    0 b5 B' V$ S0 |5 ?! n' [* U( N126 a. h$ e6 x( T% L" K
    13
    0 v* }( X: S; r! o8 B: C14
    " P4 b1 Z" `; {5 f8 l$ w158 U9 m( J& r$ h$ a  a# @
    16+ M  _# F4 U3 s0 _% R
    17/ E1 K& O" s- j& A* Q' h  j3 ^
    18
    - i! n6 ]9 A( N: Y19
    / Q; l5 X. ~1 N* {; @20
    % b% e  j9 U) e- T0 J9 h4 L, x- E7 E21
    : ?4 g* s! P4 t9 O0 K22
    ' I% {) B/ a3 ^* I% c7 G8.5 练习
    / R7 Q* N8 U  B. R. s3 qEx1:房屋信息数据集
    ( u( ?9 \! ?; d% F现有一份房屋信息数据集如下:+ _( |: i7 Z& p0 V3 Q0 v) G6 |! C8 U

    9 X( {, q2 w% _( P) M/ n5 h+ Vdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
    # F3 B0 a9 |7 G4 {df.head(3)  w+ |; Q/ F# h5 A' k# |# c* L
    Out[115]:
    3 I# L; c: b0 c      floor    year    area price
    / r7 K% B9 h9 @1 ^* b1 A) T4 g0   高层(共6层)  1986年建  58.23㎡  155万
    , X, {( x. M8 T( h1  中层(共20层)  2020年建     88㎡  155万
    % [) _# s8 _7 m; x6 T2  低层(共28层)  2010年建  89.33㎡  365万8 l3 q& D/ g. p4 ]3 J+ v
    1- }/ P2 t  R# c/ y
    2
    ) C, Y8 i9 z4 m+ p3 z3* U' U: n/ D' w2 `' m3 J, c
    47 K0 f  \" S6 m8 e9 U( s2 u+ |; B1 f
    5
    & O/ W' Z9 `0 {6
    9 d. q4 |& d! i$ Y+ H" B- ?5 e$ O1 I74 g( ~9 w) z8 R9 A5 |
    将year列改为整数年份存储。, w3 q4 |. B8 B* f  U
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    , e& v3 t/ G0 \/ m! O2 C$ X计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
    & _8 W- T2 N% v4 C6 l: m2 }将year列改为整数年份存储。% V9 S" _; L' W9 m
    """
    ! F4 u% r+ o; e整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。9 y% I! r) |5 y
    注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
    % L& u4 g7 _$ y; X3 }& c+ k转成int后,序列还有缺失值所以,还是变成了object。
    $ s- O6 \9 o0 b/ |而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
    1 C: y, [( n* {/ V"""
    # V5 ^2 g" ]& z8 P) zdf = df.convert_dtypes()
    . E# y0 ?( g0 H6 ydf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')0 Y2 B! q2 L' ^# g# t
    df.loc[df.year.notna()]['year'].head()
    8 p7 F9 X  y2 W& B6 A: ^1 Q
    & Y6 A# M1 n# j' s0        1986% k+ f7 C% u8 ]& C
    1        20204 a: V* U  [% G3 I  J/ |
    2        2010/ A& k, U; G) j( J3 A0 \0 l
    3        2014* R/ T* g2 S5 [# T' ]. x6 s
    4        2015
    # ]* K; b7 q; _% rName: year, Length: 12850, dtype: Int64+ u, v# J7 F: v8 c2 Q0 F& c
    ; e1 Q- A7 M8 d+ r, l3 f
    18 e2 \6 t( b+ J" B# `* K3 s
    2
    ! @4 S0 Z8 G6 y% E3 T* h3
    ' y$ _! }8 U- b; E/ b/ n9 n. P4
    ! O2 n" D" {2 y9 x' N5
    # O1 g4 N1 Z3 @9 g6$ I  d; k# i1 v5 a* S' V: k
    76 O' r' |4 b; p) S( ~
    82 u* s" m& x! h; \- ?6 ]
    9
    1 X: P9 F9 M9 `( ^) Z4 l& a" @107 \& X( J7 Z5 R* t3 V. g* f
    11: y1 m3 V. J& T% m* e
    126 [6 F: v9 f2 d# c
    13. X. F; P, g# H' o
    14
    . J8 Y- U2 \9 h8 y# g1 y15
    " i3 |. r' x# Q16# y, g9 B1 e$ c' h! R
    参考答案:
    - c' K- G5 r& v& g
    : S! C! u) y7 f5 q9 t不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
    ; J9 `1 L# c3 _8 ~; B
    % ~8 K! U' r3 O9 u) Z: tdf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') . b- k6 h, v9 {
    df.loc[df.year.notna()]['year']8 r9 e) U; o* R" _& J, S: a4 m9 M
    1
    ' f7 L/ ?2 D7 k0 g23 s# T) q( J/ J& S! d
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    & n$ G- S- o1 k1 y( Rpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
    6 K! J) _5 U) G2 ]) L% d( v7 k2 Hdf2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
    : o# V' _: N# s; Hdf=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型
    2 ?; L# f; `1 h% S9 s  adf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              ' H6 r1 j5 @7 F6 v1 l: r
    df=df[['Level','Highest','year','area','price']]* |1 a: v  r& L% o
    df.head()
    % d0 Q4 k1 ~- N- U
    5 E5 i5 i4 ~! T0 ~   Level  Highest        year        area        price" d7 w* m  r' m& z, l% A
    0        高层                6                1986        58.23㎡        155万+ I7 @4 M& G4 I4 N4 {
    1        中层                20                2020        88㎡        155万2 M. g$ O" f. l$ `# y9 h/ y  j
    2        低层                28                2010        89.33㎡        365万# f  {3 [/ k/ ^" c  Y( k
    3        低层                20                2014        82㎡        308万
    8 q' H& g- z+ [) m* f8 s+ I6 ^# H4        高层                1                2015        98㎡        117万
    $ v, x1 N1 ]: u' V5 H- H12 y% h+ Q' o2 k; f/ q( K5 K
    2: M, L+ S4 S" ?: m+ e8 Q! Z; e
    3
    7 Q8 Q2 s' W6 c/ I9 ]8 V' I4
    - R* Y- U0 `! g; r& b8 ~! {50 S0 A- S9 J: v% B* N* J/ s
    6  l2 _2 F+ v/ L" y
    7( u( E) [% C3 O5 L( `7 X" W, Q
    8
    $ A4 k" N5 u0 g2 B0 V1 B  |/ y: Q9
    " {6 C( L3 h+ Y, _; ~1 s10
    * d3 j5 v. C7 c% f+ I* A11
    8 p+ I" b" P% M6 u. c. X12
    : @- T& t! F& L5 K. C6 s4 h13
    6 X& @- N# C4 _# x! m8 O# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了3 b- d& J: a  K# P4 o. F
    pat = '(\w层)(共(\d+)层)'; R) c3 L4 a# {) G; C, E& `8 W
    new_cols = df.floor.str.extract(pat).rename(
    ; j  M1 ~9 x: J" G6 {* D                    columns={0:'Level', 1:'Highest'})
    9 s$ [7 X' o4 b& o. s- T9 v& s& e) E: k7 }+ x, Z2 t
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)( y/ O4 @) r' w* G8 E- Q, D
    df.head(3), b& p# ?$ T8 z6 S
    % e6 P4 L/ f3 |8 @' M2 v8 v- _  h/ Q
    Out[163]:
    9 ]) W6 `& J( t3 Y2 k4 u& z   year    area price    Level Highest
    : M5 o, C; R- E( b* [. m. j2 n0  1986  58.23㎡  155万    高层       6
    7 L2 j9 a0 c9 Q6 q, Z, l( ?5 j1  2020     88㎡  155万    中层      20
    5 u- l2 y8 K; n2  2010  89.33㎡  365万    低层      28' r. E3 J+ i; |. j# Q3 z
    10 t0 `& }% z. ~' V! H$ x' l
    2
    - @7 c2 H" r) R1 b- z# Q5 K4 a' ~( N38 {% T; `7 I/ w& {  s6 A
    4# h2 F0 k+ P' W9 w: V* [% y+ f
    5! g, x' f/ ]# G
    6" A6 |* z+ ?% h8 Y
    7
    3 k! }" q( A& v. s9 A8
    , `: O/ r( a. a5 h& S9
    + N, s- _& a' d- w10
    " t$ a" C" ]- T4 W6 A2 I# D& F11+ e' E) b' {  t+ m, Q: U3 r
    12
    7 y8 K: U6 n) p5 E8 Y13  `( a7 r- O7 u$ A: g8 d* z: D2 c
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    ) u$ N9 t/ D/ \6 B9 a& r, Q4 U5 V"""
    ( y) h6 {2 I+ t: vstr.findall返回的结果都是列表,只能用apply取值去掉列表形式
    3 X# V( t3 C$ d$ s) n" Q参考答案用pd.to_numeric(df.area.str[:-1])更简洁
    8 E4 O% H1 N& @5 Z0 ?8 w; Q3 p由于area和price都没有缺失值,所以可以直接转类型8 ~- s+ ?! v  A. p1 B3 S% V/ v+ Y
    """  r' J0 p9 z# x  T
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
    % U9 D! I3 |& K/ p# ]. rdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
    7 O# j) o5 t) h& j# rdf.eval('avg_price=10000*new_price/new_area',inplace=True)& \4 I+ M8 O5 I2 \9 o$ d7 x3 I( v
    # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
    0 V2 }# }" V  Z: r# 最后数字+元/平米写法更简单
    : A( r& e6 C8 p+ k( a7 Gdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米') _) l0 b- p7 w
    del df['new_area'],df['new_price']3 \* F" q5 G1 Y$ z* h4 c1 |
    df.head()
    / L* F2 o8 h( B! s! {. I$ c
    6 j' D3 g4 T7 r; a, J! q! Z! \; ~# E   Level        Highest        year        area        price        avg_price
    8 w3 |, d/ T: C* h- Z, Z0        高层                        6        1986        58.23㎡        155万        26618元/平米
    . F1 G+ n  F- P1        中层                        20        2020        88㎡        155万        17613元/平米! U+ X4 N+ a- K* G* V1 z* \
    2        低层                        28        2010        89.33㎡        365万        40859元/平米9 A/ n# P! B5 u- R: Z
    3        低层                        20        2014        82㎡        308万        37560元/平米
    . ?9 E% U7 E! a# B6 l+ u4        高层                        1        2015        98㎡        117万        11938元/平米
    : E& Q1 C- C! \: H9 B/ b" r
    / n' p0 s) m* S9 H8 R# x1
    , B4 M; W- O+ {' U0 n26 F2 y8 n, E% v2 c
    3
    " t" o$ s) \5 h- {4
    2 b# ~+ J: H) O5( ~" `& P0 R0 }, p
    6$ G5 @0 j6 l" v+ C- r! H
    7
    * @! a6 m) i: r6 s7 H+ [1 B8
    ! N: m  P% o, V8 ~- m/ F/ \9
    8 n- W! A/ @* n106 T( R$ ^% q) ~' D. d; L9 f  t
    116 j  M0 X0 |. W7 C7 v  H6 O
    12
    , s# j$ w$ E. p6 \9 O: ]# V2 f136 P+ G8 [* A; z: ^1 Y% F
    14
    1 U6 f# X% }) K: }: K150 u) K5 ?% F% X0 g% t
    162 H9 Q' Z0 c# V2 ?; O
    17% v  }* Y1 J; b- _2 [- e7 A
    18
    , `  t! O! ^5 t+ E19
    & Y/ b/ E9 L- D2 [0 y2 |: {- r20& u, K! T9 g4 F
    # 参考答案. {  V7 ~1 e  I8 a
    s_area = pd.to_numeric(df.area.str[:-1])
    9 }+ ^7 K# b3 w0 ts_price = pd.to_numeric(df.price.str[:-1])* D: f8 U* e" Y' e" R) ]% o2 z3 k; [+ e
    df['avg_price'] = ((s_price/s_area)*10000).astype(
    ( l  d; b) g! G8 u9 [: g9 I$ F0 |                    'int').astype('string') + '元/平米'7 I, @8 w! W3 _" t+ v
    " S, U  c+ }( U' k
    df.head(3)2 o2 P5 r. g6 |
    Out[167]: ! n3 V0 Z" _- w
       year    area   price   Level Highest  avg_price
    : x* S4 |* q; u5 A3 x0  1986  58.23㎡  155万    高层     6          26618元/平米! o7 E9 m; c7 @4 W3 V7 p/ p" |# j
    1  2020     88㎡  155万    中层     20          17613元/平米* d2 n) P2 P7 q" p1 t3 C: R( y+ K
    2  2010  89.33㎡  365万    低层     28          40859元/平米$ Q  J! M! ^) O2 [# G' D$ g
    1* ^' P+ \4 d! O2 `" d( c+ v& h8 w8 v
    29 j8 E( y; S. q2 ]
    3
    9 P6 o: M6 t  v- a4
    % P$ r' T, d- f, Q2 V9 Q5; j" m5 S  x3 |! B, g
    69 Q7 |! i! m% Q9 j& ~. n1 E
    70 M% R; H  e2 r9 z
    8
    ! ?' ~3 M& k1 o. y- Z, [9, `4 R! T! p/ N/ |0 o1 @
    10! \6 x& O8 \& t0 e
    11
    , D3 j8 u! B; y4 U# L12: `' {' |0 h/ ]/ O2 r  a! p8 M$ s
    Ex2:《权力的游戏》剧本数据集
    3 j! `: H* C9 A) z# T* }现有一份权力的游戏剧本数据集如下:. p0 x% D6 Z% r9 @
      }8 Z* Y) D: }) a
    df = pd.read_csv('../data/script.csv')1 f2 k1 p2 p* \/ O  R, u$ Z
    df.head(3)
      R3 m0 {7 o- Z( n0 C' @: ~7 V$ U# A
    Out[115]: + v+ o6 c" j& Q. V0 H$ Z
    Out[117]:
    ' k. k5 G" J- r7 i- t) {  Release Date    Season   Episode      Episode Title          Name                                           Sentence
    # _! [8 \0 q. e( h$ K. Z0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    & D: P" J, l1 t8 y1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...
    1 Z8 z* U1 z" P5 p8 \. I2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce + F$ y$ _4 i) p! A' ?
    1
    0 A9 s, j, T: N, [5 G1 U. `2! j8 h4 J! l4 n. S
    3
    : p4 u; ]# A: @6 E2 }$ `4 r4( m, F# y/ {/ G9 H3 u
    5
    3 ?( m. y: F; r1 _7 j6' J) [: Z( G$ Y3 r
    7, ?5 n& `! j5 n' ^
    86 X* A% J% Q1 l
    9
    4 Y' W1 t; Z& B$ a1 B计算每一个Episode的台词条数。$ Z/ r1 Z1 s" e: t% T6 }
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    6 X0 n9 |$ ?8 A( L; m7 t5 O# R若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    , X' }4 b$ H; y" D* a; U计算每一个Episode的台词条数。; E" B, Z: q# ?7 v7 z3 b: F4 G
    df.columns =df.columns.str.strip() #  列名中有空格
      L% K* Y  L6 s: Y: {* p" Mdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
    & ~& H( x( ]$ K0 c; {$ h8 e
    7 R- i5 y, r1 O5 n. l3 [  Z; b8 d3 xseason    Episode  
    : [8 i" T% r' Y, A7 }$ ESeason 7  Episode 5    505
    7 p( K" X3 {" g! G* `* dSeason 3  Episode 2    480
    6 T3 P6 {' E) |- H$ QSeason 4  Episode 1    4755 d0 i0 H5 X, a5 k
    Season 3  Episode 5    4401 o! w& |  N3 N' H' X7 Q! b( t
    Season 2  Episode 2    432
      f# t! Q8 O( E" i! b0 [3 k2 ?1
    : {+ w8 \7 c" `$ J2
    ; \0 ^  ]/ x" c! n* w# C$ L3
    # D! |2 U1 O) }; b% D4
    & A7 ?4 h% i# I* d- b55 l# T8 i( N) c6 O% R6 D
    6# G4 C5 b0 s8 B$ |% q! Y
    7; d6 |6 p# Q3 ^- R. R: l
    8
    ! ~, J% k6 W8 |5 s9 t! Q4 R9  f1 E3 P  v3 l. h. d& X2 a
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。  S( z* {, [8 `; i3 `0 d
    # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
    - f+ @! r% }4 O/ ~) idf['len_words']=df['Sentence'].str.count(r' ')+1
    & V. E+ W  j( jdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()$ n9 d: d" q8 ^  I
    ! C3 _( }7 w+ A5 U
    Name
    7 ?8 [2 b3 b8 X  k. }1 qmale singer          109.000000
    2 P, d* y5 E8 V' ?  q* P' eslave owner           77.0000008 A! w; E) I2 z6 y
    manderly              62.000000
    , q. T7 w; ]) ~# R" n( U" ~+ Nlollys stokeworth     62.000000
    2 g- ~% E3 r7 l. {- Y: @' edothraki matron       56.666667; N7 V; D4 p7 N  S; C. D, |9 H
    Name: len_words, dtype: float64
    2 [* x8 d8 g! ?* g7 t; m5 f0 }1/ N$ e. z/ m+ q6 N
    2) J% i: T1 R: {' X( q
    32 f  ?1 d7 H* R+ r3 ^
    4
    $ |9 ^2 c* c: Y3 h! T* x5
    4 Z4 S+ H: J; P' v: O6
    " t3 C: g0 o: ?4 ~# g3 n) W% D7
    / {0 `/ k- Q! D& [& {8
    & k4 Z- C, {6 ?( }" F- S99 B  a' a9 k2 W- _3 L0 b
    10
      _6 J" L: k, f11
    % X, z7 R9 z3 d' l, I+ ~+ @+ I% d若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。! K- R. V5 \+ j6 Q8 O. Q
    df['Sentence'].str.count(r'\?') #  计算每人提问数
    9 L# P3 y8 W( Y- U6 P$ e* s) Hls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填01 I# [) {5 Z# h2 x# |6 M
    del ls[23911] # 末行删去7 j5 [' I9 G) k; W
    df['len_questions']=ls
    : Q  v! I) X$ b/ ?- k( jdf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()) i. d* e+ V  Q# y+ Z

    " M! i+ F' d2 d, n$ ZName- h1 D( n, H' J5 ~5 J
    tyrion lannister    527/ ]6 q/ k. ]6 B8 A9 l
    jon snow            374
    ' m) y1 N" C% |! o. Y$ mjaime lannister     283
    , q, S. F2 s" C1 _) y* |arya stark          265
    ( P2 g0 w- h9 V7 y( E) [# |5 A: @$ Zcersei lannister    2466 ?8 y( ~2 {4 t4 S) x+ `0 O
    Name: len_questions, dtype: int649 m# A  Z7 ~4 B) @. e
    % m* @( |- {3 s$ R3 |
    # 参考答案
    + S- @1 a! j% D1 d; ps = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
    - F2 _8 U# e5 c$ ~3 V$ D) Us.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
    & I" |8 r7 |; ~; C& ?$ [$ m* Q2 E+ }( R" |* q
    1
      A: E1 V' ^6 J2- P7 H: v* R9 _! v1 S
    3
    $ ?( n  G( p7 r6 E# e$ ^  C& g4
    3 Z/ I/ z( U" Z8 Q% U. ^5
    . T' I: e1 ?5 g6 R' ^# \' r6' @* u! r8 T+ [9 M1 m% I( P- ?
    7' j5 _3 t6 d/ ~5 T3 ~( {
    8, u* u% J1 K& O2 U- B1 m1 @5 E
    9) {1 x1 T9 X/ I( f
    10
    ) a) F' [: [& E, L1 J11
    1 i: p/ x7 t, x! a7 V! I12
    ; h( u/ M5 t" F; u" [13
    0 [9 g" L6 b. B4 b  [% A14# _* {- O- L( c* v$ g; ~+ O$ N% L
    15  X$ w! L3 t& w- y' y* q& v! R
    162 x& [. a$ Z/ K) T8 p
    17  b) z8 h$ q# B6 `  @; e, X2 D/ }" L
    第九章 分类数据
    4 _* _, I6 A. S+ cimport numpy as np' a8 h. n0 r6 }
    import pandas as pd! j4 S5 ?; _! ?1 p8 s
    17 W4 g* f0 T& }! W0 s0 B
    23 _& i* A* T, {( @8 d6 `$ w
    9.1 cat对象
    ( P2 K' n; f, Q8 p; P) C" J4 F9.1.1 cat对象的属性2 ]+ b  B9 l" p
      在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。0 b6 E! l9 j: B3 `
    3 r- T' W8 E; ^$ \% q7 y9 ]. s
    df = pd.read_csv('data/learn_pandas.csv',( ]& i" [  E6 F: Q- b
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    . |7 |+ F! e: ~$ Z- K, {s = df.Grade.astype('category')
    ( P, Y2 q9 I' A' P. H
    ; O; h  \" h6 h0 K* `s.head()* Q  b/ @7 V  g: h( @
    Out[5]: % B" B4 o; Y5 y8 a) T- V) {
    0     Freshman
    $ m2 R. X2 E) f  b4 j% S1     Freshman/ [3 O: G! {" X: _
    2       Senior6 I% f* q$ Z% D0 U; K/ ?
    3    Sophomore
    1 M8 E0 b: Z# m* J5 a4    Sophomore
    8 q  g; ^. }5 W% |7 KName: Grade, dtype: category1 W$ a  a( _% k5 \) }% k2 q* a
    Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']$ s9 `/ A1 V) a; B1 m8 k" q, \0 _
    1
    0 Q- i6 o5 V2 v, e9 m3 M  j2
    ' G5 M% P" K8 F& ^$ l3  z% V3 [  V# s7 q. c4 Q% [- p
    4, r) ]+ G7 C, ]! I" ?5 |8 q
    5
    & o- `1 f& ~- U6
    , g9 |+ B4 T! W7) ^/ m4 Q1 Z- H; |% ?5 W
    8/ s- O, x$ ^* z+ P3 q! |  {# Z
    9- k$ [7 F; @' L. y# y
    10$ H; p% e6 ]% z1 E' y* P) U! |
    11
    / U6 ]2 Y$ w9 w0 j+ I12
    " _3 s% y( s9 `$ D13) ?8 v! g) a( X- S- a. H
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
    5 y0 f: c# t/ U: s! J5 L6 P) D
    # H1 F5 s9 G2 u: Vs.cat
    8 O- W: ~* x  Q4 k% ~Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
    ! l  W& z/ K8 g7 o8 O; G- @1
    : N4 M% q# u5 h* L+ E! ]: {2
    , g' A  X# L- T, o7 v3 F$ ^cat的属性:) w4 K4 _( K: ]' h* U2 X5 G

    9 T" @5 N9 [/ n9 f+ U9 t5 X: X4 [: z+ _cat.categories:查看类别的本身,它以Index类型存储
    ; `# ^5 j/ r$ F9 Hcat.ordered:类别是否有序
      k+ ^. F; e0 F! J9 R4 ncat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序6 h" j6 w0 s% g8 d! D
    s.cat.categories9 g, B2 E7 B$ p
    Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')0 ~7 K, k- L- M+ j& b8 I6 s
    3 j2 @4 T7 O, e/ H0 j
    s.cat.ordered
    " ]  O& ]0 s& z% f6 lOut[8]: False
    - ~: x6 r0 j1 H+ o
    5 H# w% F: }5 Us.cat.codes.head()0 e) W, \: I1 M/ O& W7 ^
    Out[9]:
    - G6 N3 x9 q; K/ p6 H0    0
      X7 T% x. S3 h9 o7 O7 w1    09 o) b- K$ y1 W0 F6 P
    2    2
    - Z9 H+ @# \; B& |2 x( Y3    3" e) @9 d# R! {3 c: H& C! F. @
    4    31 n  v3 E$ \# i+ c9 [
    dtype: int8
    ( g4 z9 w. m' `5 c( E# O! b3 W/ d1 w1
    7 g" n8 V$ \; Z- ]" s& l; Q2. h$ F. _1 a; ?, x& p
    38 |5 t; y# {! H
    4
    $ y; h& ?+ C# w" n* z7 F" i1 v# H5! _# j0 A' U" `+ N
    6
    1 R3 e; f' d$ s; A! N1 F' G7
    5 I$ i5 V- |  [# {9 I& O5 v6 x8
    + J( s6 M" T) u# y* w/ e98 x- _; x1 y- x9 o; J( Q* {: G4 c
    100 y2 w# |+ A1 A' H/ j  {
    11% ]4 w! U2 P# r* G
    12* ^% h4 M9 `1 F
    13
    / F& N2 n+ h) }" M+ y14
    0 V0 c" w9 r# M* v. U. B9.1.2 类别的增加、删除和修改) q' I+ D$ c0 ]! W: w8 V4 |$ ?
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?* B: z' K% d  W- i( ?, p
    8 d+ s& K6 x2 M, L
    【NOTE】类别不得直接修改+ t4 x1 K9 O7 t( _7 g
    在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
    % y/ t; ?' `! c8 v2 i- d0 E, q9 x+ d
    add_categories:增加类别* F" G4 H( B  m6 z
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
    : P8 b; f: J% ps.cat.categories8 _4 `5 v6 K! d' Q; q) n
    . [/ `! ~! `  f. n0 n- H* q
    Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    ' S7 [5 @: s) o17 f+ }  Y/ K  j* p( A
    26 E# _7 V; R: K  e0 l. `
    3: j9 [6 T% P5 h$ U# l( t( b  u
    4
    ; ~$ J' i) D8 n4 \% w% F# Z6 premove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。2 {; h7 U3 @! X2 z$ W, |/ O1 A! f' ]
    s = s.cat.remove_categories('Freshman'): `4 ~: A* Q. O- Y2 q. J4 H/ v7 u0 l

    . U  R: k1 y7 G0 A. As.cat.categories' I% P* I  Z9 t& R1 k4 a
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    ! G" I7 a' @/ X( Z  s
    , D, P- r1 T2 O2 \6 J9 N) A$ ss.head()
      X' f7 s+ q' O% `( @# [0 p4 _' FOut[14]:
    " o: _( r' M6 s# F5 D, j0          NaN% r6 ]; G2 V1 W$ F: t6 i( x9 P
    1          NaN9 E% J4 j- A  B  C6 Q
    2       Senior
    0 M2 t7 N; H7 I3 O3    Sophomore
    0 g; {; w4 X$ q4    Sophomore
    . F0 H' l0 f) EName: Grade, dtype: category
    # e9 R, c, E0 `. u) C1 A- a7 hCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    $ Y2 g& }. q6 D- f  p1  w3 O2 y0 Q0 y8 @8 J% \& ]: }
    2" ~" I  j3 t* r# K. t  L5 M  B/ u) g
    3
      ]3 d$ a  L! f7 g3 b- Z) w4* m. I. E+ g- L" S- b
    5
    ' H3 {$ \- O- s! y* N6
    . e# P. r; {0 d6 B) v1 w) j$ b0 ?: t7/ s0 m1 u& z4 ], b( b- v
    8
    - r# d: e% ]6 T' y, Y" Y1 K' x7 o8 t' Z9
    $ p- j+ i5 H1 A4 i: A' B10
    # t+ a% M. o  r119 @: K( N; p7 ?( ~/ h
    12- G$ c' [* n' t" `1 j6 ?) L3 `9 y' l
    13% c2 V; K/ e6 v) u6 [4 T5 W; [
    14" a8 G- `. \$ \, v2 t$ k; O4 T! L
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。. j% [$ S- `8 Y  U# I2 [
    s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
    & u# l* U- {2 V; s: x- ]9 |s.cat.categories
    + a2 h2 S' w- ^. \/ B9 _& T3 jOut[16]: Index(['Sophomore', 'PhD'], dtype='object')5 k- R: f" g9 w$ B
    3 x( q8 b1 n! A2 w" x
    s.head(). Y3 V! B5 `! {; v/ y1 o
    Out[17]:
    6 Y8 m+ |) @  V+ c7 v/ M# y6 t( @* Y. {0          NaN
    " H4 s  R5 P# K) l6 L1          NaN2 ?! M7 N" C: ^; \$ ]$ ?
    2          NaN
    " p5 y* R1 t; X  l3 R' o3    Sophomore; W0 E4 z  t. L0 x1 M4 Z1 c
    4    Sophomore/ @# q% ^* e' _8 M8 A
    Name: Grade, dtype: category+ D1 v$ ^* R: u% W
    Categories (2, object): ['Sophomore', 'PhD']
    7 V' j, Q' Z9 R" C0 e1 {( R1' I7 R. \- W% c0 g, N% `4 M
    28 t+ A) I; a- M, C
    3
    ) g5 M& W# O8 v4 D+ O8 D) q5 b+ o4
    + l& {( k: q% |; }# H9 x& \6 ?  j* R5% m6 _0 H9 u9 E: Z
    65 \9 C% y; N0 t# o
    7
    % ?; Y0 D! ?" t9 J: p, \8
      z8 Q  f8 i0 `# M$ z3 b91 U! `( x$ C. D: F. F1 q
    10. S$ Z) j# l( L, ?; b* \
    117 b- t- l' Z) d" M
    12
    ( T7 B3 k$ g( q) ~9 {; {" X, H" W& n13
    4 t- q7 d' o7 q. q, jremove_unused_categories:删除未出现在序列中的类别) K( u$ R* g3 h' o
    s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    8 N+ Z5 J' k3 X& E( gs.cat.categories
    : j/ U. ?$ ]6 H9 `. }7 }$ C% s. s: K( b" s" e8 X
    Index(['Sophomore'], dtype='object')+ R# {+ x# p" C3 m# L# @" W! o
    1( ^. c2 O8 f( Z( Z! y
    27 a) C& h" V# M: G, O. m
    31 n4 B4 S5 i2 i  U& M4 v
    4
    & W1 N- b3 \6 W6 Q7 V  j1 |# f* w. j# Erename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:7 E7 I/ d1 Q1 n. Z9 ?! V, _# d
    s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
    & l) p" N7 F1 P. Ws.head()
    " v+ u5 S: B3 x7 i  K. Z( Y( @2 `& w2 A$ X! \
    0        NaN  g6 o' m2 O6 j6 T7 V
    1        NaN
    " n. x! K: b, G% z& H+ \* ^7 p5 Z2        NaN
    7 j* B* ]9 H+ o# v$ [( i, n$ N9 |3 X+ a, h3    本科二年级学生
    1 y7 j. \; [- T- Y2 \' v4    本科二年级学生
    / Q/ t& A  }+ F4 H7 lName: Grade, dtype: category
    % @5 a, S9 F8 @" TCategories (1, object): ['本科二年级学生']  c2 Q8 B- R! C
    14 |% D0 T7 R+ `5 ?# P
    2* x" ~3 Q* @! l9 {
    3
    4 m. w) I- V2 }" U/ w7 a4
    : m4 T) z* y9 B) \$ p# P7 n$ }: O5* S' b8 ?# D1 m) n( A
    6
    . W4 Z" t& d* t7
    ( q$ F/ ?: K  i5 C7 m+ C8 w, ]; v" Y) I8; ?& v$ a, L, `. v5 `
    9* b7 V( Q2 g( c3 B
    10$ N2 x: ^/ r6 Y
    9.2 有序分类
    : G& Q# `$ x. o9 f+ X. @9.2.1 序的建立
    9 h3 C, T7 ?7 `: A' r& a  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
    # ?( a5 @4 {" T# T0 v3 v1 ?7 _& p; D7 O# O) y2 {3 _" g
    s = df.Grade.astype('category')
    * \; N, b$ e5 i2 ns = s.cat.reorder_categories(['Freshman', 'Sophomore',0 F2 }; ~% U3 }& Q) n# i- I
                                  'Junior', 'Senior'],ordered=True)( C6 |8 D/ b/ T! C8 ^& s
    s.head()! H. e0 ~0 ~9 t) A+ Y; ~
    Out[24]:
      W/ C( z; Q$ y0 j( ~0 L& I# O0     Freshman" Y. B: O3 d" q
    1     Freshman- j" h: U* e4 U# O  @! n
    2       Senior
    . h1 v# Z4 U: X- Y3    Sophomore9 M* \  Y9 E/ Z" l7 r  d
    4    Sophomore
    ) r+ G2 n% b, ~. r! \4 DName: Grade, dtype: category* k7 h2 S' c: I# H$ d& [8 u/ c4 V
    Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
    0 q+ S0 @$ ~4 n3 n& u4 h* ^- T5 H+ p: w
    s.cat.as_unordered().head()
    % T; v' u" d1 A4 H& [5 K! M3 UOut[25]:
    4 Q* c. A$ Z  d- S0     Freshman7 k3 v% Z. l  J! S
    1     Freshman+ s( [" g$ W+ _% H
    2       Senior, E. k, U4 K& ]# \( w
    3    Sophomore8 t: a) a" ^: ?. G( r) \
    4    Sophomore1 u6 \. d  r, i" ]7 M# A
    Name: Grade, dtype: category1 W. t! P. d5 R$ ?% m. H2 r4 |+ E$ A
    Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']9 V+ A2 B7 h. a

      R. {. v; C9 X4 U) M* x9 s9 e: b' j1
    ( t  x7 e, A2 V) N) y$ j7 D& W" f4 b2
    * g  g4 |. U3 _# u5 p3  e6 n0 Y# @$ |
    4
    , }1 E4 d3 T/ e6 E0 m- C54 i/ o1 t; v2 V, t% [. }# j
    6
    6 o& B; T4 d' A6 \7 F* f7 i7
    # T+ h% @/ P% a. y( t0 k82 c9 q7 Q. P* w# L" v- |: _; F4 F
    91 i0 ]1 m- f% b) x( g* l. H; L
    10' T- o8 @5 l( \1 z
    110 r$ Z" I) o" }$ r6 B9 b
    12
    5 B  m- n, k' W! H$ K135 d2 G) |. x  `4 ?5 E. E% b
    14% m3 K+ C" B& w2 J4 m
    15
    ( }% S# s3 M" r. ~  W& m16, s: o  z/ k. u: ]7 @
    17
    ! x& j6 n4 h% D2 b18
    1 S' Y' R3 ~! ~+ D/ u: v19
    $ Q8 Z! g" J( c- z  o/ @3 s$ J20
    + a' i% D9 G; a8 w21, }$ p, k: Y9 h2 r# N
    22
    0 w2 J9 [. a. ]. K% D) Q! E  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。. G7 B( _6 @( E6 l
    * c" d- f/ J% d% c' ^+ l; F
    9.2.2 排序和比较# b6 Y, ]9 x; @% M/ b$ O# P. @* `
    在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    # I( l( z6 I7 K- e3 R3 v( |
    3 y, a3 P( U5 i& B; [$ Y" d' k  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:4 ], ?! _7 x0 `* S: T2 Z

    4 M0 b7 C6 E: d& O7 ldf.Grade = df.Grade.astype('category')1 O' V# D1 Z; l' t+ G0 [
    df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    2 ?- G5 a" F% c9 s& |2 U, Kdf.sort_values('Grade').head() # 值排序  |* b# _4 p( a( j, @$ K. v
    Out[28]:
    3 u; ]4 G+ j; P1 j$ I6 d4 `5 w% v% B5 J        Grade           Name  Gender  Height  Weight9 N$ `' u/ z6 m+ {" N7 u  ?
    0    Freshman   Gaopeng Yang  Female   158.9    46.0
    + y: h+ ~0 K6 q/ E& E2 n. t5 }0 u1 B2 q105  Freshman      Qiang Shi  Female   164.5    52.0
    * @/ {& d4 }" c( i96   Freshman  Changmei Feng  Female   163.8    56.0
    " t) b& k+ i: L: P, ^  j$ \88   Freshman   Xiaopeng Han  Female   164.1    53.02 V9 ]/ o; z: x2 j8 _) s8 w5 v
    81   Freshman    Yanli Zhang  Female   165.1    52.06 D2 `1 b  T+ u% B) n
    ! I7 d9 d) v( [$ B' q& Q2 w
    df.set_index('Grade').sort_index().head() # 索引排序0 ~3 H# E8 S8 f
    Out[29]: + m: @/ C& [1 u& y3 {  s
                       Name  Gender  Height  Weight9 X4 M. M' x" N. r2 e. y* ^
    Grade                                          ( b3 f' b( _3 U2 H+ \
    Freshman   Gaopeng Yang  Female   158.9    46.0
    3 n. b( W" ]0 x$ F. i# ]3 K/ G& wFreshman      Qiang Shi  Female   164.5    52.0, O! b* B, W# T: A) E4 H
    Freshman  Changmei Feng  Female   163.8    56.0
    , @7 |' T) I3 w# d( _9 n/ cFreshman   Xiaopeng Han  Female   164.1    53.0
    . M/ b- }& N1 t- iFreshman    Yanli Zhang  Female   165.1    52.0
    + H. N4 P! |8 i' c. Y( ~  ]6 Z9 [+ c/ T" I+ l: W5 `- A
    1
    ; R  z! o3 b% a* i) y2
    , p( G% y! G% }' C" f% ^$ I3 h3( W+ M8 |3 M5 _9 S4 W8 h& n: J
    4
    0 I( J6 y! I/ w! |3 [- [& c$ G5
    : D+ f& [/ a9 }0 B6
    8 g4 q" z+ Z, d0 F; u) ?7
    % C7 x; F4 a* }5 d7 K& O8. e/ ~5 i0 x: G5 Y: K
    9
    9 H6 S3 O. x4 T7 l( }! p& r10
    # i) G6 G% ^/ p( G  X. A11( O  q$ W2 s+ M3 [  R
    12
    6 C6 d  m. N, C5 u. X# S4 o: x13- {4 }0 m4 G* X6 D
    143 C/ L1 U4 g/ S3 o8 d2 ?. s
    15
    2 p0 q' k- \( o$ l3 e# D$ k16% m- p, |( w7 F, p. D
    17; {/ z8 ]. W! B2 @5 |+ S
    183 j: X7 b, Z5 a$ j% W9 h/ \
    19" v9 M# n! ?8 a4 i1 _! O3 V; l- q5 T
    209 c9 R1 k9 m7 w0 |1 Z1 Q2 J5 E$ p
      由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
    , W0 G' v) n& T7 o" h9 R: P. s& F) u; k% s+ |
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)0 H, [; J* v3 u
    >,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。$ m; \9 n8 i: T' @) O4 {3 j
    res1 = df.Grade == 'Sophomore'
    7 E9 {4 ~3 Y# q" X" K% Q
    ) u. X: _  Q/ y* {, d1 M7 _  y: \$ S2 pres1.head()
    ' P0 J- `, r* w$ QOut[31]: ( R9 }# X8 c' T7 B* h" H+ L+ j
    0    False: H% X2 K& J, |2 J
    1    False/ U1 t! d8 Z2 \$ ?7 X
    2    False: y8 p1 }+ M( M) i/ y9 C+ O6 y
    3     True
    . y; Q; t% _1 x5 A2 }4     True
    % j8 Z& F: Q/ D& _  V$ m+ E+ t2 r/ nName: Grade, dtype: bool
    - c# c# x  {8 x5 \6 B4 ]- R; C
      A9 ]+ k2 m! G, U! @res2 = df.Grade == ['PhD']*df.shape[0]6 _0 M$ n* a& e3 ~  d4 h" O
    + Q( E  W, _4 v; e7 O" G7 W7 t) |
    res2.head()
    # s- Q/ ?+ Z& M7 @. S" L* HOut[33]: # D" K( o9 ^+ \- q0 O( K5 {. ?8 a
    0    False$ Z% x2 j! v8 G: e9 W5 d0 P" H3 f3 t
    1    False
    ( R* q5 ?) s" ]; o- q2    False( R: A, H+ {" Y8 \
    3    False
    9 s" b1 V2 A: F' r* t4    False+ C  {4 T! W& q0 b: o7 N3 O
    Name: Grade, dtype: bool
    1 e5 B/ n0 b+ F5 K: W; Q9 ^3 r
    1 e# L1 ]" G& N- Z! |7 W4 z1 Pres3 = df.Grade <= 'Sophomore'! V1 E( T7 X. ~2 h( \$ g# V& d" h$ L

    # @$ M4 ]2 L3 W& V2 R5 w) Kres3.head()# O7 I- r9 @& N2 O
    Out[35]:
    # i6 k  ?: d5 U$ B, g0     True
    . Q+ n( d# R& O1     True2 Y9 y" E. }7 H8 {; w) d
    2    False4 J$ q+ L( i7 i5 B1 T$ ?
    3     True2 _. v0 E2 N' Z- y
    4     True
    . I! u. l# D4 X; l/ U0 _3 Z+ J* M" UName: Grade, dtype: bool6 S4 I5 X' B, i9 w
    " V+ Q' a! `" F) ~0 @/ [
    # sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
    # B/ e7 k; P6 C; K  q' a% n( l- Ares4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
    9 @' \- i% F( i9 o" R* A6 x' T( L) N$ z& O. c* G5 H
    res4.head()
    ! r: \# u1 H* A. e7 c5 rOut[37]:
    9 i% @7 k2 J1 J7 e, N+ P' U0     True
    . ?) M* T$ F( h* w: D1     True4 `+ r; |, A/ E# @5 L7 y$ E4 H
    2    False/ G! ^( Z+ b/ `' x6 f$ L
    3     True# K/ v, J4 O2 ?$ o9 Y0 d: o
    4     True
    & Y- t. d1 Z0 Y/ `6 uName: Grade, dtype: bool) f3 v; Y3 V2 x1 A9 D: l
    $ A( N8 N3 ~9 D6 K  ~2 [( [
    15 h/ w+ Q% P0 W+ a1 {; D4 D" G
    2. V/ D' F) W9 z8 R3 q+ v; Q+ N
    3
    , F) u' Z5 X) @4 l! ]! U" U4
    , L; {4 j7 y8 W9 m2 K5
    & O+ ?( Y' }, @  D1 B5 @6$ T6 Q9 w  d) G3 V6 z
    7* ~1 `8 r/ E0 S3 t/ j/ p7 o
    8# v3 W+ S9 R1 o/ F! b) {
    9' ]* Y3 b# j, K. Y6 L
    10( l# W' D# U3 S' P1 H; c
    11
    - l5 [: E* v. K, ]12) C' \$ j+ I5 a+ w, i& A/ L
    13) K6 z, N4 r5 L$ K' ^3 u  t1 i
    14. {/ |2 R) o" a2 C- G& Q
    154 {) w$ ^4 M! t" ]+ `
    16- p8 ^4 v6 J8 {
    17
    ! U1 ^0 d5 [+ F# m: v6 }18
    6 `% {' P/ X9 X4 B; ?: E# P; l19# s) X/ O4 [" f. B) [
    204 C( i1 {( o8 Y
    217 A3 a: {) a% ]# N) i3 o0 v
    22( d: H9 n% m$ D4 {$ B, Q
    23+ E6 u) Q" ~7 Y! G
    24
    , a- U. C4 [( w& k8 V25
    ' x1 Q! [: i# g" Y* b* n, Q26: Q' ?: s5 p7 M* ]
    27
    3 D# A! C3 e& V5 u! a28
    ; u6 G+ K7 K6 C7 X1 p5 r29- s1 S9 w, D. C9 I7 ~
    30
    ; H3 J: E4 S) s31+ Z- _2 h8 y6 j8 S  T
    32
    ; d. t5 e$ i4 }33
    ' ~- V2 j; v: @# w34
    - v8 _9 S( l. b5 J: a- B; ]! ?35
    4 I# X. ~- u  ?% b5 U36
    ( V. j6 x. s  v% t/ z37* A6 K; b8 B5 D* Y: U
    389 x5 o4 y- w4 w- |
    39
    7 x- i9 W" O6 m2 a407 R- B" b* ^: C$ A  r: f' M  o
    41
    7 X3 G: ^0 X. P4 J# n/ z* B42
    . {" R+ c! h1 \, [43! L3 r! n4 _9 k( G7 I& Y
    44
    " Z: q) U, P/ x& g& _8 l+ A5 _9.3 区间类别
    . }+ O( C0 k/ c5 o9.3.1 利用cut和qcut进行区间构造
    , d& B- a4 x) }5 Y; A  d# N+ M  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
    ! o8 k0 L0 @+ Z) ]8 X, a6 A, f% D( G3 n7 Z& |
    cut函数常用参数有:9 J9 X6 Y! f1 h  R  o# Z
    bins:最重要的参数。, u1 p5 w% G9 _! [
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    5 K& p1 v7 h! z2 u* t1 I+ P4 ?! u也可以传入列表,表示按指定区间分割点分割。
    1 m( `: s: ]; X5 D. l: s  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
    / I' ?9 Y- m3 y% e3 l% B  如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
    % x1 D& i( d% `0 M' [4 x
      q) W# i% }7 h) [s = pd.Series([1,2]), V: I0 x/ u2 ^) r# k- }
    # bin传入整数
    / r8 U7 ^' P5 Q6 z. M/ E$ |9 I, z# B& A2 L
    pd.cut(s, bins=2)/ Z# t: ?5 |3 y8 w5 b' S3 q
    Out[39]: . ]* D$ X9 M% Z) b& I
    0    (0.999, 1.5]
    6 }$ |, g( Z/ W1 r# Q$ X1      (1.5, 2.0]
    - d; |" Y% j) D; |+ E  odtype: category. l% E8 [1 l+ N/ ~* p# b4 X: E
    Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    % n7 m  u6 E# B1 v( P: C; J& u; |0 D: R- \0 j
    pd.cut(s, bins=2, right=False)% ?2 y9 F! o! T$ ~2 C
    Out[40]: 6 K& f1 Z/ Y5 W+ p
    0      [1.0, 1.5)) n& E2 N5 s. N# @
    1    [1.5, 2.001)
    1 `2 j0 Q  q4 b9 ]  \1 Z; w: Bdtype: category! i/ `. d* }/ H1 b" k; V" K4 R5 w
    Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]' q  e/ l- J2 |# l" U- \

    # b4 r7 w4 W& s
    6 }* p( K6 y. U# bin传入分割点列表(使用`np.infty`可以表示无穷大):, q7 D2 N% \  @% ^9 u0 Z% g7 }
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
    - F( J( m4 [4 T. `% o, B! L! OOut[41]: ' Z, L+ ]- X; E; K- P: C. Y. I
    0    (-inf, 1.2]
    8 _' |( ?' T7 D/ W1     (1.8, 2.2]# o& P# E5 Q( d$ q6 F, ]3 C: t) ^
    dtype: category
    8 b7 \+ S/ `& T* [9 S- X" mCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
    5 W4 `% U2 d' y' v, Y$ f. A
    2 V) ^# K/ ]  F1 w1
    , \- k, e5 v* N' O5 ^1 C  v' T0 [2
    8 e. L- e$ o- V& ^% }2 b% r3
    # K4 o& X& D, F0 ~* t- q9 y9 G4
    7 z9 c! ~1 W) z- X5! w0 T6 q" ~' r# B
    6
    8 J; N) i) d# S7
    ( T3 @  d9 a9 n" R8 D5 |8
    / G' M% u: K7 M, ^9
    9 F: K2 q& H& ^# ]8 o+ Z* T: O* H107 T) _. b) Y0 I! [
    119 G. v; v; j+ V& K1 K
    12. d7 [/ j, i% u7 N5 h& [9 ]
    13
    ; B0 h# t* x) u; v) R$ f4 p0 z14
    ( A. z! T1 s' C* h# Q/ x157 y9 \) Y' c9 K3 N9 U$ b& s/ B
    16# I5 w  ^0 {# D% B! B5 P$ I
    17
    5 Q2 z- z; ?/ |0 Y+ S, u$ `; R0 w' R18
    , a- {5 @( E* |* B* r& X19/ P/ i! Z; @  D. R9 Y
    20. E4 c( M8 \0 t- t4 t
    213 e- G* M  Y6 Y8 A
    22
    . M5 [8 I/ r& h232 R; R8 I3 v# I0 ]
    24+ `- V( ~7 |! E, s# n9 w" i
    25) U/ k' X. R3 H$ X, c
    labels:区间的名字! J+ l: d2 B6 |
    retbins:是否返回分割点(默认不返回)8 J  `7 B* v$ n& N9 Q
    默认retbins=Flase时,返回每个元素所属区间的列表' T8 _0 P% W- F5 z& h  W  g
    retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
    1 d1 W" H/ \, @8 }5 J7 m, o6 G2 W- ~, y, v7 r
    s = df.Weight  Z7 U- Y7 \( Y6 {9 J
    res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
    * X2 {5 Y: f5 ?+ \( nres[0][:2]7 g7 ?$ e( G7 }  e: y2 R% Q

    . q7 L: ]8 \& [: \Out[44]: 0 T9 s- j, v# T2 q; J0 t+ X& I
    0    small0 Y9 Q8 j* H1 j  U" x; S% `8 E
    1      big8 j& }2 e; W3 g- B& O0 H4 B. U3 \
    dtype: category
    # X4 w( p# q: b- W! H: VCategories (2, object): ['small' < 'big']
    3 A% j) Q! c, s/ A9 e5 E4 @/ E0 ~* y* P
    res[1] # 该元素为返回的分割点
    : F( R3 n" C8 h2 HOut[45]: array([0.999, 1.5  , 2.   ])) U1 M2 E5 F9 I- i: E1 @6 s& o
    1; m2 O6 M* E& H; F1 D
    28 U! Y# y& W' {; g1 F
    3$ _# ?# L2 [! S
    4
    : P; B! w3 i2 J5
    5 D1 ~& a: O! ~; i0 U6* W3 J! L) s; S4 [/ X* N
    73 ?9 A+ ~2 h9 q$ y- V
    8. b& m; `, ?1 y1 h+ [3 h
    98 N  z* y+ d; ^; b
    104 v& u& p7 D' q3 @, P( i
    11. x1 q7 ^$ x& o" d0 t- N
    125 ]. }" B( F* O5 Z& I* l7 ?/ \3 e7 o
    qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。9 c  w  p: u0 o
    q为整数n时,指按照n等分位数把数据分箱
    1 `, z& G) n. ^4 m2 h: Pq为浮点列表时,表示相应的分位数分割点。
    6 w3 C3 O  {6 Y2 Os = df.Weight$ X0 N0 ]* n6 \: j0 J
    * d' U9 m6 \3 w: l
    pd.qcut(s, q=3).head()( u% P& a( o/ H* e- t
    Out[47]:
    9 Q2 `! R/ `0 z0 B1 m0    (33.999, 48.0]8 A# s" ~8 r& ]0 y' n  L+ ]
    1      (55.0, 89.0]: i! ^- ?! n) m  Z# A* r: J
    2      (55.0, 89.0]
      x! C$ G% k8 }3    (33.999, 48.0]& a3 w7 }+ u) ^$ h, r
    4      (55.0, 89.0]* n7 q3 c' G& M8 b
    Name: Weight, dtype: category
    : o7 l1 p9 ~* G: [) H" eCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
    , t. Q$ A  F  }. {
    5 s9 }( Q$ ?: Z# R; r1 {9 ipd.qcut(s, q=[0,0.2,0.8,1]).head()3 Z8 w; i2 C5 N/ K# ~" ~% V
    Out[48]: 2 y# `' b, V% ~( X, r' _) }
    0      (44.0, 69.4]
    & c* J7 V" x; }  U0 c1      (69.4, 89.0]
    1 h, m% A7 A' x) j! l2      (69.4, 89.0]- C1 I% t4 |" l6 c* G
    3    (33.999, 44.0]
    3 }/ K  q$ ?5 [- R5 T$ j% J4      (69.4, 89.0]
    8 D) }9 M4 m/ j+ MName: Weight, dtype: category1 l+ K% [; B/ [; X/ m4 p- I+ G
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
    6 d/ u! C- R' ]( u0 U7 z" N
    # H- v  Y; `" ^. y1
    . F, O  M* w* r! ]4 v! x7 _24 ?* m5 m+ n( U9 S( g. r
    3
    ( V3 I. q0 M5 ]# ?8 z# i1 j. f4: z7 x8 Q: S. @: X0 |2 {2 D
    5
    9 D# a# v4 d, E8 X1 C! B$ K0 b, a/ P6
    ' A  A( Y( s9 L0 B1 j! K73 F8 j/ z; [. K
    8
    5 g2 w7 k( F/ x1 t5 ^1 v" l8 U9 m9
    ( T6 `9 X, ?  j( V" X' G10/ Z; @( `) `& P* x, f& X
    11" c# F: t  n& J1 f9 P) J+ Z+ T8 a
    124 h/ s# q. J, q: W9 x; }
    131 U3 L5 j8 U( u  f( Y6 P. k
    14
    / L: V9 p  V) S" @. I15
    , I2 r- E- r" c7 P5 j16
    $ u0 d+ ^9 M6 k* R- A8 j& a3 E17
    * _  w+ {0 q7 U: `/ f1 ?7 H; I180 `$ \$ h! a6 H; N+ h6 _6 Q
    19" J4 a( R) |; I- Y3 o8 [' }
    20) j, a# P; R# }& q
    219 s3 K, G  z9 I5 |8 L$ m  C
    9.3.2 一般区间的构造$ ^* ?1 L9 T8 C/ i" f
      pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。& d) a+ W5 T. L1 B& B0 Y

    3 ^2 }( O5 M; s, p; Y开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
    0 L: D/ Q7 y8 s. d# f" x" Umy_interval = pd.Interval(0, 1, 'right')
    8 E4 B$ B6 M5 I0 m5 Y& L0 c+ ]' Q$ O. k
    my_interval3 @" ~; v$ k1 a1 i1 ]& C4 d
    Out[50]: Interval(0, 1, closed='right')9 D+ c1 }$ `' B8 D2 D% O8 s
    1
    / o" I& o) r% ]# v  j23 z5 x4 L. _( S( q3 R- G" J0 ^: y
    31 p. `9 v, ]7 D2 m/ G6 z/ S2 x0 q
    4
    + U7 R" s2 F9 N* A! O0 }/ L$ ~区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
    % x* G8 g( \- Y, y( t7 D  j使用in可以判断元素是否属于区间/ M8 s; y( L6 m4 O3 S5 B
    用overlaps可以判断两个区间是否有交集:
    0 R/ c' d& t" d0 E0.5 in my_interval
    4 e  n$ K6 m1 Q, g* g' z% p! P) D+ h7 ]- n8 {1 Q
    True' W3 \. Q" @( E: x  h
    1: K, f8 e/ [" {9 E6 }# n/ I
    2* `$ z3 c& Z- B5 c
    3
    % s; y( W, q& }( {- n' A" }$ J: wmy_interval_2 = pd.Interval(0.5, 1.5, 'left')- N/ ]6 E- g/ B1 K/ t
    my_interval.overlaps(my_interval_2)
    ! |) z& R1 F+ {# \1 k5 u. _( j# y0 }: @* c. r" c+ T
    True4 V* k. i. }+ m' L8 V) ?
    1
    . _* U% U5 f& `- A* \2' {% w+ e. |+ ]9 \. ]8 Y7 ?# E2 y
    3
    2 Z+ W) E# v5 s8 n/ P4: g4 n0 ]- J: A% G. y) [$ ]
      pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:% f6 ]1 ~1 g4 ]& J& K( B3 K7 G
    1 @5 J# b6 ^7 w8 ^! o
    from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    9 |! s2 i0 l& M" B' npd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
    ! K" q1 c! n: Q2 z
    2 a; P) }) a# ?' }IntervalIndex([[1, 3], [3, 6], [6, 10]],
    . H/ n$ b- I# T; i+ }               closed='both',
    0 D1 u/ _# f! Z# z0 P               dtype='interval[int64]')0 `$ Q/ E' y3 _1 \* B
    1
    2 j5 ~8 ?/ ^- h2 o. G4 O2 I2
    ' `. o7 Y$ @1 R- Z3
    + s1 Z; C- j; |# ]6 v" m$ m4
    8 `) |/ j9 O, g0 |7 c: v5
    / u- s7 v3 M$ k3 afrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:: \% ]3 E$ X6 i- D
    pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
    . q5 |9 S+ M  L
      F8 ~) d3 Q# \) VIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    , U- U6 ?6 I, G: J5 ~4 x- g$ m( }                  closed='neither',  N' _( v3 s7 y: }
                      dtype='interval[int64]')
    8 X& u/ d, \9 g8 R/ \  N. x1# V9 _6 I0 v8 J6 \7 j
    22 e8 r1 Z5 N6 u8 L
    3
    , O' q( I; U- ~2 `4* [0 i3 ?$ H8 Q) ^7 o$ w; R+ R! U
    5% o6 E) d" @9 Y/ \5 N
    from_tuples:传入起点和终点元组构成的列表:
    2 O' L2 s) K8 H1 s' ipd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    ; _8 M* |7 J8 `/ s8 }  G+ H
    7 t2 h  {% m. a2 A. L+ V3 e. SIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],2 q" T6 j- g" W- H- t: j! X, A2 f
                  closed='neither',7 O3 n* ?) U! }% ]5 F9 u- n" e9 p
                  dtype='interval[int64]')
    $ W3 W3 N8 j# a, Q: m8 \5 M; b1
    " [! I7 R5 V& W$ K2! G4 Y3 s5 @& _7 j* Q( y: U+ N
    3
    + i8 n( J8 G" ]( o$ D# T* z47 n# j0 I% W$ O' B
    5
    7 }, E* F+ b8 h+ ?8 Rinterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
    ! W' \( Q0 o3 @' ipd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数1 G4 {- U% X* Q9 e, B# t
    Out[57]:
    6 ]% T* I$ z! l: tIntervalIndex([(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]],
    ' R$ T- G3 y( n, o4 t2 _4 E              closed='right',
    % T, Y. R) t1 x& n4 Q              dtype='interval[float64]')
    " y, k4 z7 |2 M
    # D9 f$ {6 N" }5 o4 d: w4 ?pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度8 G- T" p" d. Q6 n9 g8 N$ W
    Out[58]:
    4 b; ]  r. G9 y7 H3 U' n0 OIntervalIndex([(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]],+ x5 ?2 H4 f) R8 b3 i2 E
                  closed='right',
    # X, g$ w9 M5 F  X9 W, k. ~1 I; C8 R+ l              dtype='interval[float64]')$ N8 y/ A) b3 \8 F
    1
    6 J/ \& k) P6 b  F23 w2 t' a% m. h2 F, f
    3. V. \, {" h1 L" L6 V3 R
    4% X: ]6 w4 O+ v
    5& D. p+ c- H9 W; ?
    6
    1 x9 i; q4 i! g7" H" X- W/ i# l+ {/ _4 C
    8& c  A2 E. G2 _8 p" n
    9! f/ B# a, Q3 V* j& ]! {- ~% e9 h( C
    10
    , m$ m2 F8 ?8 U# M* p3 h$ K. o1 a11
    1 W$ M7 q1 o8 R( j! }" v2 T1 I【练一练】
    ) L; \6 Z0 a3 d  M  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。9 y0 ?9 {+ S& f2 x. ~, C
    / h3 K% w* u, F# O- {- `( m
      除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
    5 k/ ]; i7 p4 i0 u  B$ G0 M8 F( P
    1 _' H# \( k2 umy_interval
      Y1 p4 `9 a+ Q5 }% A! QOut[59]: Interval(0, 1, closed='right')  e$ y8 t5 v: w, W( `: J

    . f; S: v1 |6 o4 Bmy_interval_2
    / m$ N" O* {) Q* |# uOut[60]: Interval(0.5, 1.5, closed='left'). G; [5 c; ^% |5 P
    2 z7 t  _! u, w: y# G# r1 ]( G6 C. P
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')3 H6 A9 e/ y% s( [1 O
    Out[61]: * Q. a* Y& E' {, C
    IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    ( L1 I- W6 z* O9 |- P/ Q4 m. Z              closed='left',
    " z- Q! v5 c1 M) w              dtype='interval[float64]')
    9 J1 s+ o1 g  ]. ]4 c2 C1" p2 L$ n" g4 z4 B8 g
    2
    % E: E; T! x; L: G3
    / e/ J/ W- H: e4
    ; U: p" q( E9 f4 c5! `3 [) G& J5 L9 |5 t) p7 W% G
    6' K& g9 d6 s: J
    7
    1 f( ~7 [, d5 C8' [& p3 A. Y/ B" m
    9
    5 q% v$ J1 u) ?3 V5 L6 z6 A$ c10+ z/ W" c( T7 D/ C
    11
    + Z) Q3 t0 _$ p9.3.3 区间的属性与方法1 @( _' p3 `. }+ j! {
      IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
    2 g' V: P1 u: ]- o; E4 N- V8 }: q% R' ]* W" c/ a
    s=df.Weight+ X9 B! D: u# y8 t' `+ x
    id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
    : T+ e- `% a( Z9 o2 a5 Kid_interval[:3]
    0 r) g% |* i8 ]& n* C2 w7 x
    : b2 F( X9 o5 nIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
    ) @8 _* K. N! h- T5 ?6 W# ?3 V" T                 closed='right',
    ' x9 Y* G$ a- Z1 e1 @" z                 name='Weight',' v/ L5 ?& |9 n( X  I
                     dtype='interval[float64]')
      d$ [, R4 }+ C2 A. N5 p' b* K' v1* s# ?- Y# M/ {, i' |) Q4 C4 Z+ o! C
    2
    5 k  B8 I, t5 `" U4 [' @* I3
    ' Y8 E6 s9 p, F42 \. Y- x6 \0 a  ~
    5. C# \/ W$ S7 d( q. m8 i
    6
    : |4 N& e) E; i! Z$ j1 n! d75 |) f& f9 u# [. a) Y2 x3 x# a0 K
    8
    : w% X0 b7 T- c$ M9 c( Z  Y, r( c& [与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。1 x2 g8 z; h: Y. Q9 X
    id_demo = id_interval[:5] # 选出前5个展示
    9 n. z0 B5 y% `& V1 H& F  R: |- j) Z8 Y6 f0 m/ w! c
    id_demo3 C) s# w/ H1 r3 z( n. c0 {
    Out[64]:
    5 S- S7 [2 w" x6 c9 F9 z6 sIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    + R& N5 z" P) ^' ^              closed='right',
    5 W+ O1 k7 y( k7 S3 V% d              name='Weight',: w( e- ?( y: x" Q
                  dtype='interval[float64]')
    ) \8 v" a, m) ?3 y6 x8 A% f- Z8 A% ]* i7 Z  y- D
    id_demo.left # 获取这五个区间的左端点" \; d# G" h8 y  S% n7 P0 \
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    $ [( }. f6 h' i0 r
    $ A' e0 I" ~2 }- L: h/ X# eid_demo.right # 获取这五个区间的右端点* V' q7 b( F% e, l
    Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
    0 }! }) l' J$ v+ H! F$ {
      Q3 r" ?9 `- B2 mid_demo.mid
    ( v( X8 L0 k" {- oOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
    % B, n1 h: }5 p4 u, @. K3 O5 |* v
    - j8 e: c% W; x! w* Vid_demo.length7 N; @# X' N+ A7 p& ^% [! ^0 f
    Out[68]: 0 I. n8 j7 o0 m$ K# F/ Y: v
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,1 R' l; X/ }& h/ P# P! R+ q0 P
                  18.387999999999998, 18.333],
    6 i* t5 z  u& ?3 O: K7 s% J$ W0 q             dtype='float64')& L" F1 t9 z' i+ R

    4 D- e5 @1 \8 f1
    0 \7 O2 p8 f+ w2
    7 C) `7 z' L! t" ?; n- x3
    5 T# b5 M# j- X4  l2 H$ h) a' G
    5$ f: o5 q* M6 t" g; G
    68 r- E8 i8 ~5 E: S5 R/ x* J
    70 B5 x' a8 B* ^
    8
    , I3 \. l* ^  _9 k; X9
    9 I  Y8 @: G" f10* j3 @$ D  i. i5 v* T1 s
    116 y* y* w$ @1 X: z! r7 @4 a: i
    12
    7 B' b& F5 f/ \" W13
    . B; _# W$ A/ Y  f* G14- `$ J5 S: Z) U# O) g2 U
    15& o& X2 Z6 @  E! }& k7 H
    16
    # F+ l! E( j. o8 P17
    ( f3 S6 o+ c' ^18; v5 i! Y: ^& X3 w
    194 m& `0 R8 `* J! Q: {3 D
    20
    0 _2 }0 ?$ C( {$ ]$ u# \21
    7 h9 E$ P2 v: v3 K, F# N2 {22- C+ p' \4 X& N; U: Y5 o, j
    236 a- b* I4 z/ G
    IntervalIndex还有两个常用方法:2 @+ O) B7 d' `1 A
    contains:逐个判断每个区间是否包含某元素7 Z8 i: C5 D* M) X
    overlaps:是否和一个pd.Interval对象有交集。
    - K" [+ s# z0 q% B$ Gid_demo.contains(50)$ m! z$ |# q) q  F+ A# g& s
    Out[69]: array([ True, False, False,  True, False]), `$ Z+ f, X9 i: i4 x  v! p

    8 T; c9 ^+ K% t" r  wid_demo.overlaps(pd.Interval(40,60))5 o1 J: J& D9 Y* \3 k
    Out[70]: array([ True,  True, False,  True, False])) p2 V+ h( m6 R& k- S
    1
    2 Z  E) u" ^) t/ x2
      w  |9 `+ X' Y2 I# z3 v5 d3
    ; b$ v9 e$ i' g2 S: y' j, `4( l3 A- y' d2 j  t5 `
    59 V3 a  z& A9 {7 h: N
    9.4 练习
    5 W' T7 v- e) w3 g+ V% zEx1: 统计未出现的类别
    0 ?& I+ I! e' i  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
    ) q! F+ @  r! g9 \5 J# U- N7 {+ F; u7 ?0 x* r
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}): `0 [) N7 M0 J8 w. b' O4 \
    pd.crosstab(df.A, df.B)
    8 F: t/ a  e$ ]3 F) k" c" ]. T! c
    / Z8 y, y( \* f! [Out[72]: ' v# T5 j$ A' L! w. t1 B( p
    B  cat  dog
    1 m! v8 b9 |: @3 z& {, k/ h& w, ]A         
    ; }. ~6 T7 g2 o. P; P) \0 [a    2    03 p% p' ?. P: H8 k
    b    1    0& s) r& ?  L0 n  T' K1 j* K
    c    0    1" F  {& E( b3 s7 w3 w7 @/ E
    1: C5 Q( a$ l3 _) C; \; o
    2
    ( p8 A; y( y  {7 v3
    2 \. q1 q; S2 S2 v) r1 e' Z4; f- a. @/ P& m3 @
    59 Q" _* }. Z: h* F/ w* l5 d
    65 U4 Q  O! t/ l6 `3 O& L: ]7 n$ y
    7
    ; \  e: S( Z8 e# n- g$ l  h8 _8
    2 E4 `: l% I2 L1 J0 V  B9
    , c! S" g$ m) `5 e" v' ]4 x  但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
    : G7 ~! C6 W$ b0 V# ^+ l  B% [
    0 @* ]5 r  P  {+ K" z$ S, Rdf.B = df.B.astype('category').cat.add_categories('sheep')( a. F8 v5 H2 H/ b3 w2 K' q( X  D
    pd.crosstab(df.A, df.B, dropna=False)% H" m+ Y! I# h8 m4 c
    , ?( f, T, n6 n. F1 t
    Out[74]:
    , a: A& ^# q( \; ~( qB  cat  dog  sheep
    & }1 [/ o! p( ^3 M3 J" vA                 $ z2 |) \; R$ j: E6 [. F2 J
    a    2    0      0
    $ U+ f* M% u/ @6 X1 U$ Mb    1    0      0
    3 W' r1 H- [7 ^* n8 Pc    0    1      0- u: C: y5 H+ i" P
    1' k# c$ o' r. p* ?
    2
    ; E$ w) E7 D* i' G& ^8 v31 H: u) z$ f6 j) i  i
    49 w' I( o+ O! m. ^6 b) _" Q
    5
    1 _/ _# ~8 l- M: v6, D6 B! ~7 A6 `
    7
    $ h% Q+ B/ Q/ ?7 W8
    : c- e# d* {" V9
      m! H, p# C; n: n7 d/ M3 T& U请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。) b- a  x$ L) X7 O/ c" V

    2 p, h, O4 m; X$ z1 O$ Q- Z5 {0 L5 gEx2: 钻石数据集
    % w+ y+ w# w3 _( `  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
    , A9 h- @* L+ _( H' Z% v& E* L8 p. q8 @5 l5 w
    df = pd.read_csv('../data/diamonds.csv')
    9 s; b4 E- t* Y' l8 }" G* edf.head(3)1 o4 `/ ~0 Q: y. ~# X
    # W# ~+ ~9 d" e$ X& D- ]
    Out[76]: 9 s: }: V: {8 ~$ ^" d' W* d
       carat      cut    clarity  price5 n1 |7 _% X3 i
    0   0.23     Ideal     SI2     3265 l- A5 Z/ `! C$ {" H/ s
    1   0.21    Premium    SI1     326
    : o2 i9 \# s) Q; ~: Y# N2   0.23     Good      VS1     3273 T8 u  I: Y2 Q4 M
    1
    6 y- W2 }' }2 P2 A; v2" R/ p) y( ^+ [
    3
    9 {; M6 y1 B( r! B4! [: s" i; E& T# v& @3 w
    5) ]! P: \( Z  V5 Y7 I
    68 |# a9 }+ k4 H; ^
    7, q7 Q3 Z; f7 v& R; j, T" `1 `
    8
    8 w5 @& ]$ }  F分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    , H' j2 k/ m8 O- [钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。% K  V( q7 |- E( ~8 ]) D
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    & d& [3 D3 j* n, }" v' ~对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    9 d+ x; I0 |3 J4 w. ?第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    ) G. y/ n' y. R5 `% P7 M对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。2 |/ ^  u3 Z! g
    先看看数据结构:+ _. ]5 u. d$ n) y4 k8 T

    0 j5 P3 [, d. zdf.info()
    2 O+ t! ?1 J$ p7 U/ e2 B9 MData columns (total 4 columns):
    # T' A2 q; O1 v! T4 x3 u5 q  e #   Column   Non-Null Count  Dtype  
      c  a7 N5 l0 c5 A1 r---  ------   --------------  -----  
    $ m' [& ]1 J& r* [- _' p# } 0   carat    53940 non-null  float64  f- t9 ?4 Z! Y+ A! _
    1   cut      53940 non-null  object
    7 K7 \- w3 A% f! U0 z 2   clarity  53940 non-null  object . q0 |) ?" g' ^) ~6 `1 ]
    3   price    53940 non-null  int64  
    : A- w8 b6 m: J0 E7 W* @dtypes: float64(1), int64(1), object(2)! b$ ?3 y4 f  @2 ^
    1
    / {3 E, d2 T+ C4 o2
    # S9 k: a9 @; A6 f7 \3
    / u& F6 S" A1 a+ ?4
    0 d; n3 A4 D4 T$ v1 G3 C$ d53 E6 k! U" n* r# }
    66 ~! h5 T4 J+ m+ G
    7
    % x/ a$ J0 X2 p  }& m, a& a8) Q. a  x9 ]" @5 f9 Y
    90 y8 A1 ?9 H' \8 E
    比较两种操作的性能
    ! z' W! @: ]5 Y5 K! I%time df.cut.unique()5 f$ h! O& W6 l4 H- ~. [

    " G4 J" K4 `6 A; O, e) `8 z0 CWall time: 5.98 ms
    2 p- ?7 F( ~# Q% E6 Jarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
    . u0 Q3 \) V5 W! k; ~1
    6 m/ T0 T7 D; i. L2
    " @& |5 G$ O7 F8 ?3* _* \+ d' g9 H- K. \
    4
    9 H( m9 k/ d  L' c%time df.cut.astype('category').unique()
    6 s' v( f* Z6 N* ^
    & }: y& D0 d; ?  [  ?, tWall time: 8.01 ms  # 转换类型加统计类别,一共8ms$ [' i' b5 m, y  p" G
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']" ~' K; i" m  j: m
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    : m2 S! C, \& A/ F1, K; q! {/ ?1 f9 D
    2/ C! J/ b- c/ K0 v
    3
    * Z5 K. Q. O* F" R" Q45 b9 @4 \3 x, X7 p: B! n  C
    5
    / Z6 X6 X6 J8 @' [df.cut=df.cut.astype('category')! f4 p: h! q, N( F0 n* ?
    %time df.cut.unique() # 类别属性统计,2ms
    2 v5 W+ s. T9 D
    $ o# I. ?  J# J( n# y" yWall time: 2 ms0 _9 m0 n* Z2 r4 q% B$ S7 q
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    2 s+ C3 o+ d, Z( s; j. M8 O3 fCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    ! H: O- W/ R: E  h/ G1
    0 s) ]* h! I% v4 S' R* ]2
    9 g8 P% l5 P; \3  T( C4 u' C3 _# @/ _
    4
    3 x) ^  V, i# e" q# J1 h+ `7 U) U; X5# F4 q7 V2 T8 V* P$ F7 F
    6
    5 _% k, ]9 K( L4 q3 N1 b对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。: |1 u3 m  E" T4 v# }& Y  e
    ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
    . E$ a, v  t! x8 O. P4 Bls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    0 r! S2 u1 V+ i9 I6 o5 `8 [6 adf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    0 N1 ]7 m/ P9 a! odf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)4 B: n$ q$ M; `" I
    5 }' q" m1 X  d$ K5 a1 h
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)- n: ]- o- _9 _0 ~- x+ G

    ! N  X; {; Y. |+ J8 f4 P- ]        carat         cut        clarity        price. o5 C& v( v2 D4 d; r$ p: |  ~+ j; L
    315        0.96        Ideal          I1        2801
    6 E9 t0 x: `3 B( t$ p% [! L535        0.96        Ideal          I1        2826/ I0 _' N) Y( R: X: |
    551        0.97        Ideal          I1        2830
    4 O7 M5 Y+ }) K/ Q8 d1
    . H( i' C% _; h23 n" e5 e: s5 r* w: X2 c, R$ f
    38 K: W+ N9 \) g( R
    4
    8 b; ]" @/ x8 q+ n* ~5/ v( t6 }9 d# X* P5 d; T- i. m
    6
    % k2 T- T/ K; P# B, f& @7) K4 C* G& W3 i9 }/ R
    8; V$ G- b0 q3 R+ p0 L
    94 v# b0 W9 A0 r
    10
    1 P" z3 x$ m9 P/ u" A) j11) _7 j1 t, ~9 o4 e. w* @- d
    分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。8 l9 `7 U7 |2 I6 \) B
    # 第一种是将类别重命名为整数3 `( _1 E  k  P2 v
    dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))4 k) l0 ]0 _" ^# ?& K
    dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))$ G' z! P' E& I+ f& F' U  o

    & Y/ {/ e0 P% N" wdf.cut=df.cut.cat.rename_categories(dict1)
    8 w3 Q/ B- S$ Y3 Gdf.clarity=df.clarity.cat.rename_categories(dict2)
    / @: L: e' d* }$ fdf.head(3)  w- Z, Q! u8 Z- ^
    ) F  E3 G7 n) {) N! W+ c. Z' J
            carat        cut        clarity        price: [2 o8 V6 }6 r) F3 P5 Z+ k
    0        0.23        0          6                326; f+ Y% k" b7 u# w; w" {
    1        0.21        1          5                326$ f+ ~2 K3 D2 P/ ?. M) j) T, H
    2        0.23        3          3                327
    6 ]- I6 x( l0 U7 j) q  ^1: f5 C3 r" d! t* X/ E
    20 b2 \" _6 r1 K, ]5 Y
    3
    % p, i9 ~* F* Z  C- W0 ~43 ]# A+ k5 C3 b, T( r  {4 y
    5
    - b! Z7 W- V1 F8 @1 T) G* L6! X8 X3 _& U! ~9 |' ]
    7) Q/ q! u" {+ n. g& C( R- L
    80 i# S# O" w! Z& r' G
    9
    - a( @& W8 x. I  G8 B* X) e. C10
    2 C; e6 y3 X+ {* v+ d11
    ' S; Y( c3 Q* p6 l+ `0 u# f9 `128 ?* r0 I: o7 V
    # 第二种应该是报错object属性,然后直接进行替换
    , u+ @" ]3 y8 Kdf = pd.read_csv('data/diamonds.csv')
    8 A3 F& i+ V( Y2 efor i,j in enumerate(ls_cut[::-1]):% X' H/ b+ i0 b+ S  j" p; e3 T) \- I% |
        df.loc[df.cut==j,'cut']=i 7 x" ~$ l$ O6 M' v( ?

    2 B7 b$ C* S7 W3 k# f# f  y+ Kfor k,l in enumerate(ls_clarity[::-1]):
    + T. B( u- f( t5 ~6 I" W' @8 l    df.loc[df.clarity==l,'clarity']=k
    % K& E! y: z2 L, Hdf.head(3)
    2 K7 {5 ]; C; L* ^9 I# V9 |$ y0 X8 |* i$ b8 P% M% M% c
            carat        cut        clarity        price
    : J  E8 n+ e" d7 _# Z0 H- j0        0.23        0          6                326
    7 L- G) b& `! K+ F3 F1        0.21        1          5                3265 E+ A3 o, p: M$ W% [
    2        0.23        3          3                327
    " n( y* g% l" W1 p8 j" g& e' t19 i  ?5 i0 g4 D6 F
    21 c% ?9 m, {! d9 O, E& Z: ]
    3
    : k- a- v3 ?: m" P9 M. z: E4' F# n+ M7 }6 J- {3 C% A
    5! v0 E4 L/ P" X  d6 {0 R3 o6 g9 l) h
    6/ l- C9 c, b- }0 e- ?
    7/ k9 N9 _  O* ^
    80 [- d! }6 K2 W
    9  c) ?- j+ J1 D% W
    10* r8 l6 E, U' s( }5 A; u; \0 |
    11$ I$ W( R2 s0 h  h! m5 b: S" q
    124 R% p: j/ l, b' {, y9 f
    13
    , z; g  x: Y0 h! P对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。2 ]5 {/ ^* ]7 F) \
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点: y# M9 m- w' X6 M0 V
    avg=df.price/df.carat, H& Q) e4 w3 J1 l4 v; ?
    : I; p1 }5 V0 [  F  U7 O) N8 `' |
    df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    9 V+ T+ h. v! ^  n2 U% K                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]6 {! ~  K* {3 w& S1 d, t: ]

      a( V7 D0 ^. w* z! W1 ]# Udf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],! X( f. L" g9 W& m
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    ) f3 H, K0 G/ Q( t6 ndf.head()
    4 t. r7 S* Q/ _$ o, e0 c! |# O. v/ E. l" k3 j
            carat        cut         clarity        price        price_quantile        price_list  W8 O0 ?$ d! {% q
    0        0.23        0                6                326                        Very Low                Low
    5 j" W2 ^) D- ]8 V  i6 l1        0.21        1                5                326                        Very Low                Low
    4 G: i2 ?( y: ~" W7 ^$ X4 W  v9 |2        0.23        3                3                327                        Very Low                Low3 L; _4 [6 j0 l
    3        0.29        1                4                334                        Very Low                Low0 L0 J9 {- Q7 N/ f0 s
    4        0.31        3                6                335                        Very Low                Low                                       : m- W1 a" Y/ V$ a1 R) {' o: y$ i

    * a- t; ]& O0 i+ [' H18 s4 G* M. ^' W% o& W5 Y. ]1 {
    2
    " ~, t; ~( ^7 G: U3
    + i, D6 ~8 N0 U! A) G; z6 B$ R4* m1 A8 z& e$ u0 V3 K9 x1 y
    5+ c* Z* ^0 Y+ |0 k4 H
    6
    , ~! k7 V+ O' G3 P6 r9 F* ?. Q3 V, o7
    5 v1 s* W  B/ P- X& J. ^8
    8 S$ L$ }2 T% a9 x  c9
    . c3 [/ |1 U9 ^7 K10" K! H8 x# u/ R9 B) u! p. a
    11. w6 f, j2 L# ^- Y
    12
    - X; T6 x' a( n0 |. r4 q3 j13/ L, h6 f3 s3 i
    14
    2 J# O( }/ a( l2 C0 v# W: @15
    + L$ {( }; g: J4 i& f0 o16
    , J1 B9 k2 ^3 S5 S分割点分别是:
    3 [1 L) Q$ l' |9 I8 l1 Q
    ' ^7 S$ F3 k0 w" d) F: uarray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])6 B' Y/ [& @3 ^
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    0 K0 e, A. ?3 n4 h10 R& b- ]/ ^7 c8 N+ o& i" b. j4 ^
    2* |. ]; O5 b; R) |* F& _9 w4 z( Z
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    6 a* T( s, Q: J1 Zdf['price_list'].cat.categories # 原先设定的类别数/ G! M! O% X! a1 R% ?
    Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
    7 V: N( l. R# K& e/ Z8 D* |  H% Y6 A( ]2 w9 b! N- ]% h2 u! E
    df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别
      R5 V9 V+ ^9 G. w) HIndex(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现, u* b$ u+ [6 R* Z" j
    1" ~( J2 H! p/ P; j: H* T% V
    2
    ( U7 ]7 {) m& c3  V. e/ m- m. _4 N) S
    44 h+ O  c# w1 E
    59 b9 s, D7 H! Q/ a9 f* y
    avg.sort_values() # 可见首尾区间确实是没有的. B- [  H" Z% s. P/ Y7 C& Y, p  C
    31962     1051.162791+ j7 V% X% _2 w2 T6 v
    15        1078.125000
    - _2 R4 B$ x3 ^& q4         1080.645161; K2 z, d+ v7 x8 I
    28285     1109.090909
    5 }' D0 J2 w9 S3 ]: e! b- R13        1109.6774193 d4 q9 {) L. J# k
                 ...     
      Y7 l2 H% f' {26998    16764.705882
    * `7 t( p6 x* `5 J27457    16928.9719636 M/ f2 y+ p3 u3 @) n
    27226    17077.669903
    9 h. O8 B" q+ a9 D! W27530    17083.177570  [. ^6 |! F+ P# G% o4 o  D4 @
    27635    17828.846154
    ' }9 C, s; g9 u& Z1; a/ B, d! ~  O+ A1 ]
    2; j( n+ W* E0 }
    3& A2 L& X2 F9 U
    49 r; R: [+ q8 w: h
    5* v7 ~1 }- Y) ^# S5 `
    6) V0 n. _9 m! N/ n& {
    7
    8 k# y' J2 b1 j" k9 D. I4 U8" \! i4 h% s8 L5 w
    9
    + k& N6 W6 s( D4 q10
    $ N2 `5 [9 l  x) U) s: D! n' I6 ~11
    . M8 E) H$ e  _" y12
    % ^/ V# e6 k% x, I& Z; l2 j对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。& s& g1 o) O+ m
    # 分割时区间不能有命名,否则字符串传入错误。
    $ F8 b" r& s, |; p; @* b6 Lid_interval=pd.IntervalIndex(
    # _5 B& K9 r- R& q: r    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]  H1 C; s5 ?+ l9 l: y! R* C7 F, O
                                )& }. P8 \9 O/ e4 v
    id_interval.left# ^4 A3 {3 I' O, @7 h* k& e1 m
    id_interval.right
    1 t  f& B+ W# y3 Z) ?9 iid_interval.length                            5 u0 j8 f- E3 h/ l8 |/ U: {
    1
    ' y2 x. ^1 I% t0 y( K2
    6 _) _: K4 d# i3
    3 b! s' l- f: R, F" E1 v: d" w44 @, ^6 n( {, x* d. {+ S) N- G
    5
    0 b6 m+ E. t' C- z/ N2 N6, g1 ]7 \7 b. \0 _$ V2 r( F
    7! D4 b, {$ H# ^
    第十章 时序数据
    * @  X7 x! i# cimport numpy as np
    0 F$ X+ }" \% n7 ?7 aimport pandas as pd
    + g! m8 Q5 a( \' @! {. q3 Q: S1
    4 v7 @' ^+ R$ J2& P$ G3 G1 d& @
    . x# L8 r1 f1 y& b0 x" O0 {

    - M8 u5 Q) y6 M. m10.1 时序中的基本对象
    9 w+ H, C$ s/ a# A% O# {; L& {  时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?+ e; W1 f' I# F2 g, ~) ~' Q/ E
    . z5 u1 k/ I1 B+ I
    会出现时间戳(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的简写。
    7 s. s7 c5 E1 u* n2 d# m+ R7 \/ T3 g$ e5 u, D
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    ) P  O2 C  X% Y; L0 _# z' y/ l2 O8 o, y4 Q) N
    会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。* N  M9 C* C+ G( ]  w

    ( s9 ?: S* n5 G% u& H# O% _0 N- t, g会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    # V7 u. r5 h# x, O: Q7 V
    1 L7 _9 C" P& y( M  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
    ) Y% ]4 n/ R! j: y$ F( D, F- W' X5 L4 J$ g/ I  z. U
    概念        单元素类型        数组类型        pandas数据类型
    ' u! E, D; T& K- S7 {+ rDate times        Timestamp        DatetimeIndex        datetime64[ns]
    3 {% V7 z4 |5 ^. z5 q0 Q+ {2 lTime deltas        Timedelta        TimedeltaIndex        timedelta64[ns]0 A  G5 @! Y( ]/ f7 b/ H, n4 _/ `
    Time spans        Period        PeriodIndex        period[freq]  E; {9 x, B! W2 R. w
    Date offsets        DateOffset        None        None, Q' B8 V$ G2 h6 x2 I  D
      由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。3 h9 [, T* v% t1 |2 P) F5 \! _+ ~

    : o7 ?% }7 c* H10.2 时间戳0 \% R$ `9 V  F2 t/ e" p' Z% \7 E
    10.2.1 Timestamp的构造与属性( ~+ r) Y" f4 ^+ z- L* R  ?
    单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
    ( ]+ n0 Z) ?7 j
    / m* }# \# k/ r: r- Kts = pd.Timestamp('2020/1/1')
    5 z* H* {1 P) x, E6 R0 o8 T: |' J% n  V& c1 q: j. U
    ts; e, K) q6 M! J  p! E
    Out[4]: Timestamp('2020-01-01 00:00:00'): `9 _# N7 _3 s, B
    + V! G9 C" P+ u2 ?* R8 u$ V) f
    ts = pd.Timestamp('2020-1-1 08:10:30')
    * e" A7 y5 W9 v/ |# h
    8 `, }- |% [9 Pts) y, V# w/ q- S( z# F( F
    Out[6]: Timestamp('2020-01-01 08:10:30')
    ; t1 {* B+ z! X9 b/ n- Y2 i+ K# Q1
    5 D# H% \8 j  k4 p- f% P2
    * I9 }  {6 I' I; N' C4 F' e- i3
    % l# i# d5 W" j/ M3 N0 \42 h$ S* V8 w9 \; }/ o( m4 |7 i
    5
    5 K' B0 ~$ I$ Z* r6" ^4 e% j+ S4 ^* @
    7
    % c- p8 z/ V$ ]8
    - r- x  w' ^4 C( }7 G9
    7 d- B' h7 q4 u9 F+ u通过year, month, day, hour, min, second可以获取具体的数值:
    ! a7 Y) S* `8 [1 t+ O& G
    8 Z% E. F* l3 j3 D; b0 lts.year
    ! O, P7 t  _2 y- Z6 v) p' p& gOut[7]: 2020: ~7 P, v/ U5 e2 t' A; U

    ! x. |, ^# H1 y: }! }% _6 hts.month2 J- c( z0 B$ H: [  r7 Q9 \
    Out[8]: 1
    / O7 R& b+ d& i# p  {. I; Z. J# G9 o( s6 ^% I
    ts.day
    / l  W0 K- h  aOut[9]: 1) |* u6 J9 L4 O6 Y# ^1 O

    * Z7 E7 w3 }( Z* R% Q1 fts.hour  `  S0 \) t. N0 a
    Out[10]: 8
    # h  L5 n" _3 L' s0 ]1 i! }2 f, R9 E: H5 d
    ts.minute
    2 ]$ F$ r3 f9 @$ qOut[11]: 10, c& a" e* J4 }5 X9 q( I9 e
    9 N! s" e4 ?7 `) G* I
    ts.second. k( l, e  [8 l6 O+ x9 u6 M
    Out[12]: 30  y& x) _6 b3 _& s$ l% I

    ' g! `, ?% U! }+ r( H$ [/ m4 j1
    2 `# \+ n- |& U5 z: m4 {2; {! j( g; P0 F& f
    3# G6 {: b5 O% m, M- r* A
    4
    9 d$ l' g9 K2 g2 D$ f9 u5
    7 h  |  J8 v; T! y6& r( E9 O: r& x
    7
    1 X0 h! w/ |2 a% x" b9 @. `8
    6 O7 a2 w5 y' C: ?9
    ) k" q* @; q0 v  T! Q8 H( \4 g10
    % q+ `; T1 I) @3 k11
    & d+ ^' k. ^. i; Q" g& T12# Z. V. D2 q, ~8 J' l8 |6 B4 l
    13
    9 y6 g' T  r6 l14
    * R; c8 @% c" u/ ~; v; W15
    ! k4 ^) N6 ~# h+ Q$ L$ [) }16) B4 G. u! ~3 d' S
    17
    7 d" ~5 d6 v. Q! e; S# 获取当前时间- W4 `" U( O% C+ D* ]9 @* Y* r* h" t  R! G
    now=pd.Timestamp.now()
    * \5 W4 v. k4 W! B7 _# L1
      v! J( K8 q8 N" d1 n8 G. |6 [2
    - o+ n# c+ I- O) A/ u8 o* Q在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    $ k! p7 B$ L, F3 jT 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)' y2 K- R7 d" l' @8 J
    TimeRange=
    2 l. F! o9 u4 c8 |, y10
    ; v3 t" X7 n* f$ R9 a5 ~9
    : e0 c& J  P! x6 j  q. y ×60×60×24×365
    ) X$ U2 o2 Q# S5 [# ^+ L$ j6 l5 B) q2
    / F4 E/ X1 C: n% f8 V5 G. c) I64& S) p* E- C" P
    # Y* B7 }7 t( H/ U0 `
    * L# p# E8 f' i3 e9 P8 L; r3 ^
    ≈585(Years)( T0 q% g; o* F, h+ U

    3 H, n& Z( O0 h  m, ~通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    $ F6 v& F# F) [" O7 g6 \# |' Q
    ; H$ I4 A3 S* Xpd.Timestamp.max
    1 O# o4 e$ W! H. p! Y+ i: EOut[13]: Timestamp('2262-04-11 23:47:16.854775807')2 F' r! H. h5 j- e1 w" ~" F3 {/ n
    8 a7 t  `* ]) k# `
    pd.Timestamp.min
    * g& V8 ?3 y6 N) N! `2 G2 qOut[14]: Timestamp('1677-09-21 00:12:43.145225')' D3 r8 [9 C2 X1 j. d: u
    7 G$ w, I$ \0 }$ ^
    pd.Timestamp.max.year - pd.Timestamp.min.year
    8 s0 m8 F# y8 d- S; S! e$ {Out[15]: 585# \+ M/ F8 B4 e, N
    1
    . F# Z$ v/ H0 Q2 Z2' K& a3 I/ g4 w* _4 F4 a( D
    3! w% u8 a3 p( J- F3 u. r/ ?
    4
    2 n5 j. P+ O" j) d5- `8 |6 }7 s, B* U
    6
    3 j. [/ N6 `8 {& e9 H70 x3 d4 P7 b. |1 E0 i/ ~
    86 ~6 V+ t6 N0 a8 }
    10.2.2 Datetime序列的生成
      s1 ?1 w  l# G$ r; v4 I$ w2 |pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,) {+ i5 |" @, F5 O# A
                                      exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)$ z; k8 b5 @5 t5 K: q- C7 |
    1: r, G0 p3 U4 d/ |* _) b
    2, g8 w% h, p& m7 V7 B
    pandas.to_datetime将arg转换为日期时间。
    $ Q- c! j0 J* \7 O9 ?) O8 l: ^
    ! W/ q7 {6 V3 B. ]3 H0 Q' w! Darg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。4 S& k5 P& k% C4 j$ I& u
    errors:
    - v! q' ~6 p4 h) m, o0 a- ‘raise’:默认值,无效解析将引发异常: }7 @0 b7 i9 l
    - ‘raise’:无效解析将返回输入8 a4 ]2 F  _& K) j; Y
    - ‘coerce’:无效解析将被设置为NaT4 K  _  [5 _& |, K% {" q& V; g$ g; Q
    dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
    , [: T- T+ F% M1 \4 [6 Wyearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
    $ t# j$ }4 B0 ~( @) C1 j7 ]* `utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档6 z0 b6 P) c$ b- X5 a9 D; D# D
    format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
    . n+ {, |8 s  r+ C" e" }unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
    9 j* o% H% y! K% L) Ito_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
    ) x( |6 P" t4 {2 m% Rpd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])' E" R( |- O! y# F8 C

    . m, B. }; l2 D+ vDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)0 n4 r+ ]& z2 {* a) P' e
    1
    . _/ ]: S( R" P- B. V2( z6 G  R7 {9 B- `( Q0 ~
    3
      V) ~- ?: Y# b" P$ b) i在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    8 l: h; O' p' H3 U2 v" T5 _4 G" Z" x4 ^) ]9 E9 f
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')* j$ ]  |$ i' ^4 z8 ^2 [' \
    temp
    9 s$ ]" h( o# m6 n: s' ?% ~& K
    1 j' x' O* t+ ~1 T/ bDatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    $ S7 S9 h( ~/ u4 Z* K( [3 {1
    ) [- X3 n# @9 J7 z% F0 I3 C2" i% z# G. L9 S0 @
    3) w5 ^5 q4 e' k1 u& i2 Q7 c, C
    4
    9 S$ O* u8 F8 `* O0 m. }0 s  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:0 Y, a& ?- X& z5 R: V

    1 Q. m4 A' s. j4 zpd.Series(temp).head()
    , r5 v- r6 K/ Y4 _. H/ @7 i$ r0 v$ `7 o
    0   2020-01-01! C. j, G& f# c1 `+ _- T# ~
    1   2020-01-035 K# C( y: d' y2 @* l( s
    dtype: datetime64[ns]2 G- O6 w8 ?: L1 x9 c& P- y( y7 q$ X
    1" s7 I$ Q- T9 i, ?5 }3 `: L
    28 j4 x3 ^3 ]+ p. O
    3+ l# m- J$ x0 M$ ~! r5 k
    46 K7 j! p/ Y1 f0 D7 X/ ~7 r
    5
    ; H6 n: b" @( K: {- L下面的序列本身就是Series,所以不需要再转化。
    % G! {' |0 y3 U
    / c& x! D- I8 @/ Tdf = pd.read_csv('../data/learn_pandas.csv')( x- ~: D) p8 y$ X, U
    s = pd.to_datetime(df.Test_Date)" l$ `& ]! o$ p) ]% I
    s.head()
    ) P/ y  n6 V  W2 v8 f
    9 x7 r6 v4 t# D1 B/ [3 L0   2019-10-05
    + d- k4 l$ d/ G% {8 }1   2019-09-040 P/ N) S7 e8 D/ c1 }0 K6 p: U
    2   2019-09-12
    - j4 ^' d& G1 e; m. }: `& l3   2020-01-03
      R! n0 b' M- ~5 E4   2019-11-06
    : ?. e, C9 V8 \Name: Test_Date, dtype: datetime64[ns]# k- ]  \; V/ C' B
    1- L7 g2 b5 T' J( N9 E6 x& M
    2" \$ H" G" g( k! ~% m
    3
    2 @4 Y+ Y- n6 Q6 _4 H4$ f4 t5 V) v6 ~+ Z0 d  h. |
    5
    ! ?& p* f7 Z- |! s6 o6& }! S/ z4 }0 ?7 o5 t
    70 u9 ?' L8 H$ i6 u: L( S' n
    85 a7 Z6 k. F+ A; h. w% |, b/ g
    9) `: P8 {- b! U4 g; G
    10& ~* p; Q- r' o* Z/ a( D: i( s
    把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:$ u0 _6 Z5 _  Q: |5 ~8 [
    df_date_cols = pd.DataFrame({'year': [2020, 2020],
    # S8 O7 `- @# a$ ]$ p                             'month': [1, 1],; p" j1 z) ~% X- {. e) h+ g
                                 'day': [1, 2],
    ; o+ O# e: B4 U  Y                             'hour': [10, 20],
    % y6 J& J  u2 B. B                             'minute': [30, 50],; G# \' c' f2 X+ {2 d
                                 'second': [20, 40]})  N" c2 e* H* Z4 ]0 f5 D( h, s
    pd.to_datetime(df_date_cols)6 z$ M' S/ D! [- ]" H0 N
    ' K6 \+ A# G0 v5 b
    0   2020-01-01 10:30:202 F/ f, q. X8 E6 ]4 N) H* F. `. ]4 a
    1   2020-01-02 20:50:40
    ' z. j, k4 r7 F& Zdtype: datetime64[ns]
    8 r" Q/ R' `* w8 A& `3 \1
    $ c; a/ x" l8 g- u" j+ M1 u2
    $ C! I+ v. r7 [3; {# X4 n/ d0 _/ x
    4
      M$ w; p# Q$ @5
    ; W" n& Z( C; H& `68 D" S4 `  i/ m  g/ J9 n7 D
    7% u. f" v2 P7 _6 q3 f. O/ c
    83 e! P, s& B- v: L0 Y5 A7 A2 N
    9* x" V! |1 c/ `4 p6 J9 {
    106 b) N( d7 f- H& I0 y& @
    11
    3 q) r, u! p9 r" l# V; N! F& }5 Udate_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:2 I4 j' G. T* T# F) m
    pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    - r  w3 Y6 P" e" a6 AOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    * `2 d. h4 y/ G' r: Q% Q
    ) x3 l/ R8 k/ I; R/ wpd.date_range('2020-1-1','2020-2-28', freq='10D')
    5 ?# t/ _7 r8 QOut[26]:
    6 U7 ~: B6 W. Y% t# xDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',) n: R* ^/ |" `5 }1 I
                   '2020-02-10', '2020-02-20'],3 J6 X$ V# R4 Y' ~4 n
                  dtype='datetime64[ns]', freq='10D')6 e5 t- S" i8 O1 X

    ' t( H: _; V$ B9 J: @# M/ qpd.date_range('2020-1-1',/ X  [& r% w" i$ G% h
                  '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天* q6 f! x. x, q* R# T
    ' ~/ J4 G0 W# l6 }5 L
    Out[27]: ) X5 |: g- P) P; c
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00'," s  f; c& `6 ^' Y: L$ ]- k
                   '2020-01-24 04:48:00', '2020-02-04 19:12:00',
    & h5 z) T' h* `' Z0 O9 Q               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],8 ^6 Z9 |) S9 i' L3 _( R
                  dtype='datetime64[ns]', freq=None)( s6 s/ u/ J. E3 R' J
    & P! W1 s3 ?$ n9 Z* n* h) r* T
    1+ h3 z) z3 q% N. A9 u6 C
    2
    : _* X. D: i) [1 c. y+ B; y2 {1 `3
      n  y" e- p$ L4
    5 z1 K: W: H# e2 F5
    % [- N3 x" |/ M+ u- [6& W+ {0 \, I" X+ I- B- m( h
    7: u2 M/ ], m$ \
    8
    3 E0 _  D. h7 D+ P# O, m9
    ! {( Q& |" G  Z# T  E5 Q5 R10
    " V8 x9 Q: U7 s2 S& m  Y- q11% a* }  Z8 ]( k
    122 M" S* x: I. {4 _# z2 h8 [2 W7 o3 g
    13. k  F5 `4 X: D
    14
    / d0 x; `/ m0 k  q$ s15* K5 `+ L: F0 b9 m% m
    16; Q- d8 N8 w4 q. q0 ]: A
    17
    " M, z/ ?, ~  @; H4 [这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
    3 E- `* S0 O  ~% H8 H; X& l: n
    【练一练】
    ! A+ P7 ~/ C- N7 v, i. r( jTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。6 W8 Z9 u5 e$ b7 q* J4 `. ?5 ]
    % t/ Q$ N  d; c8 m
    ls=['2020-01-01','2020-02-20']
    5 K& |3 ^! N3 Y3 M: Qdef dates(ls,n):! x0 U' m6 W# q$ u
        min=pd.Timestamp(ls[0]).value/10**9" U3 {$ t/ N! U1 ^; v
        max=pd.Timestamp(ls[1]).value/10**95 K! c7 r/ h* C5 x8 `
        times=np.random.randint(min,max+1,n)
    / R2 b  b$ Y: Y9 X/ ^    return  pd.to_datetime(times,unit='s')
    , q' I& s% {8 ^' C% _dates(ls,10)
    + M- L9 J* z! r1 Q
    3 x' r; \- F( Y) X$ }6 `) pDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',) Y+ y& `6 Y, ]5 g2 Y) G8 Q
                   '2020-01-21 12:26:02', '2020-02-08 20:34:08',& O: a) ~! e8 ^$ l
                   '2020-02-15 00:18:33', '2020-02-11 02:18:07',
    ! n6 E* i5 d9 F! j               '2020-01-12 21:48:59', '2020-01-12 00:39:24',! {! ~0 p, K7 b) q# E5 h
                   '2020-02-14 20:55:20', '2020-01-26 15:44:13'],# b! a3 U' j; b6 W& Q5 u) N
                  dtype='datetime64[ns]', freq=None)& J5 I) ^1 v. d# K7 Y$ ]
    1
    6 V0 l: U+ u. r$ A$ o22 d% N4 M7 ~# i
    35 _" N% A! v6 e  ]0 l3 s$ R2 A
    4
    ; ~$ Z* m: _) l# S5
    8 u3 @- h6 O7 `5 Q1 I6: p2 ?$ n6 H$ [
    70 u4 V- S+ i: {0 y6 c+ T- B
    8
    6 C8 O- O  s: f1 u97 b: D, g; K* m3 L4 `, S5 G
    10
    ! `, v( u/ P9 B5 b$ d11
    8 ^* J$ Q& |- M) P9 ?" U: f# K12
    3 c3 {; `4 X$ Z% K% h: ~: B13
    * D4 q. {( E" s; |4 r7 t! l14+ i7 f+ w4 B4 t0 U. o" j4 v3 N
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:+ f2 E+ A4 n0 J- M9 i  s* ^& W- ~
    s = pd.Series(np.random.rand(5),
    ) a, s$ Z. X  l3 K            index=pd.to_datetime([/ F$ q$ N( i3 U% g5 T
                    '2020-1-%d'%i for i in range(1,10,2)]))
    : H1 r- |% N! L9 x) a& r# S4 k5 Y& d' S' }$ L( r6 U( j6 T& i
      P  L2 @. y. N3 d) C
    s.head()) [2 S" m! p$ j5 ~9 H
    Out[29]: $ E: u; H) P; D% E9 Y2 s1 T# c
    2020-01-01    0.8365781 k" A7 r* h4 v* ], P+ F
    2020-01-03    0.678419% B4 y) r: e: n. z
    2020-01-05    0.711897
    : H- L- o6 s, \# U6 p2020-01-07    0.487429& V, h3 w7 X5 H3 L3 G# p) U
    2020-01-09    0.604705" n" o% f8 S- b  I4 i" R" A
    dtype: float64
    * U" b9 S: k5 X- k5 _7 b5 `/ Q* u* P
    0 H: `! Z* @7 e- n% Qs.asfreq('D').head()
    / K% ]: M$ ]7 a- }0 dOut[30]:
    $ Y0 \+ H# q  ~- P* @6 B/ `2020-01-01    0.836578& ]9 X" \( ?( R* T
    2020-01-02         NaN& [: F2 a9 N6 Q/ K
    2020-01-03    0.678419
    + g# i  D+ ?9 k  G5 W* ^2020-01-04         NaN0 s6 ^* J: y2 M  d
    2020-01-05    0.711897& Q- b2 S) h( b0 I& I
    Freq: D, dtype: float64
    / O5 `8 F+ i3 Q
    - V. B. z" f4 q! |3 Us.asfreq('12H').head()
    1 b! h3 Q8 ?5 V3 S. N; I4 \Out[31]: $ S7 w  v! ]: ?* o, y
    2020-01-01 00:00:00    0.836578
    9 @8 e, W6 P9 E7 I2020-01-01 12:00:00         NaN
    6 H9 R6 J: S7 m2020-01-02 00:00:00         NaN  D1 q/ h5 ~& n, O+ `
    2020-01-02 12:00:00         NaN7 u( Y) M0 H& P& D
    2020-01-03 00:00:00    0.678419
    3 D+ s, |! f. Q) BFreq: 12H, dtype: float64$ v, X# t) k# h% V1 w# u

    . B! F" f7 [5 o- z) w7 n15 l7 m# T0 [) C7 V# f9 B
    2
    + y6 T0 M. s2 Y. v5 V5 Q6 l3
    * [6 r* n; B& V7 m( p8 I' H5 S4
    . e% f# p% d  V+ M: ]5. k6 B0 K" F; x* C1 c
    6) ?7 M* _: p$ o  z5 t* u/ W/ q- b# G
    7. L: O' L4 k9 a3 P+ j
    8
    6 b) o3 b# Q. N* r' g& }, y1 D3 E9
    ( L; L8 {$ l+ D) O% o10$ Q% h6 ^; p6 G- v3 W0 n( Y( e* Y
    118 `6 G! }( k4 w' z: Y
    127 v8 t. v5 o1 W
    13
    9 o% {  Y1 l3 J6 n% Q" ], r& C8 {. h14& }6 Z, h. |2 e4 W
    15
    0 b- D: z+ ?: s& U: g& L( O16% Y! o+ }* O  _9 e3 c4 s# o: l
    17
    5 L! E9 |, H  s; c1 d- a( k8 C6 ?- v6 D  ~188 \: S6 B, R9 s: `
    19
    2 u" ?% X! y" @$ L0 h$ ]( @5 e20
    + ^+ z' o3 C+ [. D6 m2 s21% [% q9 z# U$ |- E; B
    22
    ' W0 K* H. S* C$ D. B" h9 ^  E23, E- E+ j' m  h" ?
    249 E, g" R3 m. t$ @8 O# q0 j) ^- E
    255 l, Q2 w' x0 L( ~6 c2 F( b
    26
    . j* q0 v) I1 T8 C8 H+ s27/ S, e# ^7 P# F% c3 }
    28
    6 f% W& L  ~" d! t5 c! O2 k29
    # d) B% ]( k5 b# `( K303 C4 K( L/ [. x# z* `/ A
    31$ q* h4 v: v4 }8 k; _8 J7 q& Y
    【NOTE】datetime64[ns] 序列的极值与均值
      x9 G7 ^2 w) P1 ]! y9 S4 v( H  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。  s/ ~" O2 c& s0 d4 D& L3 i
    - T& K7 V, Q& d* Y8 V3 ~6 Z8 m
    10.2.3 dt对象3 u/ l1 R) q$ g2 x8 z
      如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
    . u% P# M; W1 e, b/ }5 g/ |& E2 k
      n' \3 M3 P- @0 ^1 p4 g第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
    ( y3 [2 j4 q7 j/ S# Ss = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
    6 i3 \8 g% E: n7 A+ [- f, E9 O9 {* s/ m( Q; W- E. A
    s.dt.date
    7 T7 [9 @+ w8 ^Out[33]:   d- u; s# H9 V6 ~
    0    2020-01-01- I! Z3 u/ P, K# J! m
    1    2020-01-02" b& S9 c- d. L/ w3 _( g
    2    2020-01-03! O$ `+ ?$ b- u+ `
    dtype: object
    0 y8 M: ~$ E- z( ~: W2 D9 {9 X6 u& l8 V* ~* q6 [0 K. P
    s.dt.time+ A6 P/ p% m$ U+ d' e
    Out[34]:
    ; W/ w' `, ]" ~- [' B) N2 [& V0    00:00:00
    7 v1 K8 [% g6 Y# h9 W5 d* T- I; c1    00:00:00
    # d9 U" m- a+ z/ Q2    00:00:00& R2 H5 ]: \" b( r
    dtype: object9 h- T3 x1 F  b6 i2 t

    8 L2 l. S" S2 I( J: n; as.dt.day& d; u" ?2 w' H+ |
    Out[35]:
    " \- a1 w( I  e* I- m* J0    1& k$ j+ a9 D1 `5 ^: e: U& x
    1    2
    0 Y' _* E3 m) [# v. X2    3  Y3 B7 t4 N" q- |4 f5 U
    dtype: int64
    * t3 f! Z4 X5 Q$ K& q: ^; R- u  i, |, E: R% v, F
    s.dt.daysinmonth
    9 ?! T) t* ]& x. c7 V5 m/ N* KOut[36]: ( X. B/ \" J( U: B+ }
    0    31
    . Z5 F& i( x4 p: h7 N' q  i1    31
    ' T+ m, w+ T5 z: A0 E& E) e* H2    31" n7 {  L2 O/ S' v% {# C
    dtype: int64
    1 x% r) \! z, K8 v! O. p$ I( G  Q* y
    1; m) d3 b  I. A
    25 D7 u! B2 p. t, P  w
    3  p; g+ `4 J; M$ p! }
    4
    $ W: s. o* R7 C0 u4 Q6 r5  W; ^! m4 z0 K; [" W
    6
    ( V/ d( }7 ~# d1 G# I- p/ }77 \! |1 {+ Z4 W' P. @2 R
    8
    # b) X7 l( A& e2 M( _1 w0 q9+ I5 ?# u2 M: y8 a/ I7 L$ F1 ~
    10
    : W) ~  d4 M! ^4 r9 Q6 w11+ B1 i7 j0 [8 X$ d9 U! n8 A7 b, y
    12
    8 m: p1 S/ L: E13
    8 p6 Y, }/ |$ [- u14
    " m6 Z* r6 O2 |/ \1 E8 N3 x15$ Y5 ]1 [: h3 V. {, r
    160 I1 {8 \. D( H; w  c0 H# G
    17
    ! L1 q/ ^0 c) q18" o9 G. Y! s! C% M2 \
    19
    8 ]/ r1 h* x1 c, M9 P1 w' e6 {9 h20- ]3 K8 G2 H- N3 ]. W' z% e
    21
    3 {( z* H2 y; T+ _5 _  E) d22
    8 X& {) h! A+ Q23
    . G4 V! m3 a8 _, G+ W  g24
      B* R* N6 }/ T5 _25
    3 s; B/ d5 W  n/ V. s% a. T26
    2 r# C/ _! p6 Z8 t0 I27
    1 `+ O0 }9 J. f28
    8 ~4 \3 P4 p0 b+ `% U29
    3 Y( Z4 ~1 e, |8 c  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:" X& z& b+ Z+ R
    9 p0 E& ]* Q8 M5 o# ~# ?: @) H
    s.dt.dayofweek
    9 E( e$ U/ N9 v7 c$ i4 ~3 YOut[37]:
    * ]9 v& R, w: J; X. a( k) ^$ R* f0    2
    5 @4 g! }$ i% U1    3
    - L2 R* z+ e0 Z. q! D# S2    4. y1 I+ ^! `0 i& d5 Y
    dtype: int64
    ) r- ^0 O8 Z; Q8 [+ o' x! T) |2 b1 x' I8 c& Q8 o# J" E2 D/ Z# r) P5 L
    s.dt.month_name()+ L& @. g! x0 M3 @
    Out[38]:
    : \6 E: C: m9 }  c6 [4 c0    January7 V$ y0 @* }" U* a
    1    January
    ) `9 B/ w5 `; H7 V2    January9 P) k( v3 ?7 r6 }& D
    dtype: object
      U8 v& a" T" l# R% w9 K1 o* P3 S" b0 c. v' V8 l4 L% L# [
    s.dt.day_name()
    2 ], A* z4 P& X& o% `$ y' V$ N+ POut[39]:
    ; `& g; t+ y2 a$ K' ]5 \, ]0    Wednesday: [- v" i/ Z: J' K$ o2 U9 [0 @$ U5 G
    1     Thursday' e0 z) P/ ~- S
    2       Friday
    7 _7 d( P! o' ^( o, Udtype: object
    ) x2 {) i; G6 T, E$ i# W. ?
    ' S) G, J, L: o0 R1
    4 N3 _% _: O, v$ b8 \( `8 W% R2
    2 X1 `7 Q; _2 o8 x, q: t, x& D; O0 ?3; D/ ^% t6 U$ k' a- [: T, {0 E5 }
    41 s' w5 W# Q" p( ^7 l
    5
    2 M# C/ c0 Z: n; G. T6+ e- G% Z) G* M, u
    78 T7 f7 i# ^! U! p' }% q+ p
    8
    . w4 D1 [. e) `5 }6 i7 j# u9: A+ W' F+ k* B/ |1 T$ c
    10
    . l+ S6 @- i7 g2 Y7 t: |1 L11& i+ R5 G9 U4 y
    12
    ; H8 q) {6 b" @* P1 G+ p  ?8 X13
    + b9 N. d. |7 }* i14
      _# u4 |1 T# b. W' d2 \" g' `15# b$ W: x; _# j/ M) W  u
    16* ^) ^) _! Z9 Q: B( l. [2 z
    17! r3 I( K$ g+ ^* S1 F
    18- {" K1 Y! k) p& m, A
    19& [  V% w$ f! L0 C6 m* U
    20, ~8 [' f& m! J4 u2 H* t
    第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:$ ~" U. q( h0 L) H
    s.dt.is_year_start # 还可选 is_quarter/month_start
    ' G7 R+ Y. m5 u4 x% k! ~, aOut[40]:
    1 c8 `* Y$ N; ]5 G& o0     True) n& l& M# ?2 B! Y* a' J3 @6 W
    1    False: w9 o+ ]9 M9 U3 r" q! Z
    2    False; o/ M5 Z: A/ `1 k
    dtype: bool% R! ~1 P4 Z5 L7 @  G) [1 y1 S

    . }- ]' H& z' e3 [s.dt.is_year_end # 还可选 is_quarter/month_end
    3 s2 f1 A+ \2 U0 A" D$ M; ]Out[41]:
    3 F3 l9 i) z! o& k# r- p' O& i! f0    False
    " s! d$ I% O2 p* @- i& e$ `5 K2 E1    False' j% z, e4 \' ]+ K! |" }. g$ {3 K
    2    False# u; Z% q: @1 h* k  S1 s; D
    dtype: bool" A9 p& g3 R! r
    1
    . c" Y% _4 t7 Y: @+ G# A0 v2
    : y% b! {/ {+ f( g8 H% [37 t, D& X3 r0 y8 n1 Y+ f6 d
    4- O+ `& h! x: J. [  p! ~  u* X
    5
    8 E! {+ b! }8 Y. \# F60 }/ B9 a: L1 c6 ^
    7
    9 }9 [# o& I. X9 |9 s8, S6 r2 G9 `$ T5 Z9 _/ S  L
    9# k& p. E+ A4 \6 |% `  Z$ `, e0 O
    101 O, [9 k5 ~9 m. a* s  Q9 _  v: o
    11
    6 r! {: Y$ M$ z4 q8 `3 G12" a# U& V; W: h. G: O
    13
    . F2 z% t* M" Q/ ?第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。/ h: n  K( ]3 O! R4 `, V( A
    s = pd.Series(pd.date_range('2020-1-1 20:35:00',
    . B; Z/ `0 H8 Y! l                            '2020-1-1 22:35:00',
    ) {4 i$ T, X* V9 C7 y# y6 o* c                            freq='45min'))
    ) D+ n+ _% \9 {' _* l$ z4 m- H6 i' _" p' U' u( r

    ' |% A  m) a$ {2 U$ O: k( G+ ts
    & U: |, W- k2 Q' TOut[43]:
    2 e4 e* w" g) I0   2020-01-01 20:35:000 t2 _" R  \" l1 H) y
    1   2020-01-01 21:20:00# t* F$ F$ s6 M
    2   2020-01-01 22:05:00
    3 E+ U" y! q& `8 f, H7 Xdtype: datetime64[ns]
    6 v* d9 H' D; t9 v2 h! e& ^1 C8 t2 J, @
    s.dt.round('1H')
    & n% o4 Z. L- M5 N+ ]4 pOut[44]:
    " r, g' I4 ^3 f: [/ x- e0   2020-01-01 21:00:005 ?- J6 N  w- K9 O- Y& h! [
    1   2020-01-01 21:00:001 W3 y) n2 d" F7 V1 O
    2   2020-01-01 22:00:00! `( u! o- A* H' W
    dtype: datetime64[ns]$ R8 ?. S  q# Q" a" N2 ?
    5 I/ e2 }8 d  m- J. h. F
    s.dt.ceil('1H'). R8 d+ ^6 |/ {0 g8 r
    Out[45]: ! q, Z/ U" r+ [, w! M
    0   2020-01-01 21:00:00
      e% y3 z' i+ A+ n, u6 ^; j: b1   2020-01-01 22:00:00
    * I& @& B0 x% M6 b- n9 |- t2   2020-01-01 23:00:00
    2 @9 j; W2 g! T' l. ]+ s6 Udtype: datetime64[ns]
    . O) V4 ~3 M, g
    / h) J+ v/ b& t3 V! gs.dt.floor('1H')
    - Z! w+ e) [. m% s% G, AOut[46]: $ d" J# b+ C7 ^: T, F! \, C
    0   2020-01-01 20:00:00  Q# ?/ t* s7 T
    1   2020-01-01 21:00:00% M- s* n( q- L  [* c* F
    2   2020-01-01 22:00:00
    2 I( q+ Y/ ?% G. udtype: datetime64[ns], r. ^6 U5 v1 n3 ]% I
    / K3 v, A. Y& A5 Y3 j
    1
    / M1 P# I# e9 X# O& t: a2- q  m" B( K" M5 r
    3" R/ q2 B( }, J/ V
    47 y: s2 T$ u. r
    5
    - a8 ~! h6 a2 e/ j; A" X$ t63 p+ s; }* C8 O1 T
    7
    8 b- B0 w) B) H( Z$ S9 N8
      ?3 G; t* G' Q1 H2 |. B9
    1 U; B7 Q6 Z& C8 }: P. H( N10+ h: [/ E" q' E$ r% ?4 M
    11
    2 R4 g0 p6 Z* Z: `: Y) a12- \! |5 {2 [) I
    137 v: k+ m+ V+ X( T! t
    14  U6 Q) E; S) t" D9 i7 N7 ]0 Y# k$ K
    15
      k6 l. ?: u* t9 C( p" `16; t5 S8 y  W- M& x+ t/ ~4 t  H
    17
    ( ~, f8 S$ a2 `$ ]18
    * X- N0 F( \9 a& @" P) L19
    2 P+ c7 t- Q5 v$ g1 q1 F20
    # R: Y$ {2 {4 l' s21
    ( |! k& N6 K. B, I$ ^22, n+ |- `* w% L" Z8 V
    235 w/ V% X& p/ B: a; x% n
    249 I2 c( Y# g/ [$ {9 U) F5 J
    25
    - _3 q! `( ~' ?/ I& ^26
    5 r/ @- z' W0 p- K/ [1 z' e27
    - |# a' u8 P% |' \. s5 \( K7 k  V28
      i* v* N$ h0 V- r: b29, U9 W2 i' |1 M4 d  ~
    30
    ' n# p: t$ q' D2 \# O31
    ' U$ J' U" |" t322 v$ N! `) c( ]4 W5 {6 g
    10.2.4 时间戳的切片与索引
    " d8 \1 l1 ~+ h  }  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:% J5 l$ r  v* e+ h
      G; g. i$ t/ O) Z7 Q6 d) i9 f
    利用dt对象和布尔条件联合使用9 g( k9 U2 c3 ~% S" q
    利用切片,后者常用于连续时间戳。- C" b, w  m8 b
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))" ~% L; ^1 j+ r
    idx = pd.Series(s.index).dt7 x4 U5 \3 _; F9 g+ V1 `
    s.head()
    * {* N6 @: H% \8 b2 F' `3 s) q* e3 h2 L: M: j7 n: v9 q1 \
    2020-01-01    0
    7 M. K2 V* `% V, v2020-01-02    1
    - o/ a/ `. Q- {# E' i+ m2020-01-03    1
    4 I) i. g- T" c& ^4 p2020-01-04    0
    / W4 D4 V/ O, d  n* V0 v6 ^2 o2020-01-05    06 `6 r9 L+ {! v
    Freq: D, dtype: int32% w9 y4 }$ |. F7 ^
    1
    8 b- k7 r: \" c  G# n. P6 h1 Z2* S2 Y6 O% @+ n6 x8 K
    3
    : i& I0 w6 v8 u2 ^, |4
    , h" U# m: ]/ C0 a8 o4 q% p2 B2 M5$ P9 K. j5 ~" U- G# O
    6
    * |9 q3 C2 u: t( e# X4 T73 W. X' ^/ G; y5 _9 v
    88 r8 [' z8 r- i0 a; h- s" ]
    94 ]4 \* t, g! b0 G7 K% J0 E
    10
    7 D' x' ~, N, r5 d" C: ]6 }Example1:每月的第一天或者最后一天) ~0 `$ P! p2 o# @4 M/ e% C

    ! h8 p+ K+ `! R: F  O' C8 ys[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
    % G8 K5 y; F+ K+ C% N" \; d% POut[50]: 3 b- s* D9 o7 ~6 T! p/ j/ Y
    2020-01-01    1) b$ i+ Z+ l8 v( H  ?) P1 ]
    2020-01-31    0
    ; \  D: p' X" l' o& U2020-02-01    1
    $ }7 q% Y/ C4 L2020-02-29    1
    6 s3 f5 ^- ^* k" K2020-03-01    0
    # V6 B8 S% t1 L0 K# P- Ddtype: int32, }1 P5 f6 V3 _# m+ g
    1" R7 t7 I% w1 G9 ~4 N
    2" a, ]$ }# q- v. H
    3
    : l, g. {8 G% D  @2 w43 ^. B, c/ W" N; R
    5
    7 P6 \5 C1 a* ^; r& U6 t% B6* Q' o& B; Q# S& N; E
    79 K8 W. z2 Z+ t- ~" s/ Z! p! }
    8
    , ~. x) e5 m- d3 @3 \- EExample2:双休日
    ) U7 h/ F' D7 g4 |8 r$ f5 A! \) F9 |  f6 [3 ^
    s[idx.dayofweek.isin([5,6]).values].head()
    , C& D0 L, b9 [# LOut[51]: " b' z; s$ n3 k2 l
    2020-01-04    1' s. R! D; r9 Y' z- K, s
    2020-01-05    0
    & G3 Q3 }- X# r# T# `' z5 b; q& P0 W2 X2020-01-11    0! h# R4 T3 c% e# P0 W' Y8 P
    2020-01-12    1
    ' E$ J9 A5 X9 H. c/ Y2020-01-18    1" b2 t# y/ V0 a1 X5 B7 ]1 a
    dtype: int32
    # T4 L1 C8 W2 D2 b5 ]& S1
    * W$ X5 A- i2 ^) T4 Y) f  b: Q* g' q, n28 o* u( F' r8 P* V% `
    3
      c: n4 r- \3 k$ f7 h! ?/ J5 F4
    & J4 L! X0 M: e" \; x0 _4 x5
    - [# `! X, T: X+ L" W6, A" e9 {- _( g! j
    7! A+ K* m9 k  ~# X
    8
    ) c8 ]5 C* R4 c- y, n$ mExample3:取出单日值5 T% S- Y; b% B$ `+ z2 b  `

    + B2 g- y/ {. B" G& u4 Hs['2020-01-01']5 Z- j, z9 B' K
    Out[52]: 1
    7 Y# `7 E, J& b2 r8 ~) z5 z7 W
    $ ]- U4 |6 Q3 F8 Hs['20200101'] # 自动转换标准格式
    1 Y; E; ~# u* v4 H: ~8 ^0 ~! w3 ROut[53]: 1$ {& g: V( M, w* B* p3 R! ?
    1
    0 H- r2 N4 F9 C4 X& v% G' @- A; y2$ r3 |# d  C3 x  _7 r' }: T- \
    3$ c- Q" }, \6 d+ d) j/ V
    4+ Z1 Q% K; N/ e6 c
    53 H- a# M  I0 F: ?/ y
    Example4:取出七月6 U9 s' I) x: Z( U
    8 q8 y1 E! }  J# M' B
    s['2020-07'].head()# d" ]! f+ I. M& o( ?
    Out[54]: 1 [! E% }; o, V. |3 {
    2020-07-01    0
    ( }: J! j% z& g/ P2 _0 [7 v! t" T2020-07-02    1, n. B4 @! B$ q6 X  j5 `' m
    2020-07-03    0! Z6 _# \$ m1 Y7 m3 X4 |, b
    2020-07-04    0
    ) O- P' V+ {* I% y: u; @2020-07-05    00 d) v, p' v9 e3 h' _* N! u
    Freq: D, dtype: int32. [6 j, v' L5 Z0 b$ c/ r
    1
    9 }) I! @* E: f5 w, `9 {; ]2
      o, L4 ?% B/ j3# I; V3 ~( y. q+ ^2 I) r. R
    4
    # \( d' ?0 S) }5' J, J3 F0 _4 h: O
    6# S$ o5 _2 Q6 e) Q% ]) F2 }# s
    7
    " p9 h" ]2 S7 U% N( l8
    / G" C; y$ Q+ o7 [* q! o  sExample5:取出5月初至7月15日4 P3 B1 J0 g7 e0 g# h9 O* D
    / a2 ^/ D( G# B% d5 {' Y
    s['2020-05':'2020-7-15'].head()
    4 H- @8 ~. ^; G& x. a: g% ~Out[55]: ' g" l  e- V/ H1 x$ u9 F$ ^
    2020-05-01    0
    & M. v. z$ i; y: |3 L$ g2020-05-02    1$ e) X) A6 J5 K" j0 l) g  H
    2020-05-03    0  r! A8 E9 g$ G- |
    2020-05-04    1/ ?1 u8 H/ n% _0 C6 \' O
    2020-05-05    1' R. J( k0 }' z4 ^
    Freq: D, dtype: int32
    ' N4 V" B8 T5 B1 z
      P7 b/ N2 L+ s6 A1 \s['2020-05':'2020-7-15'].tail()9 Y8 A/ W% G  l6 x) Y  t5 t
    Out[56]: 0 A* h1 n  G5 G: u+ z# \
    2020-07-11    0+ d, l7 P+ L* j6 p" ^6 ]% H! t2 c
    2020-07-12    09 H7 P$ U9 |6 R0 Z/ a+ ?
    2020-07-13    13 d# ?% e  e7 W( ]) H/ d
    2020-07-14    0+ m7 l, z% v; b& z; N" R' N+ n4 z) _
    2020-07-15    1
    : F* X8 A* A4 i8 q7 y+ \Freq: D, dtype: int32
    # \/ Y# T# @( h, B, p0 m( Y, w$ @1 m5 B+ M
    1
    0 h  n1 z  W/ z3 f1 \( u9 y9 u* j1 t26 B$ J5 y7 w( z7 d; s+ ]& w* J
    3
    ' g2 c2 ~. U) {" c& j42 g% r5 [' i" ^3 [4 D/ G
    5
    5 e) n, _- S9 P+ O) H8 I" r65 c) a% [; i7 m) a& G
    7* t) g! }* N- a# G- x
    8
    * L: ~7 ]. H6 }& P1 Q9: L8 f* G4 {* o" U
    10
    1 [) Q) c2 p" p$ z; y116 {" S! ]# c% ?
    12' C6 \. D1 t& Z% ]  f
    13
    6 p! p' Y& r6 f. i0 @; ?14" ~+ {7 ^, m- V  X
    153 l, a) Q0 \/ r; Z+ {
    16
    ( h- g5 O3 q, k7 T& V7 B) }17; K9 }* i. w# y. l8 H) u, e: ~
    10.3 时间差2 ]* [- A$ _! @9 u. s) F9 J
    10.3.1 Timedelta的生成
    % e/ g; ~1 v9 apandas.Timedelta(value=<object object>, unit=None, **kwargs)8 ^: E4 [0 W! n. \1 P/ S" [
      unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
    8 j* @5 ]3 F* F  可能的值有:% `6 s# W' s" Z0 G: i7 Z
    / O6 n! b; V' r8 Z1 @! |
    ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’7 u; Q4 m: |* D, x+ Q8 T
    ‘days’ or ‘day’
      I/ G5 I" g9 t0 f# J‘hours’, ‘hour’, ‘hr’, or ‘h’
    . h& H0 ^' F3 X! l6 x5 S) Z‘minutes’, ‘minute’, ‘min’, or ‘m’
    1 s- x$ G) e- x8 p& F‘seconds’, ‘second’, or ‘sec’
    . X+ j, i5 k$ c. q) }& T3 Y8 y; V7 I毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’2 I2 l5 L: ^7 d$ |9 |
    微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’; h. W7 e6 ?: d2 \6 @
    纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.; `* L9 D2 I* S7 o% R8 w
    时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    * F" b/ L+ E" V* A( fpd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
    6 k$ S* |5 U$ x- VOut[57]: Timedelta('1 days 00:25:00')# [( D+ Z1 n7 x4 p

    - i6 w- n* o* r3 R. {/ zpd.Timedelta(days=1, minutes=25) # 需要注意加s
    & ?  _/ f2 W3 k7 Y; Y( r  Z/ fOut[58]: Timedelta('1 days 00:25:00')
    7 C8 W6 \5 a$ l' E, a9 _% _% u, V4 T/ G+ |! Y( u% v- i* H* R, ^6 w6 H
    pd.Timedelta('1 days 25 minutes') # 字符串生成
    % g6 ~/ Y: f% ]) V0 LOut[59]: Timedelta('1 days 00:25:00')8 R1 q3 N# n# M& R* A# }

    / i$ B; G/ X3 m' B' z  M# gpd.Timedelta(1, "d")
    . L8 f6 C& e8 }; t. MOut[58]: Timedelta('1 days 00:00:00')
    ' t# y  V$ l8 b0 x: g3 v6 L; O+ D13 _; K9 B- S# S; Y/ E6 R
    2
    ) i; ~$ E3 P8 g& ~3; v  u7 P) M* h$ z1 s
    4
    # ~" M' U4 f0 p' m" z54 b$ I0 x1 ~" Q- X4 o8 A
    6
    8 W6 D8 B2 T; |; x7
    7 a& H5 {" q7 e4 c3 Q: }8
    2 `; M8 m# A0 K$ M* Z9
    - l$ o, j* X5 Z/ }10
    7 H7 _5 B8 Z/ D) z6 M& a11
    4 m& ^( i1 j& @$ s) z/ t7 F; x生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    1 r/ f- f( A( D# n8 y- H2 T; _. \s = pd.to_timedelta(df.Time_Record)' x* z, M8 {  r7 ]

    3 e0 X9 G$ j. j/ W/ h( g4 I) ls.head()
    7 ~9 S3 U9 a% r: yOut[61]:
    2 r. M3 _* x0 v1 ?0   0 days 00:04:34: ^) w# v% N: Y, M; B- N  f
    1   0 days 00:04:20+ V1 c5 Q' _, X. E! \3 Y1 E, ?
    2   0 days 00:05:22
    ! w4 s+ G0 |0 O; i' A3   0 days 00:04:08
    : _0 F( i; O, Y8 K4   0 days 00:05:22
      C6 J& M4 T, ~& T& A4 jName: Time_Record, dtype: timedelta64[ns]
    ( S8 J( ^1 x( U  |, B, B1- Y. [# Y/ S) f2 r& W, `, |
    2" E  ^, W6 A5 l2 ]4 r3 S
    3- A. ^( T/ f, L  B* r! ]
    4
    - P% d+ m( A8 e& ~: l( b5 e+ f5
    6 }5 w/ E1 a1 {: w64 h4 f1 B! |0 {1 W  |3 P# q
    7
    / J5 {: I: d5 Z  z4 K: R9 k$ v8
    - @* u4 K) w# s  Q* M9" V  D4 X8 T- S% a5 {2 ~# U
    10
    7 }2 o- Z7 _2 X2 d与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:% E" |1 x) B, B$ l
    pd.timedelta_range('0s', '1000s', freq='6min')) ~7 [' R% k7 k
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
    5 ~5 d& k9 J+ {3 ?& G3 `
    / K  R% {1 e! y* {' t* ipd.timedelta_range('0s', '1000s', periods=3)- M# l2 U' l+ q' _- w
    Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    / |2 N, r: O8 Q) R/ P1
    % E4 m. K* J1 ^7 p  g9 U2 s2: {( u1 ^* o0 A; E4 h& ~+ w
    36 c( w+ @. ^/ V) ~" I( j
    4* [7 [5 k6 X2 a* s& V0 w' P
    5
    ' I8 J, g% `3 s' ?& F: J% N- o# m& O3 w对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:5 L4 B( m2 d6 r  @: l# R
    s.dt.seconds.head()6 V7 j+ r2 z3 U
    Out[64]:
    4 i6 O$ q5 |9 L0 G+ V8 R! i0    274
    : v- \2 ~  t1 Q( L: m  J6 L6 e& j4 V1    260
    - Y6 `' N  M, q; `2    322
    ; H% c9 C) W/ B! Z% r: m3    2480 i' h. V; V7 T4 r1 Q$ j! [
    4    322
    + ]" X, l" F$ X! A) RName: Time_Record, dtype: int64. M$ y7 ]; }2 ]1 S' z0 a
    1
    2 c5 j( p! U8 ^$ J% V; B" R* m2
    6 s$ B8 y% L& a; ]+ D( Y3 r) S3
    3 H! L( C; C( u2 D. O, W4
    4 j5 {2 G0 O. O; \5
    9 X# R) x' m2 M7 i% v, B, J9 f6 F66 x4 h6 ^: p8 C+ c. J
    7
    2 O& i1 T& |8 G( j& E* @7 H3 \1 L8
    . e2 B; d" Z) t3 v8 Q" E, ?0 ^如果不想对天数取余而直接对应秒数,可以使用total_seconds  X( ~! N6 B" I9 z% ]( x- c2 J% x

    ( c) R: g% J5 v7 B6 d7 [s.dt.total_seconds().head()9 D  M1 i' l# \
    Out[65]:
    8 ^, q! n% |, c4 I0    274.0
    7 L/ A) l* k* p* R! Z1 l5 r1    260.0
    5 T5 Q- b2 g/ `6 _2    322.0
    0 p, `2 J  W9 k$ p+ C# z" y- I" o& f3    248.04 E3 B  J; L- b$ {; b' D5 j
    4    322.0% z0 ~; j, {, ?) w
    Name: Time_Record, dtype: float64
    + @" o1 K# e/ g$ h1
    2 a  S) `  c1 Z& U2: w. S/ ?$ p+ {. ^& L
    3
    9 t7 V8 t6 Z: X. q. @! n- x4+ R! y- g; M. d
    59 V6 F- e$ Z# k" w; \5 v$ R& S
    62 U4 A% c% P4 L/ Y  f" u2 x
    7
    ; C* d) Q6 B/ o8" ?# C; F8 i( p1 p1 e! N4 d
    与时间戳序列类似,取整函数也是可以在dt对象上使用的:' d. |1 ^* C2 X( d

    9 m& p) L. {: ^* e0 S0 a9 S1 B. Ypd.to_timedelta(df.Time_Record).dt.round('min').head()
    1 V, p  J# }; k" l* D, zOut[66]:
    % C0 C* J' r$ ?- j, o# c0   0 days 00:05:00
    " f( x) T; F- P7 f% y- [4 O1   0 days 00:04:00
    ) Y& d; _' V1 H" J  w3 Y2   0 days 00:05:003 b% q7 v6 Q8 k  P
    3   0 days 00:04:00
    0 a. c, ^% [1 s6 ]0 x/ ~1 W4   0 days 00:05:00
    3 E3 C9 Z# c! rName: Time_Record, dtype: timedelta64[ns]
    6 F3 ~, t, j1 K2 S' P1
    * p3 k' ?/ ]) v* k( m8 C2
    6 U/ V- z% ]' H* C: `3 {3* N; L  [6 y' ~! m- i( s8 ~
    4( m3 U6 c0 s+ b/ a1 q$ w
    5: v0 O7 \9 v  M8 c5 x# ^- X
    64 G: i% B  I/ u  j' r; u% [" V
    7( @1 i8 W' ~8 W* m; v0 z
    82 P" E5 ^3 o3 g: R0 o
    10.2.2 Timedelta的运算; p  A) |8 l% v: a1 G- u
    单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:# Q+ e' W8 Y# u. K; f7 o6 ^. R
    td1 = pd.Timedelta(days=1)1 l% v3 t& m) s: i5 N/ H. I* R
    td2 = pd.Timedelta(days=3)2 A4 ?9 J5 O/ x. F
    ts = pd.Timestamp('20200101')8 r- A* @+ q7 K/ R* [3 J1 Y0 c

    " E" h4 x0 j, {2 Htd1 * 2* E6 j' A: m8 G1 h2 J/ s( e
    Out[70]: Timedelta('2 days 00:00:00')
    $ K* D4 n3 _' I2 Z& D" f6 R6 {' x# r: q% {- y- H5 `' Y5 s7 Z
    td2 - td1
    # r( o9 w. \  }" dOut[71]: Timedelta('2 days 00:00:00')& @& j: V& {( C& ?6 h7 W/ X' O
    & g) r; A# }  v; M4 |2 ?
    ts + td1
    : f+ m' d$ f5 n( U: p/ v. a9 D- EOut[72]: Timestamp('2020-01-02 00:00:00')# p6 N3 E6 l- b7 O9 ^

    ) v1 f$ I& E! vts - td1
    / K" _# v! O& m9 H# wOut[73]: Timestamp('2019-12-31 00:00:00')
    5 B+ V3 E1 T$ l2 _- y! g1
    " K$ g2 L0 M% \6 H" [" N4 v2
    6 u) Z- ]) |5 l" {9 L39 p4 g+ Y9 A' j3 y- V+ \! I. G
    4
    0 J. t. c1 F6 _' b5- b2 k3 c' R& k2 R$ z% \' U+ n
    63 Q. h+ v* P$ h6 d
    7
    ( V3 P/ T  }7 h2 ^) r$ J( U8
    ) P/ |: Y( L8 h! ~5 J- c) K9
    9 U/ f8 d$ z! A4 }' \106 ~- ?- B  y' G: D
    119 f) x- e4 s" G  @' A
    128 R, D! D7 f2 t/ \
    13
    2 s$ A7 G/ g  h1 [# Y* p4 e142 @( a- a" W1 I. ^2 ]; b
    156 L; D- _* y0 p3 @  E: A
    时间差的序列的运算,和上面方法相同:
    0 {* K8 A3 [# r8 ]# ptd1 = pd.timedelta_range(start='1 days', periods=5)
    1 |; V7 }9 [4 q8 ftd2 = pd.timedelta_range(start='12 hours',
    ( p- g1 b  }. _- R6 }' f0 W                         freq='2H',
    2 r0 d6 l2 X3 f; E( t                         periods=5)
    7 F" T1 h7 @  L) |8 ~3 _ts = pd.date_range('20200101', '20200105')4 [' n6 h8 B8 ]6 J0 u; o9 [
    td1,td2,ts
    4 d, N8 v. r- e# l! ^# u: l  H) D' \. |0 g! A7 \1 N
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
    + s# e7 z; ~: T% n/ u1 cTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
    4 D" k" i7 O! D                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
    - v! C( ?( |. ~5 v% zDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
    4 x/ P& T2 h7 @% B! Z               '2020-01-05'],) X6 n) l5 E2 A) O: V. {6 R
                  dtype='datetime64[ns]', freq='D')
    % E5 \- j% Y( w; G" t# j1
    $ \% D. l* e4 W4 P" @2, t+ z: u, ^1 `* Y/ E
    3
    ( z) N, J2 z. S6 W+ F0 Y4
    * P9 Y+ {4 R) j54 i, i( F! A& w
    67 }6 c* _* `  n) O; c) r5 Y! r) Y! s
    7& y( m7 Y; Z" I2 I
    8
    / U: M) f3 ]1 d$ }+ v% q0 s7 q9/ d# {( y9 t! T/ y: b
    10+ a! y) ~% y3 S! w
    11$ {! Z! {; K" Y3 t/ `/ D6 o
    12
    1 v, l9 L9 l7 S13
    # v/ |2 V3 m1 c$ Ztd1 * 5" s$ D6 k! N$ |; E9 z
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')! `5 o( F, R/ o
    0 Z' S  Q9 G, z9 G
    td1 * pd.Series(list(range(5))) # 逐个相乘
    # g. j. X! A) d& Q, P$ KOut[78]: 3 A$ e$ |; H0 T0 U
    0    0 days
    + t, Q4 B* r2 ]  f# w4 O1    2 days
    ) i# V' e  z  D# s5 z2    6 days
    ; _& A: b+ q, t. S3   12 days
    / q4 O& B( `  O7 m4   20 days
    + u* c( w& e6 idtype: timedelta64[ns]4 I4 s! d  {8 ^! S, r4 a" C, e' ]
    7 P/ O/ o5 j- L) m0 y6 N
    td1 - td2
    ' I" U  [6 M5 L" tOut[79]:
    - {* f! `& M( l1 F4 S/ `TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',) z1 M, F/ u( G. A6 h# e
                    '3 days 06:00:00', '4 days 04:00:00'],  q6 U3 l* w4 G' Q, h
                   dtype='timedelta64[ns]', freq=None)
    , t. j4 c# ]/ q* }4 {: r. n6 k. W7 E- r$ X( L  \9 K9 ?
    td1 + pd.Timestamp('20200101')( _# h! a7 t1 E
    Out[80]:
    % o. P  ]- c6 ^5 `& |" o: L$ U; {DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    8 v  ^+ x6 r- C. B8 ~               '2020-01-06'],dtype='datetime64[ns]', freq='D')
    5 O/ O: N+ l- C0 ?7 v! u# [5 M; l8 p- x
    td1 + ts # 逐个相加
    9 t! `! s7 R! B& l0 yOut[81]:
    1 n1 x4 d' G' t) q% d  l4 ?DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
    1 j1 h0 ?4 }; e# H               '2020-01-10'],+ g$ e, R/ c6 Z3 _7 a* M
                  dtype='datetime64[ns]', freq=None)
    1 R1 O0 h% l* ~- z6 k! [+ P7 z& e3 _: C6 T/ l! O2 U3 Y
    13 X$ o3 F7 v# ?; V* \) {6 X
    21 J! x* p3 p7 W: {1 I9 ^
    3- [# i5 @/ `: Z! @( x3 f
    4+ G. T( c* K; t  W
    55 z; n2 P' \% e( W8 n
    6! B5 I8 X8 v$ {3 C
    7' K3 v; E' u) }6 ^/ T. B3 f" L% w" Z) Z
    8
    ' Y5 R  S) E  ?9
      Z; J: m" F, u) J10. v# H- Y' d8 e+ U6 {. A+ o+ U
    114 z1 c8 Y8 ~0 i" F/ C
    12
    & t- [& R% E4 `13: A+ s) N- [0 W8 k! Y' K, E
    14
    " [* g6 Z. Q5 f; V+ R* C) e7 }5 A$ `15  c# O8 Z* t1 u/ o& V2 n
    16
    ( f! J. I) r, b! c* A17% r' D5 M" A  X+ v% S- x, P
    188 e$ p9 Z% ]! X+ [; h& S3 J
    19# m* y+ i5 i% j7 X
    203 g' A: j$ \9 C+ X
    21
    8 j* K9 S9 R- }5 n225 ]# w) ]! Z1 u
    23" X  {3 y1 H7 J/ U
    24
    ' f# A$ h! l! T6 P0 _25
    7 P0 ?4 z* R- ~+ _7 }26
    , N6 _& p; ?1 I% i' D27
    * _# X- Y/ o1 s" @$ z. z28
    7 i& _9 Q+ L1 ?3 @% N9 F10.4 日期偏置/ Z1 \) a' y! P6 l
    10.4.1 Offset对象" ?8 V+ \: V# ^3 B" ~, G! A
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。/ q* x( x7 y, |1 j4 L5 ?
    8 ]7 N$ g/ }3 V8 X' T% O/ k
    DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:# N% I3 _" v* K
    . g! y1 w; Y1 n( c
    s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本. N, @: `9 Y. X  A9 }
    s.kwds:{‘week’: 0, ‘weekday’: 0}
    - T' ]) q- O) P" T4 p* t, Ds.wek/s.weekday:顾名思义) t$ T4 X8 A3 u: |" n
    有14个方法,包括:: D& @( }* ?% h5 a6 B2 \

    8 ?7 T% P* H7 K3 n2 iDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
      ?3 m; S1 E% s8 cpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。9 h7 [# S+ ?2 ^$ w' w$ x
    ) H+ x1 T2 O7 D  D2 |
    有两个参数:
    1 b: }. o& ?9 f. M, Tweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。% f8 b8 A  y: k5 g
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
    " u! H1 u3 T+ x- F8 n8 Gpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    - y3 @  s( F# e/ o
    " M8 `! H) Y4 }2 x  A/ i6 ~pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0); S+ C$ m8 v4 F( s7 g8 A* T8 m
    Out[82]: Timestamp('2020-09-07 00:00:00')
    7 u" i2 _3 t# R7 P! e1 K4 ]2 u  j$ N* g1 z1 Q8 u
    pd.Timestamp('20200907') + pd.offsets.BDay(30)
    5 u8 `) ?4 E9 C4 G+ E: F2 m) yOut[83]: Timestamp('2020-10-19 00:00:00')2 w# B$ t' D( H
    1
    1 g: ~5 D" W" Y& E% _2
    ' B1 W2 `$ ?3 m) _# q3
    / O2 h2 r" b( `% h9 ~  g* y  X40 z/ `& ]$ z% ]5 f1 q; h
    5- u6 T3 L8 j2 e) @) E: @4 a
      从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:2 h: x2 p. N. ^6 w  }8 Q2 X! v0 O

    & l3 n  N: m, l2 m/ e4 T' Zpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)4 ^( j9 Y, B* `* @! @
    Out[84]: Timestamp('2020-08-03 00:00:00')8 y; ^4 y# i6 u1 h! P
    # _1 [8 T& K/ m5 A6 u4 ]2 w( G
    pd.Timestamp('20200907') - pd.offsets.BDay(30)
    7 D4 z* M, C, v; v/ p" V/ V2 sOut[85]: Timestamp('2020-07-27 00:00:00')) N. c, t3 [1 s* Y" l9 h2 n, V" A

    9 y" N4 h" ?# I: h- R, dpd.Timestamp('20200907') + pd.offsets.MonthEnd(); F; B  Y* U* @) [% `
    Out[86]: Timestamp('2020-09-30 00:00:00')% J; l2 ^  S7 Z3 v: I+ `
    1
    ' t: {( a9 O+ }' C( J( |23 u  H$ @7 ~$ G! y2 K
    36 Z" J" O& w7 t; s7 t$ v& d
    43 G1 {1 x; r6 `% d
    5
    3 [" u8 m9 C0 ^* z5 O6
    ; w( p$ P) r% y) I# z/ r7; ?- ^4 R$ N/ N1 [0 m6 }
    8* s1 `1 G/ z+ F) D
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
    / ^) p5 o- K. y7 X5 S9 \  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
    & P. E0 A6 F1 w- q: b. n. |: {
    0 f1 u( Y4 v; F+ D) M, Fmy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    5 F. N3 `. Q) B% J- d# g! G: ]6 S  O8 qdr = pd.date_range('20200108', '20200111')
    1 b* {$ v* y) _, L9 a4 s' P7 }1 N3 m* f5 b! m, }6 a
    dr.to_series().dt.dayofweek
    ' ~+ W. s" d, |Out[89]:
    . l- \9 \$ M9 {/ r- E* q2020-01-08    2! L! {4 S; L1 H/ ]
    2020-01-09    37 A; ^4 Z& ^3 A/ u& d
    2020-01-10    4# Z1 E' k4 q$ U# `. C1 [
    2020-01-11    5" F# j8 t) ^$ T9 B
    Freq: D, dtype: int64$ W# l4 `) k6 Z( K

    ; `! G2 t( Z! Y2 _5 S3 H[i + my_filter for i in dr]
    8 A8 a$ F# a$ {9 E1 Q! g9 n7 eOut[90]:
    0 c9 e" _& m+ `' ]9 ~/ W4 w* h[Timestamp('2020-01-10 00:00:00'),
    : P, u& O* P6 e1 i' d0 V' v/ i6 z Timestamp('2020-01-10 00:00:00'),: a: o; m% s: W7 d
    Timestamp('2020-01-15 00:00:00'),
    " y4 R! q' S4 d  Y7 _. H0 x Timestamp('2020-01-15 00:00:00')]
    6 M# U% u5 w& B, I: [  c8 z: U! M" K/ k
    13 d9 H; S6 F  e/ f
    2" y4 M5 S8 b- I( _
    3
    ( H, M  d! v% G9 S& N4
    9 t8 s7 G; I4 x5
    6 ^( H' y2 n" ^6# w( B- y- ?2 B) I
    7$ C+ z* a9 o0 ?* p; W; b; y
    8
    9 [, {% |4 x! J6 n9% j: j7 |. H! C; {7 m5 t! F+ c& r
    107 g4 p% Y) L' a. q# W
    11
    . p$ n6 H' M& f; G6 I12) s- O* C6 G9 P+ F5 C: e( n
    13$ ^5 g+ A& v& b( b% I9 h
    14
    ; d4 _; X, a0 }3 ^& n" z15, [$ l8 k' |3 K5 y; t
    16" t& ]* m- f" q8 U
    17# f: Q" i; l2 v. p# m4 c2 O, i7 q
      上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。+ K$ r( S9 s7 ~, ]/ F4 Y5 ^1 E
    & W2 K& z3 Z+ ^
    【CAUTION】不要使用部分Offset
    . B% A& ]+ g8 r在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。) @- n( T7 @( Q, r- b

    ) i1 i9 T4 K. V, G) Z4 o10.4.2 偏置字符串
    1 W% J7 J3 P, t1 h7 @+ q  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
    8 c1 _' e/ v/ j1 u. I6 L
    ' a5 [$ v9 y$ m! W+ D  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。& F$ ^" O* n, v. M, o& K$ e
    ! \/ V  t5 W: m: K
    pd.date_range('20200101','20200331', freq='MS') # 月初
    " S6 r4 s5 y( w, E3 s$ COut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    % j( }  `9 U  g5 L! G- r% N' j
    pd.date_range('20200101','20200331', freq='M') # 月末+ c7 w% `. Q8 ^! I9 d5 q3 f
    Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    4 `5 J, Z  W0 Y' w
    2 l, }- h* Z' k* T. o; @+ g/ v) ^pd.date_range('20200101','20200110', freq='B') # 工作日: Y9 F8 A8 V0 ]5 Z, F( o
    Out[93]:
    , u. i$ F  T$ @" _DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    9 K! b8 Z+ @$ j               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],* W4 K1 c; m' u& P+ l
                  dtype='datetime64[ns]', freq='B')$ e* A. M% s7 m, z

    1 F' t6 ?- @0 j+ S' Cpd.date_range('20200101','20200201', freq='W-MON') # 周一& b( N! K! S  [0 ?& k6 }
    Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    7 B4 A& A5 ?+ H# k. L, @6 R* f+ a  r3 u3 e$ z) B2 D
    pd.date_range('20200101','20200201',, ]5 U/ F" z1 c3 Z: _
                  freq='WOM-1MON') # 每月第一个周一, d2 O6 C  U% P" `# U6 E3 P) ^
    ; X3 m4 Z, Z9 S9 W$ e
    Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')2 _( z1 I2 K+ |3 Y
    4 j5 _9 F) B( p
    1
    7 t# l/ ~7 ~, F: B5 Q9 v6 e- Y, Q28 `4 \  J! }/ v, {
    3
    6 z1 W% I- t) r& n2 Q4" m3 b2 m; n/ t0 M5 a
    5. w) e3 C0 G- x( }4 q, l& S0 o
    6" \, s2 A; {" w
    7% r8 s0 G9 m7 X7 Z2 A1 F
    8
    3 x5 ^1 X0 K  b9& }$ T1 W$ V& `  ]
    10
    $ ?/ D* ]& k3 \6 _: C11
    1 \* f* P+ J: ~# `& @5 g12
    * b+ M4 P' {$ f% d13( X: a% T- M: b) f
    14- h6 ^  U4 d) K4 R, v. K
    15
    2 z- \# @  ]: A( @+ F163 Z- k7 F2 y% W! q  H- c
    17
    * H/ u! Z  o, x7 B9 P18  h8 y: m+ l: @  k3 @, p& y  Q
    19
    * k# z$ O0 M  M上面的这些字符串,等价于使用如下的 Offset 对象:+ n" \, _3 U4 I8 K: \
    $ n; r- q3 q) a4 |% }
    pd.date_range('20200101','20200331'," D+ t/ O8 U/ A* z5 R
                  freq=pd.offsets.MonthBegin())
    ' i6 i9 x: \; _" j  u2 k9 d6 f  D2 {$ T$ W1 g: k
    Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
      ?5 T5 i1 @' v8 G" I' R7 H1 [: @% }/ |2 g; g' ?6 b- [9 v& s
    pd.date_range('20200101','20200331',
    ( {( x0 ^! Y( G: O              freq=pd.offsets.MonthEnd())
    * C1 x! E- [9 w! j3 ]* x3 R+ ]3 f$ p
    Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')" ^6 P' M' D' \6 }& H

    + F0 m  D! x$ R# J' Wpd.date_range('20200101','20200110', freq=pd.offsets.BDay())* O" B/ ~8 \7 Q8 ^
    Out[98]:
    ) v( G' J0 G% F4 YDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    : i9 o2 z' o0 g# o# I5 L1 r3 s               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    $ z8 `+ _7 b9 d9 ^              dtype='datetime64[ns]', freq='B')
    1 x" q" C9 ]8 P# Y1 O. C' f: ^  r7 n* P/ d6 N+ o
    pd.date_range('20200101','20200201',. T. D7 l* ?5 r' s/ @( L/ Q
                  freq=pd.offsets.CDay(weekmask='Mon'))
    " X+ Z) {7 Q7 x9 g
    4 `" Z6 {0 j+ QOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    9 A& b1 M3 c1 ~; p4 C" G  n. Q% ?% P3 v
    pd.date_range('20200101','20200201',! A) o# H8 [: w+ |5 M5 _! }" R+ h
                  freq=pd.offsets.WeekOfMonth(week=0,weekday=0))# ?0 ^$ E/ y( V4 b6 }  ^8 I4 I

    4 ^+ V0 g# V) [3 }Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    4 Q( f8 h' ^9 E+ J9 a' d+ h2 t0 M! V6 v; F+ H8 Y
    14 T# ~( d* m# y: e# [/ _
    2! Q  c6 `6 h1 p. K; X, x. C
    3
    ) E  a" N  _/ A+ W0 m4
    4 C  e8 g9 ?+ r6 i" w$ x3 ^& E$ ~: y5
    / S9 I. B5 W" e' d) Y/ A* ]6
    * D2 h+ f" Z6 N5 _- U. I7) B0 c; q/ ]4 F  V  f
    8, {0 H5 Y* o8 Y- v
    9
    0 F. z/ |% m" Q10; }8 d$ Q2 a2 g" M1 L3 @. Q
    11
    9 \- |4 z6 ^* A12
    , G- r$ I' b1 C' [6 A+ q13
    6 C$ G7 E4 B# P, n145 M$ b( ^* c! O( ?
    150 F! z( t9 P6 F" R' U. k- i
    16
    / J* V4 W- t9 Z+ o1 u17
    , u# d7 n2 [) h  p18
    - S, C9 W. z& N$ e# d2 S/ }19
    2 D4 t+ V" ?2 n* G7 c* J5 i! ?20! R' N7 I& U8 h0 m
    214 U+ a2 c; |, L$ v9 Y, g0 q
    22
    8 ^6 o: ]2 o) a$ i23' m, \& }( F/ C& k$ A  a8 s; L
    24
    9 m9 Q' K0 B& `2 S( O  y5 x250 i/ o6 B! I* [$ H: u- Z
    【CAUTION】关于时区问题的说明+ r) R) g# L& }3 d  E) s' h+ l# y) J
      各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
    , R9 n! j, ^3 ?+ V. ~! }
    ) p2 Z9 `5 o1 w3 _10.5、时序中的滑窗与分组
    ! ^. P  ^( t. O% N6 ~10.5.1 滑动窗口& K% `. b2 @' U7 O  h
      所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
    ) O) }  \  U/ {8 [) P) G5 j
    / ?; A! ?4 t4 B/ |1 simport matplotlib.pyplot as plt
    9 i  v* W. Z" V6 M4 r- T4 eidx = pd.date_range('20200101', '20201231', freq='B')
    3 V6 A0 d: m0 L/ L$ rnp.random.seed(2020)6 X1 {8 D0 h& I8 L. `& a) I( |  \
    + J( y5 y3 Q: {
    data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
    5 m/ ?% e$ M' {0 i+ |6 c& T& ms = pd.Series(data,index=idx)
    - r; ?/ S5 F$ i" Ks.head()* p( Q6 A4 |! N; O) t4 J
    Out[106]:
    * @* ~+ n4 Z9 a; {2020-01-01   -1
    1 p; v  `: W) v7 T' [& _2020-01-02   -23 t2 ~( W, w* _8 C
    2020-01-03   -1
    . J# c* K- Z0 s/ X2020-01-06   -1
    + W5 r( L: Q) i( j, d( A2020-01-07   -2/ p7 R- M$ s5 g" G  B
    Freq: B, dtype: int32
    3 N) Q: W( E& e2 t  w- Ur = s.rolling('30D')# rolling可以指定freq或者offset对象
    6 y: r: r7 X( Q- b- P( H5 }
    % W: J3 h) A# A, v; u5 |) Dplt.plot(s) # 蓝色线
    6 ^8 P6 X3 X" ^0 N) I. [! K9 z) @Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
    - R! N/ O+ H  S# m. H$ \, K  u8 lplt.title('BOLL LINES')8 \3 V( |2 B1 d  _: C& U
    Out[109]: Text(0.5, 1.0, 'BOLL LINES')
    & b! r" @6 B, q
    - n  M0 m- u, E" e4 j+ |9 hplt.plot(r.mean()) #橙色线
    ( P% r2 ?9 O* u/ J5 o; Q9 ]  M# OOut[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]. J) c' }) B( m! F9 S9 l2 |

    1 @% j: X7 g' ?/ l5 lplt.plot(r.mean()+r.std()*2) # 绿色线( A4 m+ d+ o' `% }& g  [
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    % F- Z' M/ q2 b3 z
    ! G  V+ r; h( H5 Q: Yplt.plot(r.mean()-r.std()*2) # 红色线; s. k$ V0 p- Z# s4 H7 I8 }
    Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]+ i  w3 z! [0 [0 Y  V/ w) q
    8 |" A" H1 h5 z2 e
    19 g/ s7 l0 B% y" {+ z: A* o) I
    2$ ~5 E2 |1 w1 l' q
    34 H, O5 P3 W4 ]' m4 k0 N7 T
    4$ s  T$ t, s' w; e0 j& l3 B1 |
    5
    . x1 P+ i/ G* A7 c4 e5 y2 N6' |' |& P& W; |& g7 C/ ?' Q# M: {
    7: n# w: L0 N* Y- \0 @  X2 b1 P
    8
    3 d6 E# x& U9 k  T9- z5 S; g, h. V: d
    10( H9 v7 f/ S4 n# G
    11" n$ J1 t# w( d  v0 A" h
    12% R7 t/ }, n. D0 e
    13
    . F' p( x( u& t7 _7 v4 d  w2 S8 A149 S* h* V0 l1 c) C! W& m9 k! q; l
    15
    , n5 a  a. |7 _9 b* c168 s" Z, a9 Q3 o  g; h1 U
    17" x  U% v2 {! O# |
    18
      _3 l. p" u+ E' Y* q19' [' H2 Z# W" s6 v: }
    20$ m! ^7 r9 z; G/ h/ A
    21
    ; ]8 s9 L# |  H2 L8 l  s) ^9 m225 Y; `/ J& f9 n2 W
    23
    * I8 h" R! s5 t24
    ; J# R" v* J2 U9 [5 p6 Q25
    0 ~4 R. ?9 l; e5 Q# T9 U& |26; p7 o9 ?9 T; M2 m) T& r
    27
    5 s  }5 C6 y0 L% j28, A  z* L! ?/ g1 s! y
    29; a5 I" I1 j* E5 U# v) r  P

      Y+ V2 y7 g- c$ b  [0 W   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。8 J5 b. R0 ?4 J
       首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
    # f: f8 B) Z" W! a2 }
    8 |3 t) x0 P8 r5 i3 Qselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
    - w: W1 M+ ], d/ j9 ?+ A! w1 ~bday_sum=select_bday.rolling(7,min_periods=1).sum()
    9 U! P$ |% ?' b- f- ?' ~; O1 A! uresult=bday_sum.reindex().ffill(): z2 w; Y& I' A. F7 j) N) B! e
    result3 r) y' X4 ~- i; v) `
    8 Z$ H2 @3 s" n- ]% Z7 X. p
    2020-01-01     -1.0
    $ W: v( F/ u; b( s. p* U2020-01-02     -3.0
    ; V" E+ l$ ?1 M! r$ s: E2020-01-03     -4.0
    8 R0 g# Y: l# t$ T1 ~' E2020-01-06     -5.0
    ! @. J' I8 x7 @5 U' }# G7 F2020-01-07     -7.0* v1 j% K4 t5 o
                  ...  2 E3 F. l1 u( N
    2020-12-25    136.0
    ) O0 c; x$ c% E3 P2020-12-28    133.04 V; q1 X' |) N" ?. C7 p' G6 `7 u
    2020-12-29    131.0$ d  i# Y" ]1 i: S
    2020-12-30    130.0
    5 i+ C! A/ a' S2020-12-31    128.0
    . R3 Q* }* a1 H8 `7 f& p: j  VFreq: B, Length: 262, dtype: float64* e5 V0 C, A* x9 L. U5 g4 U" }
    - ]" p, ~, {5 D
    17 o, X) j0 t) |! D0 ^
    2
    " }; @+ R' ?! ], n; S- x5 C3. ^  s2 j9 ^9 h0 j
    4
    & m: e- F# B+ E% B6 ^# P% r0 H1 y5
    / Z! Z( p1 O/ @6 _( N6' r0 [8 W3 E" B" I* o+ B" H
    7
    ' B  {* g& T) f8 e9 i8# s4 Q" _2 z# H$ \
    9
    6 ^* m0 X: S3 w8 D* H+ M" u106 g8 [* {/ n+ |2 p! h7 S& ^1 {
    11* h- c0 e: ^: d& ]
    12
    ' e' \7 B8 \" C136 s& x  }. q# g6 v
    14
    # W4 Z" g0 |" D/ `) c( i5 C8 @( X15
    ; j* x* \( ?) m$ a) \, t4 B16
    6 _% D: M. w. w$ b  K17
    8 t* t% h; i  G: I  Y+ X9 J  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
    9 t9 ~% W4 K4 D
    : D2 D- P& m* _, B7 d; W, e$ L  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:5 H9 R+ f2 ~/ D( H! x' b
    6 ~1 C4 b2 S5 i
    s.shift(freq='50D').head(), [  }* y" C8 I
    Out[113]: 5 p+ J$ l* A$ x! Y. H
    2020-02-20   -19 R: L$ m" R/ Y! I& f
    2020-02-21   -2
    / v0 U$ Y: o2 w' {2 o; j! c% v2020-02-22   -1
    ! J$ A% n* ]1 ~$ N  }% P2020-02-25   -1+ q( E% v9 @5 }- K. V2 e+ [7 h
    2020-02-26   -2' R' r- J& M! t* {. t
    dtype: int32
    % ?2 m& E! L2 `1 f& U' V4 S  a1$ \0 ~( ]3 Z+ [. [0 T) W
    2. s7 R* T9 ?* r0 [7 L
    3
    : |4 H; O4 Y. s" v- F. w, S: R$ E4
    7 R8 l) A0 J+ n2 s# ?1 u5
    & Y* a( b9 w2 }6
      C# a( {$ v& O8 E2 b5 g* L9 R7( G5 u/ E5 w; f* ^. z
    87 N4 B+ D( }& q1 k+ q- D# R3 y
      另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    : ~# A3 k0 Y2 \6 c4 Y* K
    + r  d& f2 ?* Y( G$ Xmy_series = pd.Series(s.index)
    : W6 @, g, S! C! e* q7 K: ?my_series.head()
    3 b0 P+ ~1 x1 Z  c2 e0 c( R; iOut[115]: ; }# }: N" r) a2 Y4 T2 s
    0   2020-01-01
    ; N) F/ \! s+ }1   2020-01-028 d7 O2 C, N: }3 n# m7 y5 |
    2   2020-01-03( B8 D$ ~+ G9 l5 P, ]6 P8 w
    3   2020-01-06, Y# N: O: w- L3 c
    4   2020-01-07
    3 f4 `' i" _% x8 I& X' J" Qdtype: datetime64[ns]
    ' ?+ q+ _# p6 x- [
    0 O- T2 ^: v  @- S6 Cmy_series.diff(1).head()1 Q6 z* [3 N7 f' T
    Out[116]:
    . b. J; h* |: h8 {% \* ?' ]& _5 I0      NaT6 g' R+ P5 [( A
    1   1 days
    . V. H1 T9 h5 e$ v. e/ B2   1 days; e  M+ l" Z4 @/ [; b6 ?) l! k
    3   3 days
      V: D! L: v' f  G, r4   1 days- r" _& p  P- B
    dtype: timedelta64[ns]
    ; u0 E% y' m& i# O; T9 q
    6 q4 v1 c4 ?6 `1. F* H5 o- X% {
    2
    6 @) I8 A* T+ R1 s2 i. ^) F+ O: t39 v' _7 Z6 z4 r
    4
    7 ]: l# v5 `1 l: o/ l% ^, ^5
    ( E' [! D5 Q+ ]" m6
    ' s; ]& E7 ^; n  l8 X7 f6 S' O7
    ; I) Z9 ?+ g5 d% E5 s' V4 y3 ?$ ^8+ m+ l% ]* A* M4 J0 I: ?" t
    9
    : P) U' K, J' F10
    ) ^9 }( G* t: U- k11
    5 c. ?; [3 r; G* ^1 a2 F- b4 J12  H  a4 j2 y! n0 S5 B+ c
    13! J# }9 {0 B3 q- d6 [! V
    14' z" y5 g- B4 d  |7 y$ y
    150 J" H: t3 G( {0 R0 c: T
    16# w& D( T  P; D& K$ r# n8 z  ~
    17
    : G6 q  [' `' y1 f187 I: D# d- M: _
    10.5.2 重采样
    5 a( p( v! ^8 @1 l* h# c  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)
    9 y0 }: e, q9 U- v  P  P0 q常用参数有:" v8 U6 J8 C0 x2 b  P5 w

    , ~9 Z& O9 O6 S$ Mrule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    " @% J! ?! {, s' M* C4 Eaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样6 S1 c/ X% E3 e
    closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。/ R( {+ Q" R; @' _! e9 |
    label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。0 r' @! J8 ^9 N! l% X
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。$ u6 a; n. W  _) S# O" I: s# j
    on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。( d5 E: o- Q4 |6 r
    level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。! w9 L& }( m# w. C  h/ o  U
    origin参数有5种取值:
    4 r0 b$ ^0 Q0 |‘epoch’:从 1970-01-01开始算起
    9 Z2 c+ Q! W$ V4 W‘start’:原点是时间序列的第一个值
    6 X1 k1 q% G$ g* i. h# f% t; ?) g‘start_day’:默认值,表示原点是时间序列第一天的午夜。
    8 z, d+ `9 x! g( U- ?'end':原点是时间序列的最后一个值(1.3.0版本才有)
    " D( _( H5 D$ ~3 W4 r‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)  F8 x3 X6 G* n
    offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
    * {  `3 K0 L' v9 ^  closed和计算有关,label和显示有关,closed才有开闭。
    8 a3 z6 P# J& P' {" O9 s2 G  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。2 g. C# M: q: T. A
      c' F) V. v. m' N3 A- T
    重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:8 u6 K$ }! I! e) t( T
    s.resample('10D').mean().head()7 c* g2 N# L8 E0 }+ D
    Out[117]: ; l% o+ g- {# y% }. U0 r" K
    2020-01-01   -2.0000006 q: _. r) Q9 @& b
    2020-01-11   -3.1666675 a6 l6 @$ ?8 h# z
    2020-01-21   -3.625000
    $ G# W1 [3 s9 y4 s7 \# G2020-01-31   -4.000000& n2 S9 [, F6 j. e; s: g* T& n
    2020-02-10   -0.375000: W( t  d# h5 C9 N  ]
    Freq: 10D, dtype: float64, w9 a! s( V7 Y8 x) T. O+ I. I
    1
    ; x; D$ K. d  F! a2  _8 y1 i+ E" `
    3
    7 }2 b  q2 ^2 j/ p1 i4; Y8 G) A; Q+ X1 P( y5 ]# ?7 R
    56 C1 s' t# E: v; Z4 v
    65 f  ^) {, x& f6 @1 x$ e1 R* D0 Y
    7" y0 N9 ]" V. t: n$ G
    8( i+ ^3 R$ D' e
    可以通过apply方法自定义处理函数:
    1 _, J- \& E- O! Q( F9 K5 \' ys.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差/ y, M8 z+ j9 {7 U; a/ j/ Y
    7 O" I+ ?! f  f# ]( Q# N
    Out[118]:
    5 P6 k$ B* m4 Y/ M  i2020-01-01    31 v- ?: z8 V1 J& I. T% `# o
    2020-01-11    4
    0 t; _) Y5 L! ~" ~% n' j1 B2020-01-21    4$ z. s' E8 T8 U% h. }) D
    2020-01-31    26 }$ `) f3 N9 f* {, F( W
    2020-02-10    4
    6 ^% q* N1 z3 x5 \* p* ]4 y9 mFreq: 10D, dtype: int32" \' R& o* U0 g9 f4 w- k5 I
    1
    + F: ]7 {' _  C4 z) F/ m2. M2 J' o1 q2 }; {+ G
    3! Y) t# n: A# J7 _/ [
    4
    , e3 V* g: d: O+ J  [# @5( m+ C5 d7 I& b; [% ]) A3 p
    61 L; t7 j) V: l9 ~8 D
    7+ S: t: p; H& c3 f3 t( U7 Q
    8
    + N: u7 u9 N5 m3 I9" h6 w) Q; C$ k
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:2 T+ u) R+ O, p$ _) C6 x! \! A$ F7 R

      S$ s) C* M! R/ y( M1 u0 @idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')( U; B: q" T7 }% a: g
    data = np.random.randint(-1,2,len(idx)).cumsum()
    0 N" L( E0 g# D1 `s = pd.Series(data,index=idx)( U3 y6 `+ e. A# @4 A3 T
    s.head()
    8 |3 d4 K' V( j3 {3 {( I+ K
    . B, A( D4 {3 v! M# FOut[122]:
      k2 d( h# \6 P3 [- V7 w2020-01-01 08:26:35   -1* b" Z/ m. k- X% P
    2020-01-01 08:27:52   -18 z7 x9 T" w  B
    2020-01-01 08:29:09   -2  [. m, j8 h& r) Z. x. i, _
    2020-01-01 08:30:26   -34 P, `% L" T2 L( d; ?& N
    2020-01-01 08:31:43   -4
    ; G0 F+ D. o3 _4 k( w7 Z' W' DFreq: 77S, dtype: int320 Q% I) O5 @8 Z, X6 l8 j) `
    1
    ; j' V# D* v* g7 Y6 ^: k; c2  h5 b$ e+ N- _* H2 ^
    3
    % K& X1 Y/ K; M3 u% f* n) v1 r4
    $ _) _9 W3 N6 e- r! [) c2 n) Y- I5, V; b( G8 F( C/ D/ n
    6
    % t; a: F! x8 W" D7
    7 c3 ?0 ]2 n2 @! G8' u* U( M. I. i8 `5 Y8 F2 `, w3 d9 P& q
    9$ H2 x: `( z4 f5 o7 @! E5 Z
    103 Z( M" a+ K0 w' H
    11
      B- q7 W- y& Y5 v7 y/ p124 V7 M$ m2 A. X. }) z  f, j, S
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:/ _- E4 `0 l% L4 f; N0 r; f

    ; f" \- F0 u9 h3 r# \s.resample('7min').mean().head()
    - S5 X6 a& O2 b8 FOut[123]: / T6 N+ O0 e) b% [; y7 R. S
    2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值3 Q! V9 O. n1 u
    2020-01-01 08:31:00   -2.600000
    % b. q9 H  A2 S2020-01-01 08:38:00   -2.166667
    8 w( x4 ]9 r1 Z& q2020-01-01 08:45:00    0.200000
    + g& E6 M  b# n0 e2020-01-01 08:52:00    2.833333
    , Y& G7 r. {" e6 ?0 t* EFreq: 7T, dtype: float646 ?' l: `: G+ n$ h5 ]6 F
    1$ g) c5 h' J/ ]( @$ d
    2
    & Z9 ]% K- v" ~) V: w32 ^9 T$ X0 {! a
    4: Q1 e1 k! L' ~7 p
    5
    , o8 M, _5 P( l68 g7 _! A/ f! ?: V7 L- {8 M
    7
    / H" x, i2 g' y8$ k& q' v- w# `  l7 k9 a6 Q
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:0 H- p7 }/ [/ |8 R+ l- x  }
    ) n5 O9 _4 J% z4 D. T
    s.resample('7min', origin='start').mean().head()2 H  z- k. B- l" q8 p! S
    Out[124]:
    2 V! _& n! b: I( b2020-01-01 08:26:35   -2.333333
    $ L6 W+ ^" J9 l4 a1 W2020-01-01 08:33:35   -2.400000  v5 Y/ I6 I* {6 b. C  p
    2020-01-01 08:40:35   -1.333333: I3 ~' k) G! P
    2020-01-01 08:47:35    1.200000- J% o* i2 C3 h0 W. \0 {8 N9 G
    2020-01-01 08:54:35    3.166667# \* e" ]# O& P: n
    Freq: 7T, dtype: float64
    % l; j5 ]9 u- @- d% b1# w+ B3 T1 w* C* \7 s% U
    2: P& C* o' B3 e, m7 d
    3
    ! s; ]- o8 n( l/ f4' d8 {5 @5 F# l- h0 }7 \/ w- i
    5
    ' x( q) s  s2 h( f  w6
    : y! e) j7 m- I! Y/ l' S7
    6 a2 y7 |, t& R' h8
    + a  V3 o/ E7 ~) R  d7 I6 i/ Q  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
    6 J  \7 Q0 P4 O/ ]: A4 ?2 t
    ; u% R  g# n$ B5 \8 N1 G1 A; is = pd.Series(np.random.randint(2,size=366),
    % X& H3 x# C" m& y- v/ L              index=pd.date_range('2020-01-01'," O+ t( `9 a! [6 |! b6 U
                                      '2020-12-31'))' n; U  Q' z% M, M  N

    ( L) M3 \, J* ~; u
    1 k, s" e5 ]$ Xs.resample('M').mean().head()8 a( {: Y8 X' {6 P
    Out[126]:
    / q) \7 W  H# O9 t2020-01-31    0.451613
    ( v1 M) Q3 W; ]2 o( e) V5 v2020-02-29    0.448276. ]/ ~2 I: v8 L! G6 L& Z* J! b
    2020-03-31    0.516129
    + p/ q2 |( U& S0 u% p2020-04-30    0.5666672 h8 g3 R, M+ |: A: K, n9 a& Y
    2020-05-31    0.451613
    & A, _5 T1 Z2 Y; d3 t& yFreq: M, dtype: float64
    + M0 M3 L, o$ P' p$ k# ~& ]7 `3 ?( U; a( R3 @
    s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样: a8 N9 a5 {5 c  h& i
    Out[127]: . [* Y2 m! K3 ]! z
    2020-01-01    0.4516133 P; e4 O* J  M$ Y, O% }1 l3 U
    2020-02-01    0.4482766 t5 M& _3 h& U$ O/ _% R( \( X( `6 m3 u
    2020-03-01    0.516129
    " b/ |7 M5 m6 J3 `2020-04-01    0.566667
    0 P8 e9 P8 N( U& b0 X$ i. C2020-05-01    0.4516135 m2 Q% g7 X+ P
    Freq: MS, dtype: float64
    * S: E& O4 ~! U) Q
    ( ^  U2 V  N' t8 o3 L1
    ' o! m7 R8 W3 L2 n* o6 A. M2
    . u$ `: h0 U+ G7 \! }# |9 {3- M; P- d# h% ~7 ]
    4
    8 b6 V4 B7 F5 s0 K% ]  z5: ]& k+ M7 S: H* j7 ^
    6
    ' b* \5 D, l+ D+ u7( o" y) \8 t; C$ e, l
    81 j7 P4 a5 M. R6 F3 D
    9
    8 Q, _% t/ n* y! B& m; `. o% x10" k& d: a! y4 ?6 T9 C
    116 S4 G# s4 i3 a; w$ l. N9 x
    12, c2 v+ Z- ~/ t+ D
    135 M! S7 R, @& |& q$ v3 R3 Y) m2 ]
    14# D% b. o9 J( p7 n% C/ }
    15
    0 e% }% d) o6 a/ }1 U16
    # [* F  o% R) n17
    ) V' }& P& e9 D0 t0 K) \: T0 d18
    8 ]0 ?: S8 i0 E# y4 o  p196 z) D; H1 n3 O  ^8 R
    20
    5 `6 ]  Z3 O! c( [- ~  N, x21: D. ~7 g% u* Z  l' p/ F
    229 X5 @& R) c/ V8 f/ [1 P3 j
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
    # c6 x6 z% n! ^4 Jd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],1 u# e8 ~/ ^2 M( p; g& b" R& S
         'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    " `  p; L7 i. v* @/ Y  q: ~df = pd.DataFrame(d)7 b: f; m: r$ w4 y7 r. L6 C! z  q
    df['week_starting'] = pd.date_range('01/01/2018',
    2 i' Q: q; @1 j: f                                    periods=8,, a7 Z( M: v( [
                                        freq='W')
    0 L$ _, f2 _1 \4 M3 Hdf- ?& E; D8 B/ S7 E2 _* N5 ]
       price  volume week_starting
    ) [  x& z: q0 J' U4 L! e0     10      50    2018-01-07) Z' I% n, l, _0 }- D; V9 R. H
    1     11      60    2018-01-14( x$ `0 U: G- d
    2      9      40    2018-01-21  q+ x% c( N( P& I" @% f' V( b: n
    3     13     100    2018-01-282 a! {! S7 r4 N7 q9 q) r4 }
    4     14      50    2018-02-04
    , h' S0 |9 ?. L" U* n0 f! |5     18     100    2018-02-11
    1 I, i- V8 i$ d  u6     17      40    2018-02-18* A% V5 c* l1 Y  \
    7     19      50    2018-02-25+ k( _, t0 n& P% [1 Q2 y) y  }
    df.resample('M', on='week_starting').mean()
    8 p# ^8 T! H& \               price  volume! x' M0 r# k  m$ N) I5 N- d/ o
    week_starting
    ! E  y& N& d- b  T1 |- F6 A& E2018-01-31     10.75    62.5
    8 [2 ~4 u" n' \" L9 o2 }2 J6 w2018-02-28     17.00    60.0& M7 w5 Y9 ]% \, S& z6 P# q; S9 M

    ( H( \9 [5 `7 K& V1
    / _( _. g; ~& \' ^! d2 M2
    6 H1 l$ c# L9 q% @/ `, }* R) C% t3
    - H% d! b8 P, b. H2 W" k4
    : A; G7 N7 U3 p  K0 A. l5% p  U% q& O4 }
    6
    " \, V) ?  {$ E7 W4 [: C" B7
    ! [" F" \6 v4 s! }8
    8 ]$ b' J( p2 Z- W1 g) u9
    3 i+ q2 z$ ]# b$ [10  Y/ K+ h4 i6 b3 n/ r+ ~& q) T- ~
    113 o% z3 _' {+ E: h4 E
    12
    * ^0 e! E" x) O3 j. K$ t13. ^4 G2 H5 _8 q/ a/ g
    14
    ; x2 Y; e# M- Z* a157 D2 n: N/ A$ D! }- D& Y! y
    16
    - ^: x6 n; `  W! B, J1 b8 d7 }179 S) n: N2 w5 J! s5 B' ]2 P
    187 w# w% V& l& }" u0 l% i
    19
    3 r  l/ w' e: F- T$ `1 C' |* i* }20
    * t% Y; z  g! v: T21
    2 E: A7 R( c. A对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
    9 T) z. m* y5 S4 x9 `8 _days = pd.date_range('1/1/2000', periods=4, freq='D'); L$ |3 G& d" M# C; r6 d* V9 K( [
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],5 z8 q6 k. y$ o' a- c
          'volume': [50, 60, 40, 100, 50, 100, 40, 50]}3 g' q8 r0 V6 ^- }4 `+ R$ @% P
    df2 = pd.DataFrame(7 @& w, Q6 m; O/ a
        d2,
    # `5 p6 N1 K4 Y' K" `- }& E8 C" g    index=pd.MultiIndex.from_product(# }/ c' `3 [! ]
            [days, ['morning', 'afternoon']]2 g+ Q( ^; E; M$ A6 \: G7 M
        ); `# C% p, p+ ]" F8 t. q0 l( r
    )
    # i2 V4 U" ^3 G4 E" d: ^# c3 K- c/ Idf2
    7 x4 S1 x  ]6 D                      price  volume
    $ b6 H/ O* l% i; g  [5 s% ?2000-01-01 morning       10      505 R9 z% ^8 v% @' Z
               afternoon     11      601 N' P  k: D; J; I* |
    2000-01-02 morning        9      40
    ! f" z. n3 D! k1 e5 ^, ?) F           afternoon     13     100* S, x1 `2 |2 h8 n- n" t$ u
    2000-01-03 morning       14      502 }# W$ R2 B9 I0 ^: a
               afternoon     18     100
    1 e$ w* r  a2 i. N; o/ d2000-01-04 morning       17      402 S$ h* M4 g6 P
               afternoon     19      50
    2 l* @" s3 N/ n5 idf2.resample('D', level=0).sum()
    - ?7 @$ m% H( M7 q' |! K- p            price  volume" D- I$ r" E$ w7 \% w
    2000-01-01     21     110
      S* c' n+ k" P4 T2000-01-02     22     140
    . n$ I( j7 Z$ ~5 t% Q; v, X2000-01-03     32     150. x  ^2 O4 f- O
    2000-01-04     36      90- E2 T0 u- C  x4 |: k/ l

    9 v. C, o* ?$ q9 Z* i1
    , S0 N  V! q* T: Z% K' I0 r# O2
    % H8 b, @- b9 B4 K3 V% `0 ^3# @3 E8 p' i; H+ @0 s) d
    4" M- T' D/ X( D5 Q' o
    5
    ; G2 o- B5 O$ X+ {0 O6
    - C% I" D* q2 I8 x$ A7
    / D& w* ^5 E* F+ e) L" r8
    4 {! [1 Z2 E5 q+ [% h91 a; g# P; [" {! ]
    104 a* C% ]+ S8 L; G4 f
    11
    " R1 C1 D: i' \' V. U12/ T" t- @1 ?% Z3 H3 h
    13. {' H+ h2 F0 L. C# [7 Y" H3 m
    14
    4 M5 U* n; d: v15' [4 T4 f9 y! V# z
    16
    & _5 R! n* M% O4 o" V17: m' u* p5 |3 o7 T% v* q! X
    18+ l# m% d$ X4 z! Y. u
    19% {% M5 E- q  s) \+ g# Y
    20
    . z, F6 P4 F; P3 _4 R' Q3 T3 B21
    # q$ d. X' s" E+ P5 _; ^! N8 F228 e/ u" [' z+ r9 Z; s5 J
    23! x# S& u6 c$ w2 X% ~2 A+ X1 H% u, M3 V
    24. d8 a$ H& e. j" Y& V9 q
    25
    + T  J. _/ N$ r$ w7 U根据固定时间戳调整 bin 的开始:0 L' c# c% E& U/ W1 g
    start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
    5 [$ t9 s' Q; Q4 p& r9 s. }rng = pd.date_range(start, end, freq='7min')
    ) z. C# G" |6 h# ]ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
    ! H: @) V2 ^6 \7 i  |9 J+ dts3 N* {/ i8 S2 N' M1 o& r7 P
    2000-10-01 23:30:00     0) X7 Q; r$ I, x' u2 w' V
    2000-10-01 23:37:00     3
    9 e  z" n' ^; u2000-10-01 23:44:00     6& m' H" I5 B6 k% d" G( }
    2000-10-01 23:51:00     91 I# K+ H, |- Y* {2 r5 H% V
    2000-10-01 23:58:00    127 M/ N8 d: Z1 V0 }( B% i- O
    2000-10-02 00:05:00    15- U& C; p! }: q4 B3 X7 F1 J
    2000-10-02 00:12:00    18
    # I( w3 a) l" C2000-10-02 00:19:00    21
    2 {( |# c; @' N2000-10-02 00:26:00    24
    * w" O! P3 N: a; ^# S, d* z+ a* [' DFreq: 7T, dtype: int64
    9 `& i" K, S! y+ i' R3 z% |/ q, \" y# I! |) V) _. y! T
    ts.resample('17min').sum()
    $ [; X% m- v7 Z1 i2000-10-01 23:14:00     0
    8 ]$ }- c' D) O! a2000-10-01 23:31:00     9
    % ^# S& j7 q: E! D. R2 T2000-10-01 23:48:00    21% A, a( H0 l5 i3 S# k
    2000-10-02 00:05:00    54
    7 d& U' d8 Y+ L$ m2 ~3 i2000-10-02 00:22:00    24
    ) K6 i% q+ _4 e+ F6 f  cFreq: 17T, dtype: int64$ a/ ], u& c; U5 R0 D! |8 r

    ; t+ ^5 F) U" rts.resample('17min', origin='epoch').sum()
    " V) {. R' B+ n+ o2000-10-01 23:18:00     0
    # E0 R+ e" }0 p/ b2000-10-01 23:35:00    18
    . ]$ u& W2 H5 M. F: i8 z. @2000-10-01 23:52:00    27) x( R# L3 C- [. V" V& _1 a
    2000-10-02 00:09:00    39
    . @6 G  G7 e2 G% x. I& y2000-10-02 00:26:00    24- L1 S9 p- W) y5 q
    Freq: 17T, dtype: int64: O5 \0 ~8 ^$ k9 z7 q; \1 D' r$ x
    6 _0 x9 W- P* D4 z
    ts.resample('17min', origin='2000-01-01').sum()
    / ?: a5 d5 r/ v/ k; V1 K2000-10-01 23:24:00     3
    7 c) h- v$ l" ^# Q1 H# b2000-10-01 23:41:00    15
    0 n. ]4 t- T; e; W: j! `% h8 K2000-10-01 23:58:00    45& `) W3 H$ T5 Z- y
    2000-10-02 00:15:00    45
    5 e, \3 p9 d3 `& d( E( ~Freq: 17T, dtype: int64
    , R! V# n2 l! ]# u' n# [
    1 `6 ?% S3 y- p  O1
    ) a8 i) u0 U. [5 x2
    ( g- x& w7 o" x& `39 D5 k1 j8 I! F  N/ u
    4/ j! I/ |: l% ~* G! M) w8 j, y6 |
    50 D# F. W$ H+ s2 y
    6
    : H1 @& O/ B7 ~- @& ]7 F+ |7# ^  y! t2 M  P2 }% \% G0 T& B
    8, h. i1 n" E9 k; [& x2 k1 K
    99 e$ \3 w% ?" c. u; Z
    102 s2 w9 k. [9 e6 Q
    11  [, V7 s% L6 t  h
    120 D' y, d; E' O% @; U1 D
    13" r8 h& E, o  d$ z7 E  w& ]3 c
    14' y( `2 P1 p' q" b* w( p0 w4 l
    15
    $ m: c% b! w6 k! S16
    4 x" b; F4 z0 C4 g+ j( F17
    % [" [- o1 G0 u7 Z; f  u% y187 I5 Z3 p# c! g
    19+ G- U" `! S! s8 l7 K, V
    20
    ) r& ^( s2 l$ E21
    . o6 n1 T: a6 }$ n8 q22
    : Z/ H4 T0 q8 E1 J, o23  y) R. ]5 O/ D& U
    24
    3 Q- W+ Z% a. _* \4 M25
    & z& l$ l" R" m$ I26
    : C) _* Z* l# f$ I  n) X27
    9 r9 o, t% u0 l. Z9 w28
    # B) w2 g! t" o- y5 D29
    $ v6 ~/ B: s* `" M) v30
    # O! y. ~0 d+ ~7 ?4 u9 P5 R31
    ( b; ?$ ?) ]' Y+ y, V32
    7 b, u- s! y  y, G. s: L6 L! ~33
    : z6 |3 ], z* V) c4 i  R# S0 X7 \34
    2 S& a! d7 ]& Q2 M# V35
    . f6 ^- q: o1 Z% d363 R+ b, x$ f" j
    37
    ) m8 t' Z: _) O0 ?) V% ~' u如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
    & }/ O  D9 i; Hts.resample('17min', origin='start').sum()$ a# t: O; Z$ Q
    ts.resample('17min', offset='23h30min').sum()
    8 T- d# W9 q2 V2000-10-01 23:30:00     9. F" A9 L: d7 o* b& O0 x
    2000-10-01 23:47:00    21/ A# O- f) L) ^" Y5 o
    2000-10-02 00:04:00    54" H, v% c$ u3 N* V& E! g$ h+ `
    2000-10-02 00:21:00    24; j$ B" ^6 r" W9 ]
    Freq: 17T, dtype: int64
    / \3 B/ E  X$ Y1
    8 s; I% \/ R8 E" u( I, z2; N0 J5 M/ x6 Q3 c7 ~; c; }- S3 P. v
    3
    , @' u# T  A. }4 o! c' _( \4
    + N3 F6 Q0 D+ ?& `9 T/ d- F) U5
    & d. f0 L2 M) t, K$ `6' \8 z8 |! y" \( s
    7
    2 {* s% v( g( a0 W10.6 练习
    3 D+ B1 w, D. v  {$ d/ SEx1:太阳辐射数据集
    ! o, q: Z/ ^6 D3 _0 o4 `现有一份关于太阳辐射的数据集:
    / Q9 Y8 ^% [7 u2 U# W+ g6 Q" I/ B  j3 d* e5 R- u: [5 Y( I$ |
    df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])0 g$ d8 W% M8 D% T: r
    df.head(3)
    , |6 R& G% ]$ x6 x6 ~( Q/ s! Y) q% \" e2 C" H8 K
    Out[129]: % L3 o" n2 Y4 I* j, w
                        Data      Time  Radiation  Temperature
    ( C+ F5 b) D, F) I& j( _0  9/29/2016 12:00:00 AM  23:55:26       1.21           48( D5 H7 Z  i/ M6 Z. a) Q4 O! Y: D7 \
    1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    % p5 m) o1 l7 F$ Z* @2  9/29/2016 12:00:00 AM  23:45:26       1.23           48* T9 r! |, O; y- w3 V6 f% e; ^
    12 d$ m- y& T) ^: G, F4 }. P
    2, b) M4 U% |- p% h& d- x& V9 s
    3  W$ X$ b% H. f& W% q
    4. \% W7 F0 X% H
    5/ ^6 a9 K& P) c8 _: p: S3 P' s0 V2 V
    6' r7 Y4 h9 B9 P0 b2 _
    7
    ' V2 |( X4 S$ }4 |0 L% _( Z4 Z8
    7 U( ]# _0 O3 c' q7 O& C7 @( p/ z将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。/ A4 ?4 d) P# j* V9 ]/ `; M
    每条记录时间的间隔显然并不一致,请解决如下问题:( W2 u; N4 o# m$ e
    找出间隔时间的前三个最大值所对应的三组时间戳。
    / ]; X! n  l) x/ s& {# F是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    4 g' @6 v. m5 v% ^( C) |求如下指标对应的Series:3 M" s1 N8 D8 s% g/ |
    温度与辐射量的6小时滑动相关系数! N# G8 [4 p9 o( u; ?8 J; h+ e
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    ; {9 W0 c; a) O5 {/ K( V& e" {5 \7 `每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)9 u/ w. y) V5 f5 i# D
    import numpy as np* }9 ?3 P" N& l9 ]7 ~
    import pandas as pd
    - W- ~/ s9 l+ h# u3 S0 Q9 a5 M# I1
    8 C1 F1 j1 c; e6 y22 r* J6 Y7 G- |0 k9 s
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。( c/ t( D$ U; j* ]/ U  H2 k
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列, W  d; Y2 ?3 q1 L
    times=pd.to_timedelta(df.Time)- E, H/ w# u. I, J7 O
    df.Data=data+times
    / |- ?2 O* u; e+ }del df['Time']- N+ U8 q6 X8 K8 I4 ^  l
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留/ H5 b5 |! ~" y: j! X' `' h# b
    df& e& v. t4 a/ _. X- |  u% B" o
                                            Radiation        Temperature
    : o; M( ^0 C2 h% d) n8 [, X; B3 ZData                $ x4 l) I9 c0 H7 U
    2016-09-01 00:00:08                2.58                51
    " b6 L+ ?/ y# x9 P) z1 U1 R( }+ }2016-09-01 00:05:10                2.83                51. ]3 |& `+ t: }& U1 V
    2016-09-01 00:20:06                2.16                51, r- j/ k. \3 I5 F3 N8 j+ V$ P) q
    2016-09-01 00:25:05                2.21                51
    7 X/ r# b6 ?- V, Q' B/ T) B2016-09-01 00:30:09                2.25                51) C  J# B* M% ^/ ~
    ...        ...        ...
    : M9 l# T8 x( E6 k/ o2016-12-31 23:35:02                1.22                416 x1 J8 K# ?; a% d* |* T2 {: o
    2016-12-31 23:40:01                1.21                41
    : C7 g& @" N+ S3 ~6 A4 B- i2016-12-31 23:45:04                1.21                42- |* g- b8 V7 F9 Q, B/ B
    2016-12-31 23:50:03                1.19                41
    - t+ Q0 ~4 f, W7 f- C2016-12-31 23:55:01                1.21                41& d1 Y5 V7 x7 x" r6 @
    ( \# Q! u* l& P) S* F6 r
    1) U2 Y/ g" N2 x9 w$ L
    2' C9 n0 E# W5 _' l( t1 e
    3
    9 W( S0 O1 l. L$ y8 M0 e( o4( B( I' S3 o: [0 E; }+ j  U
    5
    : K$ y. O# M( L: ]; N& g6
    $ _) O8 b+ ]- v$ U7
    1 g- J* x# v7 \" p3 \" U8% ^* ?& n0 ]! c2 v5 a& a+ U) l
    9
    " t+ d* U$ t: Z+ ~% X( E10
    ; `; \/ I8 H  T2 q+ v# z( v% t11, C+ J7 U, K% P& M/ D* k8 N/ y
    12
    1 n2 Z6 ?* m7 v4 {13  d* S$ f0 A  c/ W0 D3 i" W( I1 S5 w/ N
    14* `% F  n$ b/ K5 J
    15! |9 l" M/ h6 d" R
    16
    . L8 Y3 H4 s$ X, E; s  b& t0 v' ?17
    7 n4 |7 t1 \0 [! K. l8 E$ d, H& |18
    - G& ?: p* [: r0 d/ U4 l5 n) y7 q191 _- w$ y5 e# ]3 F2 K# D+ ?6 G- _* O
    每条记录时间的间隔显然并不一致,请解决如下问题:
    / ~# s* B0 R* o# k" O$ a/ v找出间隔时间的前三个最大值所对应的三组时间戳。3 j0 K5 ?3 R/ a7 m5 g0 K
    # 第一次做错了,不是找三组时间戳8 f& M4 a" k" R( L9 G" P. `
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]! J9 N7 E) P1 {" w1 u
    df.reset_index().Data[idxmax3,idxmax3-1], @/ X/ _, [3 Q3 D2 ^
    6 e2 Z" l* j8 o% Z& Z
    25923   2016-12-08 11:10:42
    & ^- S9 V! @3 E) P24522   2016-12-01 00:00:02: J7 L/ R. p2 |& l+ E6 ~  B
    7417    2016-10-01 00:00:190 z+ a; c: \  q3 w& R7 a
    Name: Data, dtype: datetime64[ns], P) W% t! @3 u* k" Z: i( M
    1
    2 x) W8 d! b) \9 o26 \0 @# F/ K# c- J3 p
    3
    ( g; a! ?5 ^: |) J8 o43 A' F" Q9 Y4 w: |
    57 k5 ?, l0 M! o5 t% n; q! a& z" b0 C
    6
    6 d- B8 t* j, L% F9 U7
    ' _! ?/ Y. h( N# o, _; A88 J* ?7 D9 W  F* B
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]& \8 M$ J1 T9 _$ i1 L8 @8 C1 n) B. U
    list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))3 ?3 D0 z/ h$ ~3 F$ R
    2 L3 t* a9 k: O+ T
    [(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),
    + T, N2 ^) t# e$ B/ F (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),( R0 [8 J9 ?6 E+ R' ^: ?3 e" B* _2 _
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]2 j' i5 e6 A& ~8 P. B' k3 @2 k. f& @
    1  k' X2 J, Q7 c7 C) s
    2
    3 d% x: _% @6 K6 q* {% b3
    ! d1 ~% @7 T1 k) t+ D" A4 \4
    : W# D* X3 x1 d( c  X! Q5
    ' S) V+ {" f- H# B6
    5 m& @  d  v  N! b参考答案:0 K5 K% M5 c+ s
    # a* K: O0 ]  }: @
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()' B, G$ i" O9 l+ }
    max_3 = s.nlargest(3).index: W5 X: w) z1 v9 K% Y+ N: ]
    df.index[max_3.union(max_3-1)]
    ! D$ j/ W- j$ I8 B6 W8 t: r. n1 g
    7 B& t% \7 x* I4 a( O1 ^Out[215]: 8 C) F* C' I, C4 y$ W- P7 s& W- x% E
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',/ p2 B5 P% q# H: Z9 b* Q
                   '2016-11-29 19:05:02', '2016-12-01 00:00:02',
    ) z6 ~! a  l  r! P" Q) B               '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
    ! K" d6 S- X5 f. E6 D. T              dtype='datetime64[ns]', name='Datetime', freq=None)# K: j+ P6 N4 [
    1( O) K! c. o( m- B9 ~+ Q
    2, |2 e4 ?' ^( J/ B) V: P( C" d6 d
    3
    0 L5 V4 ?$ M( Q4( N$ n( e" D, @* W) Y. u# v
    5
    5 }1 O; s3 T) ?" h6- t) h/ x. H0 ^$ S$ Q
    7
    ! Y4 F0 V, A, S" F4 x8
    7 Y0 L& ^4 F2 y% Q95 r; P/ k% K0 q- G2 d
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。5 l' o" I  n/ N; t, D5 v/ A, i
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
    1 f. a5 ~" L8 I' Ms=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)0 R% ]  d. K# [) [; K# J; _
    s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
    : u0 E$ a% z; l; Q- {) N: k! G2 b' U, c3 Z. `" ~
    (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
    7 G5 v2 d+ L1 ?# u; a3 f1
    3 ~' a2 k; g. D8 u2
    ! ]) o: l2 Y( ?% T4 H7 W& b. [0 e35 O1 {% r+ N- F9 X7 G9 D
    4
    # g$ W" n/ d3 j8 u- W  A55 S; \$ o% n/ h6 m1 h( J3 _! n
    %pylab inline
    # n& G& b3 O+ X3 q' j5 B" }, ^* C+ S_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    3 C2 W* T, p1 t2 Xplt.xlabel(' Timedelta')* a. R2 }  X7 m& J( [) f& @
    plt.title(" Timedelta of solar")
    ( ^8 n$ |, R3 `4 s1
    % C+ Z: z8 t& T- X" ]) t2
    . F) l# \3 o4 o) o& @3
    % r" J" C! b( D7 Z4 T5 S% |9 P40 `0 Q; }6 Q" u5 q
      G) Y3 G" N; Y
    . d4 h0 ?9 U% W. L- x0 M
    求如下指标对应的Series:
    4 z  L1 t: B1 W+ t8 j温度与辐射量的6小时滑动相关系数; `, i) N! @! O% ~( N8 H$ F% z( s8 s
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列7 n7 B' a/ j7 D9 l) y
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)/ e. E* r6 R- V! }8 }* y
    df.Radiation.rolling('6H').corr(df.Temperature).tail()
    2 K7 M' c, x2 `9 ~+ N
    ' ]7 b3 u" P' U. w7 h1 cData
    : A$ C4 \& ~6 H0 @2016-12-31 23:35:02    0.416187' ]% i* c% c: p
    2016-12-31 23:40:01    0.416565
    3 Y8 J$ m( X; W  d) }2016-12-31 23:45:04    0.328574! Y8 I- I2 R! K/ u& q4 @
    2016-12-31 23:50:03    0.261883# x9 [- x" `/ T
    2016-12-31 23:55:01    0.2624066 b4 u  I1 e2 }' Z
    dtype: float64
    8 q  k( @; a+ z. ?- P; J1
    9 z+ u8 A; C# P/ X4 r9 Q1 e2
    0 I1 T, k3 [) k3( @5 s4 a% v4 D) ]3 u9 Y- R9 |- a2 L' y
    4
    . y2 J5 ^6 ?5 ?/ V5; j/ Q% W0 M0 ]6 p, g
    6
    , B% c$ O! r' ^" P- S0 c7; z0 F& p: H( w9 c* [; v5 J
    85 M9 W0 H1 {7 k9 q) O
    9$ W! {  X$ Z$ C( _' y6 {
    df['Temperature'].resample('6H',offset='3H').mean().head()
    5 G3 x" W: H* o; s- x/ T
    9 t0 @& t% q) ~0 s3 kData, F' u% x5 x* a
    2016-08-31 21:00:00    51.218750
    ; x* [' m% P9 b, c7 B% k* y2016-09-01 03:00:00    50.0333330 ~; g) Y: B0 F* P
    2016-09-01 09:00:00    59.379310
    $ b! J3 H  }* t* U2016-09-01 15:00:00    57.984375
    , x2 \1 ?. I! M: Q2016-09-01 21:00:00    51.3939391 x  \# r9 K( ~) W
    Freq: 6H, Name: Temperature, dtype: float64% }4 J3 S8 {& ~6 s5 J, q
    1
    8 j4 m9 ]3 l8 k* E% Q1 r2- r" Z2 L/ z) C& r9 }' d( S0 J
    3, N$ ?& a8 R/ ?8 L' }0 L
    4
    , i9 i; R, J( H5+ n% h! T- E, Z9 w  h  s
    6
    7 P! g4 `# i6 a6 m7
    : E# e9 d7 l3 _/ E6 h: Z8 i, C& B% ]8
    % q9 U, m$ ~2 l8 c9
    1 G- R5 o3 I9 h3 Z* c  C' k# |最后一题参考答案:
    * x3 h5 V1 G. F$ b: C8 n/ d! f5 R4 t2 z# Z2 a$ p( ~7 T
    # 非常慢
    7 X, h' V" n( B- w$ Q/ |my_dt = df.index.shift(freq='-6H')
    ' d  H( f* }6 O' h- l5 O7 W* x& Eint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]) i) R+ F$ v! Z8 z
    int_loc = np.array(int_loc).reshape(-1)8 T1 p' {: |# i0 i, A) T* A
    res = df.Radiation.iloc[int_loc]6 |, J- z4 p* c5 {
    res.index = df.index/ V! m8 L: v# c6 v' ]  F4 t
    res.tail(3)
    4 T0 A9 z! k( x' M; |& T1- e" k9 O. x' x3 ]! J# `- _( m
    2
    " g( e' j. k& h* D8 A3
    ) x8 u1 j; `! m* d# ~" J* U47 p, ?& i- ~6 n  y; n/ b. A
    5& S1 k6 Y$ {1 ?
    6) l2 c8 J" T1 _4 h" r4 h8 L
    7: B; l  c! {: j' ?" l! e
    # 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    - B& r& r: |; ptarget = pd.DataFrame(2 n8 w& x" G; s- t- u7 Z
        {
    4 J, g2 G& R7 ]4 Q' @* X        "Time": df.index.shift(freq='-6H'),
    + \# d4 o" X4 v% R, D* w        "Datetime": df.index,
    9 h6 q9 i$ i( T9 b; W+ h5 y( w    }- M$ ?2 w& \% a5 k
    )2 n# W/ x9 }9 I' ?  R! U# h
    % f$ |4 y6 F: S+ ~$ \
    res = pd.merge_asof(7 U, C# L! T  k8 L7 l% a  v
        target,
    9 s6 K3 V- k3 {. V* c    df.reset_index().rename(columns={"Datetime": "Time"}),. J" r- P9 _2 p) P1 B8 k3 p
        left_on="Time",
    3 }! b% j4 V- o5 _    right_on="Time",
    : c' R4 d; \! D    direction="nearest"
    2 h! N! t' S% L).set_index("Datetime").Radiation
    ( x' }+ D5 O9 A5 W! x" l! z7 R+ o9 g- t: x1 D
    res.tail(3); t2 e/ U; A- f+ P9 r, A
    Out[224]: $ r$ G  w7 u8 |# f1 u7 R2 ~
    Datetime) n% X- W. M! E* V6 h
    2016-12-31 23:45:04    9.333 U* y* B/ s8 {* _0 B
    2016-12-31 23:50:03    8.49
      c0 ]! {+ a* ~( \2016-12-31 23:55:01    5.84
    6 w* M6 W. C9 E) S. x3 i  W$ F. vName: Radiation, dtype: float645 `7 k3 W$ m. V$ G( c

    # S2 J6 F- E7 H' X  i8 j9 l+ j5 P19 Z5 l1 a$ \: C" w0 d, H
    23 B8 r# X2 ^, z; {& c
    30 K8 U5 {# }* i8 y& B
    41 a$ a; ~, j& p# P: _8 s. _+ \
    5
    9 F8 B9 u& ]( ?" g, G* J63 _# o' o& \$ S
    7) f' Z$ L  m7 r4 i8 J
    8! `' f* O, _) D% i
    9  ^/ E1 r  ?& I9 \! O0 Q2 D, e
    10  c8 q% ^) }1 O: S$ ?
    11
    . z. y" Y) a, O: ?12" K4 I- P) H' F) \; a4 c8 x( h
    13
    " Q9 w2 P( o* e/ o- u14
    - U' e7 q1 `8 B- Z. x% r15
    6 ]6 n6 `. _7 ~6 }; K* q. F- M) `16+ ]" Q: q) ?' D
    17( L, Z# b$ b. H
    182 ~9 M7 [" [0 {: X8 S1 {9 A( d
    19+ N. u& `9 `% o- ]" M+ R
    20
    4 r' i2 i5 o1 f0 O! u21
    0 C. I' ?% u' \7 a' [22
    7 O1 _' b: {! R# j% f23
    - v; C( u9 ^* ?. ]6 X: m2 b4 zEx2:水果销量数据集" F4 A. W3 d, S1 ?( y
    现有一份2019年每日水果销量记录表:
    # P9 i, k# g/ z
    6 B+ k1 q2 ~2 ^* Kdf = pd.read_csv('../data/fruit.csv')$ O: d& w3 x0 \0 O. U' |3 e. C
    df.head(3)
    1 J7 ^8 q* A' v. ^
    / u# I  M; n% VOut[131]: 3 k1 ]& Y! S! V9 W# t2 F
             Date  Fruit  Sale
    : o- Q9 @8 V# Z" i0  2019-04-18  Peach    152 `$ V, a2 m* d: H7 A
    1  2019-12-29  Peach    15
    * @) o+ Z9 n- z4 E4 Y2  2019-06-05  Peach    193 q: O+ w" V5 Y) Y/ q0 J
    13 L% Y, k, q/ I' G: G# W0 t
    2
    ! |4 ]. _" c- S: G  y. q3) }# L, z' k2 E8 ]- o
    4, [, }) [. \( |9 J% j6 v  N' W1 I6 W
    5
    2 x: A- ?8 b( `1 P" o6
    5 g; v7 N4 b$ \" K/ Q! ]" g7
    : ~$ J# @1 ~  P/ Z8& }+ i9 k0 Z  }* F+ [
    统计如下指标:# n- R5 f6 G+ w/ N; W# m9 Q* R
    每月上半月(15号及之前)与下半月葡萄销量的比值: c8 S! [' _. }' ?9 b; d9 C
    每月最后一天的生梨销量总和- J7 B, r: `1 G% I) I
    每月最后一天工作日的生梨销量总和
    2 j6 b4 b$ t8 \; u每月最后五天的苹果销量均值
    $ W9 e& G, [+ b! F5 @按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    9 f/ _2 D; e+ z. y. u$ j按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。4 [& [7 a8 T# t6 z. S. e
    import numpy as np
    * `0 o4 U' j* \, M( Vimport pandas as pd
    1 p7 n# K+ I, ^0 E4 I18 b/ m& h) K/ L0 j" G
    28 D! ?2 a6 i3 ~/ ~4 s
    统计如下指标:9 d) S: Y2 e0 o$ g+ t6 s8 c- X
    每月上半月(15号及之前)与下半月葡萄销量的比值3 C( M* W, f2 n. i
    每月最后一天的生梨销量总和
    - |- N3 {7 \6 J3 ^; X每月最后一天工作日的生梨销量总和
    " @3 i; E& T3 u+ {每月最后五天的苹果销量均值
    . ?, a$ D" J8 s& y0 Q6 J# 每月上半月(15号及之前)与下半月葡萄销量的比值
    * A2 h6 `; ?  E; Q  Ndf.Date=pd.to_datetime(df.Date)( k( u# N$ d% K# L+ t
    sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    4 L9 h; f; {- Y& Ssale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊& y. H6 Q+ y8 _- v' n2 G. y
    sale=pd.DataFrame(sale)6 p7 o6 ]2 a: M4 k
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},' \; p! ^4 [" Y8 y6 g. _2 ^
                     columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
    & f7 h' r8 u4 g/ a, h$ Ksale.head() # 每个月上下半月的销量8 l9 S& w5 D- k/ P$ K+ [. F

    - d) Y3 |' V: f6 d  Month        15Days        Sale
    ) }7 v! L8 P5 x4 Z/ \$ ~0        1        False        10503
    1 R! T$ M+ R, g+ Q& `1        1        True        12341
    7 M! A0 L+ ?7 G% U4 V& v( z2        2        False        10001) Z* y6 k+ k% H! M4 }
    3        2        True        10106+ a$ H2 C3 [; L* {+ u
    4        3        False        12814
    6 u3 f; I7 x2 X0 E7 |1 J5 s$ B
    0 t) f: O3 ?/ B# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序7 K. R. F- j* Q
    sale.groupby(sale['Month'])['Sale'].agg(
      c3 J; S2 z% ]                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    2 ]' N/ ^. ^% \" v% Z- r
    - q+ n+ l, S3 X& [7 n" uMonth9 P+ a. x3 F) L; u- M! T0 |
    1     1.174998, C; W. d0 U; s1 ]
    2     1.010499! \) |0 \5 l' g% o! p. B
    3     0.7763389 D( ?& e0 A' l1 N$ }( Z' M7 h
    4     1.0263455 D0 ]. R& W, T5 T8 [/ _1 m
    5     0.9005347 s% N# ^, f! o  Z7 R8 E
    6     0.980136
    3 s7 }4 ]9 H6 M0 o6 N7     1.350960
    2 {6 S7 y* K' d& H( Z3 i8 O5 N8     1.0915847 F0 B5 H' T1 @! R
    9     1.116508
      }  f  m, H/ ]6 j  N" r10    1.020784
    " x' K* n  Y; E$ K, C& p11    1.2759113 R+ M' w0 B- x- g+ i
    12    0.989662
    ; l& t1 X- p/ V* O/ _& ]% j! EName: Sale, dtype: float64
    : b- y7 ~( N0 e8 U. x4 ?; W3 B  _- {0 B, @
    1
    , E9 }$ A8 W: l. B2 _( X# m6 j( ]2
    , M! y1 Q4 [) m; A% _  k$ }3
    6 f$ F: g$ [) c. K& K4
    ; c; M6 g" A/ N: N54 M+ t3 U: P/ C# `; J$ k
    6! b% t4 ~) ?1 {
    7+ |! G. ~  G0 l. I) s) Q2 H
    8! q3 V, e# \% u$ z. _' h
    9" Y! m( y# J5 Z4 N" M: W: F
    100 Z3 I  a) `' j0 s
    11* h/ t9 r* `& X9 V0 e
    12) ^& T5 N% A" W" S- e# E
    135 w/ Y$ Z3 d2 E( _
    14! z/ ~+ z5 s- U/ }" z; G* r
    15
    + b, E0 n. p$ K! d/ q: O164 \; j4 W# D& l* n! ]7 ?8 q
    17' ^1 A+ d5 A1 Q
    187 F% r9 T0 a. P# M) b
    19
    2 [$ c6 ?- h: i" S. E  c3 S4 d205 J. G. ~. v# e6 l
    21
    ! S5 ~% _( C: H$ H4 @3 h22
    * B3 i- |+ V1 |. N237 P# i: m# S* ~
    24
    . ~7 o; p1 X& Z) \& x25; L, Q& K* |$ m% a, k- G, S6 }
    26, q8 s, |) a- \/ X
    27
    " ?; f7 O& n1 g0 D5 A$ D: ?28+ N2 G. [6 A- K, h( ~5 q
    295 T( X" x) @# r
    30: K+ V+ {4 d, l* S
    310 M# [, r, s/ b% D3 A
    32
    7 j' w5 S7 R- @* k5 r33+ v# @: ^6 l5 p
    34, ?: \+ N, k1 S+ @8 A/ ^( `$ {* u
    # 每月最后一天的生梨销量总和
    $ P$ u3 ]' g( C4 \3 C, p* w) fdf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    . T3 O/ ?4 t! z' M; [4 D# E: N' M* L) x& `7 i$ a# Z$ h& _
    Date9 |% Q$ F+ o  A. }# e: c* r
    2019-01-31    847- j# I+ d% O% r; n
    2019-02-28    774
      m; p5 B; A* r6 m2 G) W$ `( Y( ^2019-03-31    761: c" W' Y4 w$ [, J' x$ `1 |# _. P1 f0 K
    2019-04-30    648
      r$ m  x1 C' ]  ?. ?2 b& J; n' N# x" X2019-05-31    616! D) u. e: ~9 y" ?( G
    1
    ( O' k1 l! F4 |1 L; s* `26 Y4 u* f( p: e- d+ [0 f! T$ f( D
    3  t4 K, S- \: Y# Y% @# i2 U
    4
    & U4 x! ^5 c+ z4 Y" Y8 m: {5, [5 U2 y# l' ]$ e4 r" E8 B' V
    6$ }5 U9 F! L6 j& c: a; u( p) G+ M8 R4 P
    7
    ) a1 d* C9 N$ p0 G; c& w3 t8
    6 h- |: B. i& H+ u2 o+ q8 _9
    , @3 m0 w. R( y2 Y% x# 每月最后一天工作日的生梨销量总和! M/ |  \6 ^7 {! N
    ls=df.Date+pd.offsets.BMonthEnd()2 \- t: K3 b/ D0 I- a$ _/ G
    my_filter=pd.to_datetime(ls.unique())
    ' ]% H2 W# u. \. @0 ^3 s, F- ?; ~df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    3 s9 i8 m4 a/ F8 A6 Q: _. X+ B2 \) T2 ~6 l/ b
    Date$ ^5 n4 b  n2 p8 w+ f5 Q
    2019-01-31     847
    . e# ~5 U  v  F" }# F- c& n2019-02-28     774
    2 S# [& t/ S7 a3 y0 O; u" [2019-03-29     5101 l7 l  b3 L2 G! A7 b# x* n
    2019-04-30     648
    7 _+ J4 `+ r& z+ `, M# }2019-05-31     616) Q  @2 `9 h, S" h  f6 f0 \
    1
    ( P( _1 s0 P! B' c2 t) E6 o) o& x4 s2! n7 d: o  w) H# P: L- R9 N
    3! T7 v6 M/ G  j$ `: ?4 h( @" d3 b
    4- W1 j& w9 [( X# T
    51 |5 O7 K' i0 K
    63 w+ A+ ?! y* d& G
    7
    - ^  O) [7 E: F$ Y+ d, H8( N7 @: t# P0 x
    95 j5 z1 w' Y4 u& r, @# g8 j
    10
    # t) h2 K& v! C: T$ Y  c5 D11# F1 O: R) Z) O# b9 i
    # 每月最后五天的苹果销量均值, D6 a: n, z1 j: ^
    start, end = '2019-01-01', '2019-12-31'& L& F+ z0 Y' i3 V* S
    end = pd.date_range(start, end, freq='M')
    % V4 @( Y0 ^5 mend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差$ ~8 _/ ^# ?# X) o
    - m6 O, x: y- A* t* t' m
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
      L! E3 c8 M( {7 ^td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
    , R7 |3 l# f# c' Tend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    9 M, e/ _6 E8 b4 [# `  t- h
    # L! h4 S8 t  q' fapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    * P4 Y+ p) r. c5 z- H3 p& Gapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
    , `% \2 N8 D6 l. S- }8 R9 \, E* Q* S* R, Z. _
    Date: A- T2 |% W! `, K
    1     65.313725# c% N, @1 c! W$ V) z8 y6 W$ s
    2     54.061538& P  v+ B! v* D6 ]; F+ U% T" }0 D3 g
    3     59.325581
    ' v& y+ C4 W% e( I2 Z6 `- z8 }  i& ]; K4     65.795455. k9 T" S: W9 `8 e0 n8 J
    5     57.465116
    / R4 ]. L: @8 C5 }2 O* }& n9 z* V2 j: v9 _' d# R
    1* l, v  m9 p8 P( P
    2
    + }. ?# \5 R% Z6 k' o38 E0 E- Q6 ]/ |' K$ |
    40 r' u0 {8 w. C7 H' x5 j
    5
    7 g% u1 s: c2 ~6
    ; ?! e4 f' `& X7
    + b! d: T+ B  f8
    # k$ W4 m4 }8 ~! j9& }8 a* l* J' O" S! o
    10* W! H8 k0 N' Q7 Q! q' z& B  ~+ c
    11: i) J4 n. u! F/ U2 ^
    12
    8 x# O7 S2 u& N6 K13
    % z. X, h( ?# W& d; p14
    ) X( f0 p$ X; H" H1 p15
    ; K" q; V/ _2 v8 m16. }. ]! y! G% [2 e* j! d* {0 ]' \  J
    175 ~& b* g, Q4 {& q- G: t
    183 m# l; G( y5 v$ @( I8 @7 }
    # 参考答案:5 t, v. y$ i, e/ R$ O
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(  `, X! H( B! `
                ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
    : I; H2 H' S* |. |4 X" n# B+ Y8 ~2 D
    res = df.set_index('Date').loc[target_dt].reset_index() D7 w% @8 }& I. j& c/ u& M
                ).query("Fruit == 'Apple'"); |0 p7 r5 N+ ^
    / l. F) g2 [* c6 V0 {+ ]
    res = res.groupby(res.Date.dt.month)['Sale'].mean(7 r  ?. a& a* r
                ).rename_axis('Month')' e! R# w- Y8 C( F8 P* t0 t

    ( `0 o1 |- V3 b6 z% S" e% H! S3 o8 d. H6 l& ^! {; S9 f
    res.head()
    : L# j+ Y! A4 S& t3 j' {Out[236]: 1 }6 u. ?( U$ Y
    Month" J) Z1 O, K" |$ N+ j8 w- u
    1    65.313725
    # `! e2 ?6 h5 |' W$ d2    54.061538
      ^7 W2 y! \' H3    59.325581
    % g- n1 W8 J2 |0 S. H4    65.795455* m# \+ i! ^2 d% U! E8 I
    5    57.465116* E( E% W. p6 B1 R/ u! ~
    Name: Sale, dtype: float64- \! K! p  ?/ Q2 T: h4 Y
    4 Q9 }3 q) G) H1 |
    1
    & a; \, j7 C. {7 H$ S/ F" ?2+ \% G4 |2 F1 f
    3
      e, O: v7 U- ^: f" u: W! A4
    3 }: i' z5 p  g: P4 N1 i& j5" C& h) k- ]; c' B0 b# S! j0 G$ g
    6
    " k2 g. o/ d$ p/ q7  L7 L9 k1 ~4 J* m
    8
    . ], V( J/ S* S# r$ W9
    7 ]( u& L2 v: i9 L! Q* e7 X" U5 R10
    ' ]: X0 ~9 i; t' t7 r' ~6 A: O11
    8 z% {4 E3 W; D6 ~12  `* g1 ~8 G3 y7 ?. A9 G; v9 A
    13
    - R( K/ e, ]: y6 D1 F7 K4 V14& h( Q) ^5 ^! l5 Y' @0 ]
    15
    . T0 C$ b- Q' H3 U: r" W$ W16  D* \0 ~# R0 Q( [0 X* z
    17
    0 A! ^- i- C* L" _7 g) S1 X18
    0 Y3 e3 A2 @: U0 t" d19
      A" H6 q* B( O$ P6 P8 A2 B20
    % S7 }6 S+ j, H- x& T! q按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
      O+ M! [, K  Lresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
    : w3 A( f4 b8 V9 ~: f                                        dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
    " y+ K/ B: }3 x; ]8 S. [' V                                        ! Q2 t/ p, u& x. d  p: k
    result=result.unstack(1).rename_axis(index={'Date':'Month'},8 V- ^; R' i; H( K* e3 f
                     columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字./ K6 f, h( U, g  j8 Z) L
    result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)' X' k4 {6 _+ G: h* f9 V
    result.head() # 索引名有空再改吧
    : R0 ]9 E: Y$ O+ o9 b7 T) [: e/ ]1 O2 |1 q; `! S; `% J8 _8 p
              Week        0        1        2        3        4        5        62 k' ?/ L8 F6 |0 l" x" p* O! l1 u
    Fruit Month                                                       
    # ]' _5 W( V; E+ f1 ^3 WApple        1        46        50        50        45        32        42        23
    6 O: |6 G( \& J. }2 y/ _4 yBanana        1        27        29        24        42        36        24        357 s/ f* j) {' [: u" X8 w; h: r
    Grape        1        42        75        53        63        36        57        46
    6 u* Q# Y" o" cPeach        1        67        78        73        88        59        49        72
    % q' J& d% d; n/ t- O6 EPear        1        39        69        51        54        48        36        409 C3 Z7 m  b% S. W$ W8 f6 ?
    1
    $ _& ^# |4 ?: f5 S  c2 T# [0 P27 N- k/ ]" n: F* X& L" s; V: t
    37 M; E7 o: ^! e
    4
    ( S0 o' Z$ O) U1 \" o5- ]  M/ ^( w0 [3 B( Q
    68 _) s1 U' g' q+ _- F
    7
    & x2 l8 G2 y+ G2 N* K; L" f; y) D88 K. ^7 Y% x; g: Y1 C; a  c
    94 ?. k' n, f/ F0 E* a- m: R7 I
    10
    4 N; p6 u. L6 y1 O, O116 ^1 t5 ^( z3 E2 T) r
    12
    % U1 L6 L, k4 `. p9 v0 ^) ~13
    ; Z: Z" M1 c0 J' |# v2 H14( u* W0 q2 C; [! ?4 ~! q
    15, I" G5 C3 e% M5 }0 D8 h$ Q
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    & b1 M7 b! k. [# 工作日苹果销量按日期排序
    7 O# ]! q. ^! M! Q! Q0 x1 R' rselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
    % F0 q/ I+ Q: f. j/ b7 F  V* ~select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总& E4 |9 b0 o# X# @! @  ~
    select_bday.head()4 O$ Z5 K4 s4 b# X

    ! s0 V' r# f$ @& ?- R, FDate
    # s" F, O% V/ \3 s6 V, ?" f. Y4 f) O2019-01-01    189
    : K# B0 D% G& a2 p- D- n' J2019-01-02    482* e0 G2 e! g$ B5 E  K3 f
    2019-01-03    890
    9 H+ C7 X: |" X, g! w0 Q+ p2019-01-04    550
    , b6 U( b2 @5 w6 u' U2019-01-07    494
    0 H4 r2 U* J: Y9 y$ H" V; u
    " o% Q5 i! C4 Y) ?- }# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。7 j* w$ k) P. _! {$ h
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()7 P3 V! f' A) T% b1 `4 c
    ' I9 s7 P  I$ S* M3 R* a
    Date
    , N: e& C" u- g2019-01-01    189.000000; i" f6 f6 X- ?5 b. d" u* L
    2019-01-02    335.500000" a1 O0 Z3 x& `7 y! M* D' `
    2019-01-03    520.333333
    ; C9 ^/ p& Q* D- S* M  |2019-01-04    527.750000
    1 R- T) V5 f6 A2019-01-05    527.750000
    4 r2 W: H7 f. O2 M0 s0 ~; b( P2 C7 V$ A: i
    ————————————————& k8 h7 T/ ?% I4 f
    版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    # H  R6 s0 l7 H原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    + d/ Q0 k8 Y5 E$ D9 e
    ! t% j, d& ^* A: v, {* \- @( R7 q# t7 o6 g
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-4-16 11:25 , Processed in 0.575388 second(s), 51 queries .

    回顶部