QQ登录

只需要一步,快速开始

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

    % H* k  }" A: U8 ]+ \* V  Z
    ( D8 j+ [9 e* `" b9 y( z: T9 e1 Z) t" V
    文章目录8 [0 C7 p. X. P: a8 C/ z( e- ]  o: X
    第八章 文本数据' z% k; B# n5 X0 p3 j
    8.1 str对象
    ( d$ y: ~* R, O( J5 t7 C0 F0 G. V8.1.1 str对象的设计意图" j  j# V+ g' e, ]# J
    8.1.3 string类型
    9 p! t5 I1 l( E% w  h; u6 `8.2 正则表达式基础) C7 A6 E2 c) F8 R9 M
    8.2.1 . 一般字符的匹配" k6 P+ C* l+ M+ W/ `+ u4 I, n
    8.2.2 元字符基础, f! K9 X. d% h
    8.2.3 简写字符集
    " R. g' @9 l' n/ k; H8.3 文本处理的五类操作
    ( a% x) {' L5 W& x/ W9 L1 q8.3.1 `str.split `拆分
    ) j: ]1 k- N8 i7 @8.3.2 `str.join` 或 `str.cat `合并- V  {$ g& d" r: v3 X
    8.3.3 匹配
    ( D8 ?% C# F4 l8.3.5 提取
    9 G1 u! V" ?! |& s) D6 b6 F$ ^8.4、常用字符串函数6 B7 U: D7 s6 }3 K$ {4 A
    8.4.1 字母型函数
    - g9 v7 c1 N; C# ^2 j8.4.2 数值型函数
    " I% @: t2 j0 {* O$ s  T+ X8 n8.4.3 统计型函数% }9 y3 o' q  R( X! M. D
    8.4.4 格式型函数8 b5 P8 e7 J9 n% ?- [8 Q& Q3 |
    8.5 练习
    2 {2 [3 {# N- q, _( |' C7 K# UEx1:房屋信息数据集9 L& l! \: _6 A
    Ex2:《权力的游戏》剧本数据集6 ~- A1 C" {6 |. K  \6 ]* |& G
    第九章 分类数据1 q% n" x" ^# A% c  h0 ?4 ^
    9.1 cat对象" o& G0 p0 v: N2 O9 ?6 q
    9.1.1 cat对象的属性
    ) x  ^; R0 q. w* k- v$ ^2 A; q9.1.2 类别的增加、删除和修改
    7 {0 T+ D# k; Q) a: i9.2 有序分类( A( S+ Y$ m/ a% X! t
    9.2.1 序的建立+ w) V/ ^# V! S$ B( r8 X
    9.2.2 排序和比较
    ( M! G3 j% ]4 }; l" Q; g& B9.3 区间类别
    - s6 M; R* k! h7 f, R) Z( x9.3.1 利用cut和qcut进行区间构造+ w5 X; ~. }4 ], a4 W
    9.3.2 一般区间的构造
    ; B/ c2 f9 h3 e; B* D9.3.3 区间的属性与方法6 O4 T7 j$ x; R/ u: L3 D
    9.4 练习
    8 V9 j; `2 ~% u+ ^( ]8 c& H9 qEx1: 统计未出现的类别! N, j2 H0 P( W. p' w! C
    Ex2: 钻石数据集
    0 M) P# o" D4 v  x& c2 ~5 ~第十章 时序数据' f. i* f+ Q5 d
    10.1 时序中的基本对象
    # O+ ^. ^8 H, z& S1 `* _" T% u10.2 时间戳
    & e; D7 Q2 v3 w7 _0 \/ I& j( ~! t10.2.1 Timestamp的构造与属性2 l' U, m# ^( U  ?
    10.2.2 Datetime序列的生成
    - w$ R' Q) C% u5 s- u$ `  l6 F3 ^, {10.2.3 dt对象
    ) t/ }( ]+ {  _# r. b5 {10.2.4 时间戳的切片与索引( Y5 ^+ c, i) C4 r
    10.3 时间差
    ) M& ]! N7 x: d( a6 L9 V10.3.1 Timedelta的生成  s$ Z5 j* c0 m* N7 [
    10.2.2 Timedelta的运算) p& x/ j5 ]4 l  n
    10.4 日期偏置) \' Q* a% J0 |/ ^6 z. v4 x
    10.4.1 Offset对象6 y; A7 b& R9 |& F2 f) {2 `- R
    10.4.2 偏置字符串5 f/ }6 T: i9 p: t# t
    10.5、时序中的滑窗与分组
    3 H8 v6 V3 P1 U& A" [, Z# O1 {$ C10.5.1 滑动窗口( B: m# X) }" \( g
    10.5.2 重采样, U( e# n. c  b3 z/ H! _+ ?
    10.6 练习+ C3 n- S. O! Y- u
    Ex1:太阳辐射数据集
    6 G. z% ?& g8 PEx2:水果销量数据集' G: x& g3 r# O5 y  r: |1 m
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
    + {4 r6 J2 Y& `9 ^* K传送门:. q) u% c7 F# q; m& ]3 B0 j- w
    ! M( V( y% a# Y  {
    datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)1 Z' L* p% |$ H. c+ t
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)( x* e1 z5 J* x3 G0 V; x$ d& @
    第八章 文本数据# z8 {: C( R; o1 A
    8.1 str对象- G- q: v6 s. Q9 Q/ J9 F* e
    8.1.1 str对象的设计意图' n8 ~; u6 ?% R% m" D% ]
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:! Q: @5 q" U  W3 t6 p

    ) T# [9 S; x8 _. `4 ]8 N2 b" Bvar = 'abcd'4 n$ S+ C$ x2 N! e( q; r4 s$ [% j
    str.upper(var) # Python内置str模块
    ' E9 U9 y, r3 h+ nOut[4]: 'ABCD') k" N( y  z2 F* C, c- t( o

    1 R6 X. B, y3 r3 _; S0 q) Qs = pd.Series(['abcd', 'efg', 'hi'])
    " `- ^3 w& f6 M
      O% e3 V. p( g+ L) g% O4 cs.str
    / z  `, w3 l3 W) N2 u- f# o1 uOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>! w  `8 F) F- W9 {0 P

    7 g) f/ {2 z; w6 ys.str.upper() # pandas中str对象上的upper方法
      I7 V. ]2 ~0 s( L0 X3 ?  @2 uOut[7]:
    " y$ M) z  k0 v0    ABCD
    4 q8 o3 h: x( _3 H7 U1     EFG5 e0 d+ }7 V3 w
    2      HI. w  D0 [/ ^# ^2 T: X+ }
    dtype: object
    ! z1 {. E0 \2 F. C" p1 {  C- H15 e; n, e" n1 n, a' f
    2% J( i$ R" N( U) t# x3 k
    3
    1 J; Z: `- D2 O48 C1 S9 ~2 [; Q+ U
    5
    $ F5 q, @7 b# Z3 L6
    7 S# Q0 R" h  \5 F+ @. x6 r7
    " M% r' T/ W6 d8
    $ M3 T  \/ r3 n" `2 e' f  _* n7 P; S93 y6 @2 R3 N6 [( H
    10
    . Z9 t% F1 x6 }11) q/ ~9 Y! ~5 b/ T: I: f7 j) J
    12$ Z/ ^+ Y" \+ j+ |
    13, V7 x9 b& f/ R7 Q- @# a# Z+ }
    14
    7 O. k0 ?. c: b, b3 T! z15
    ; V8 W# J& y8 C) l8.1.2 []索引器
    9 e8 U5 k' `$ w) A, O1 C0 B  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。) H/ k3 C' C( i( l
      pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    9 Y% @8 e! U& m, E  i
    * d0 T1 I5 |, r: ~# W! s/ P% a0 u+ is.str[0]
    . p! E4 y6 o! S9 eOut[10]:
    & |& l/ O/ |) `: Y0    a. @! x$ `0 v4 Q7 x9 K3 [( W/ [# X
    1    e
    & {" c% O2 E0 `' K" Q8 O; ^0 I2    h
    ! w( ?+ B6 y. t$ @! _4 vdtype: object5 n) T* E: W  b! b

    ; ~* C3 z; _" gs.str[-1: 0: -2]
    " `: ]) j  K$ n8 a& qOut[11]:
    & B# j/ H7 i5 L0 _+ P7 ]; u0    db0 G9 y- g0 S" Y; c/ H- n
    1     g6 s. `! N7 ?# _5 p& }
    2     i( j& B6 T) @! V, {* p$ X
    dtype: object
    ; c& b4 {5 b+ m/ D# s+ X( ~
    5 p4 l8 h! {( [s.str[2]
    5 g- I& I; c! V3 l% AOut[12]:
    " z+ X6 z( P0 ^& N% w5 \0      c
    " ?& ~) {: }% P2 e1      g
    * }, L# H' P7 @9 `; G; X2    NaN
    - ~1 K! g7 _$ }7 P0 z: Q* Wdtype: object! \5 L0 A5 _5 {* j- g( y+ z  i

    & D, }) j9 O9 |8 `9 p1) e$ i9 D* k6 j) W7 r9 K+ M
    2: s9 |8 L8 X& Y2 a' I7 P' _8 S$ k
    3
    " @$ Y, d, h  A& O% O& e4
    9 ?; }9 U% y% d+ r( q! @- E7 O5
    % h! {" J; P+ b7 s1 k' @8 D6" E& I) ^5 _; J1 Y  i1 j
    7
    ( q. z+ f6 g' Y2 h( R6 x; C8& m: U+ q6 `* R3 W/ N
    9" F7 Z# M- U$ B, [% E6 G1 ^
    10: \: d1 v, G! L4 k9 E7 `$ Q
    116 H, x) Q! e. |! x1 D
    12
    . Y4 a9 q9 ?, T1 V' E! w- X! ^13
    6 w/ S% q/ J( l" y7 I2 @: ]  f5 p14  {. ^5 k2 Q# M* ~
    15
    8 _9 R% d+ X6 K/ J2 T5 K% T164 ~6 R! I9 B& v* f
    17
    , \6 Q/ U2 |7 R/ f( T) i18+ [8 n. G2 ~* q+ c2 w/ m. v
    19
    ) N6 j5 I6 R9 A( ^1 E- a20
    - ^' b4 h" p# X* N* E+ H3 Limport numpy as np  j/ R: k0 Z" D9 E8 Q) u8 ]* D
    import pandas as pd2 N8 r) e" i  i
    ; F# P/ F5 U, S+ T! L* N: w
    s = pd.Series(['abcd', 'efg', 'hi'])
    # z% S4 y( J) ~# R) Ms.str[0], a; |# _% m, E) j$ N/ N  p+ ]' Q
    19 P" F- p7 C' A3 y7 e8 w! U7 `0 [
    2
    5 ?* s, b& y7 i- }6 X8 c3% u: U6 M0 D8 D0 p2 X2 a0 b
    46 \- b9 N0 ?# e, h: ~9 U( p
    52 Y% F9 {+ d; u
    0    a
    " ~' f3 F3 G, P  p: _. E1    e
    4 @. e1 H+ i  K2    h
    # J* n2 h. r2 c' adtype: object
    ) d& C8 h' C4 |! \) Y  D# Z+ U1/ c- r4 ]/ N/ h8 y. Y5 B# Z1 t# e
    27 |2 L8 \7 i# A! G) ?
    3- F& u+ V  g, w, u, E# ?
    46 P- ~2 n& |1 c8 B
    8.1.3 string类型, P3 J( I; a2 g4 B/ J
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
    % i7 m8 ~( [% w8 }, C9 J! W8 U9 n  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
    , m. k, M6 m! f$ p- F. F* ~. Q# T$ c
    二者对于某些对象的 str 序列化方法不同。$ @, a; z+ v1 c
    可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:0 k# H. W  F/ G. S  P0 z+ o4 O
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    0 c" q5 A3 B! js
    8 B2 ?3 e7 q8 ~6 m  s1
    / j1 F4 M- Y: \" e" B0 }: a2
    6 P( }: U) d8 [" c0    {1: 'temp_1', 2: 'temp_2'}- J' h3 t2 h: Q8 v+ V3 p5 L/ O
    1                        [a, b]' n+ s4 ]( @+ \0 v- A& Q3 `8 W: _
    2                           0.5
    5 d! N# M$ ]% W$ h9 [3                     my_string% |+ [0 C8 `3 O  N$ W: V( v
    dtype: object
    7 `8 [! m2 |* |/ G% Z2 X1
    3 i% @; I' n" N/ a! x2
    ( R9 p6 ~9 e6 _# x' r5 |% }36 P0 w/ y- _4 V% ^
    4
    $ c8 U7 g7 a4 ~; r5) m$ A2 h5 _  x& Z6 z
    s.str[1] # 对每个元素取[1]的操作5 ?+ Y$ E7 h2 [, c3 ]
    1
    & ^. x6 J- G( Q3 j; f! E1 n0    temp_1
    3 y. \2 F. P3 _2 O$ D* _  q; Q, Q1         b
    ' c6 K) `' E+ ~+ K; |2       NaN
    % `. E9 m8 G+ y) E: M3         y
    . P" k: w1 p1 `; E" B, [0 Edtype: object
    1 ?! ]0 f* c2 y# w7 W1- S9 F% N( l0 {  l6 M' x5 A4 c) W" Z
    2
    : b# E7 O, d( u6 I3 r7 e; y$ c8 C3# j& n5 Y2 L: _$ z5 k  H: I6 h
    4
    6 ]$ O7 c+ }$ T, r6 Y53 H+ B/ W) S7 `  Q
    s.astype('string').str[1]/ i! @$ F' W9 @
    1
    4 y2 k! h2 k  ]0    1
    9 M7 Z, c; [; y3 L) t1    '
    / q  S$ h9 X" V7 H0 M; S1 @3 k( U2    .
    ! H9 Q) q+ ]5 @$ ]& V5 D3    y3 C0 @/ U  S# P( V0 q
    dtype: string
    ) n- R7 K5 v) O) f! c# Y1
    8 t: a) C9 h" K1 G% G2 z5 C/ a2( A: x* ]% H" E3 K4 @5 k
    3
    * @5 E5 b4 `& I. A6 \) S4 }4! s5 s/ M3 P$ m& q7 O! A. m$ S3 o
    5
    0 l; H2 D- j5 w/ A除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    ! A1 E8 C3 S: w: i; i% p% I. N) A! M" c8 K- g" l2 V
    当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    2 `+ h) r) M/ K3 Ostring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。1 f2 a# D; l* ~! S
    string 类型是 Nullable 类型,但 object 不是( O+ x5 I+ e" b6 K9 m
      这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。2 D: Q; Z4 C) p$ D: H
      同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。
    3 {9 |6 M4 s% ms = pd.Series(['a'])9 d; X% O* J6 l+ O7 j+ J6 H! S0 l

    / C) ?  z5 ]; J9 m$ K! [s.str.len()  f! B3 s6 L8 H$ Z
    Out[17]: ( B4 g3 G* p: B5 R2 D4 N
    0    1
    # ?1 s# F0 p7 F# Bdtype: int64
    . z+ H; S$ U: H
    $ O, B4 J; O* b' t1 a4 rs.astype('string').str.len()
    4 |! d2 G( a* `2 [Out[18]: ; m" v: P7 b: q; t) t3 p
    0    1
    ) ]6 J) N1 u! l! l5 L8 r; Edtype: Int647 j  n0 g6 R( O. B

    4 k$ j1 d3 H* N9 |' Ys == 'a'
    3 E+ s4 i; Z( f/ ?, wOut[19]:
    1 v0 c" N; Y# P9 v9 W: s' R. u1 |. w0    True
    ' N) ?2 d+ J' H! a7 L5 kdtype: bool6 a: Y( A: c2 O9 j

      u+ Y, y" f+ p5 R# @3 G1 t2 J9 j- [s.astype('string') == 'a'
    ( R$ L# \' N2 HOut[20]: ' C4 Q# I7 `1 a/ k1 Z! H1 S# ^
    0    True
    # U# S9 j$ v# l5 R9 o5 Edtype: boolean4 A+ v8 n0 L4 M

    : z4 m0 L; Y$ X/ _5 Fs = pd.Series(['a', np.nan]) # 带有缺失值+ I1 K2 F1 s- c3 B
    ( K) N8 X+ p3 ]( L# A
    s.str.len()9 N/ m4 f$ M# ^( v! {! I8 y
    Out[22]:
    : b, Q; n7 W. M8 \6 k$ d6 B' W6 b8 N0    1.0* q! K, |* {6 d6 U6 x3 o
    1    NaN( f7 B1 {# K% ^0 q: P. N+ R& i7 @
    dtype: float64" M, X$ N: C# y+ y  m# }7 t9 u8 _% X
    0 d* ~5 h5 E2 I, U! M1 ~% B
    s.astype('string').str.len()
    7 O% T9 M" I* i' WOut[23]:
    7 C/ h+ z( s' z  j0       1
    ; g4 C4 y5 U. ?2 |% b+ j; U4 P1    <NA>2 }4 ?: _0 C, H- |9 q% F8 D) M
    dtype: Int64
    ; S, T/ Q+ l7 `6 Q  N# E" t( b7 G* [, z$ x. U
    s == 'a'
    " a, F5 r' w' G- IOut[24]:
    # G7 h2 }+ ~: O( G0     True6 o$ f7 f5 Y$ w: k8 P
    1    False1 `- V0 ^  D( e) z
    dtype: bool
    # W" r+ z$ H) f  h, ?8 G0 h, J" d  ?5 B! u
    s.astype('string') == 'a'2 Y% A( H  `) W- ^/ {5 I! Q
    Out[25]: # L* f/ N& C& Y" x8 ]+ s% {6 m
    0    True
    & f  g# c  O1 N! A3 z* F" v. e# E, C1    <NA>
    & s% s& r3 R9 q% C- Mdtype: boolean% R  G# n7 j7 ^2 q" C! U

    / ]& b* w0 s( c3 U14 {! l- {3 F' E+ f8 L9 u# o
    23 K1 g1 K7 D! h
    3! ], O4 W8 Y# o
    4) k5 F# F( I, B& \" d% Z+ ]
    5; O/ W+ H1 `+ u
    6) ~% L7 D8 v  Q3 c3 ^5 t, L
    7
    5 i8 S$ r2 i" ^* D- c8
    * q& A# ?7 U4 J0 T1 e, s9# V9 k' c" j& |& ?$ ~" X2 W
    100 W8 M' \8 l6 G/ Z9 m
    11
    $ l  T% R' ?3 F  i126 Q1 n+ [7 ^! \/ K% J9 X
    130 T' A# X, R9 }" T
    14( g( }- m' e8 N
    15  U. E3 M3 M/ _, G9 c" G
    168 s( V  ?% P; E& `* _
    177 |' M4 J( q: z/ _, ?
    18
    + h# |7 M2 B: `6 i( A7 f) y2 E199 }+ s* s$ z; l1 p+ w9 x: U# A7 S6 y9 E
    20
    2 y& t1 B. e1 d$ ]6 F1 A21
    & }- E8 N" b3 U8 N/ F. D. s22, _: D& m& ?6 n9 [* c6 u
    23- l# ]  b" L: X, l, T
    24
    1 V  [, M4 @% F25
    - O5 a# v& @( T0 H1 z26; ~; D7 [# D5 ~* q5 Z+ t
    27, `+ k3 g/ j3 H7 Y0 X
    28
    4 s3 N4 W& C1 u" H* E1 ^29
    % Y" C, F% B0 _$ m/ C; t- ?7 ?300 k9 j* d$ a2 M3 c" V* w
    31
    ! K0 Z) l% T  M4 w- ^2 Y32
    / |- R2 B$ K' W9 ~: j5 A335 a' X4 ^5 y+ C" {! o$ J+ Y
    340 ^5 W' x  h, Y$ R1 q1 S
    35
    2 ~, h, u. j* s$ F3 I( y& L  N361 w. I; P: E+ u: X
    37
    " s1 [+ ?5 g0 }. e$ j0 c5 P38
    * E2 E1 j( W& ^( ^! J8 M39: D; ?7 K: `3 T; D; @1 W! E
    401 y) R" b/ y& B$ ?
    41
    5 n" |9 I) F+ I42) Q4 q! M* ]$ M! l
    43
    1 P: A: n( j0 g* v44( R3 P; z' R) E9 u" ^0 E2 R
    454 H, o; ~/ K# ^* t! t3 c% l
    46; W2 L& Q+ d: X7 W& I! @
    47
    # t% m: J3 a% c. I  对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
    " U. T4 d# Y# W3 M# Q) D5 R/ l, c7 X* O
    s = pd.Series([12, 345, 6789])
    9 g2 @& \9 I  @+ P* O8 M) L! H, j9 I0 H7 @; `. [& z2 J+ v
    s.astype('string').str[1]0 O) W, ?" \( m; [9 E
    Out[27]: " V- s: {# c& @8 M5 K
    0    29 }- `  p, ?  O6 B
    1    4
    , p1 T3 l$ F/ L2    7
      ?9 u, P  h5 L9 Jdtype: string' x8 c1 v( N$ k: ?1 ^+ z4 U
    1. [7 T) \' @6 x$ s, ]5 l
    2
    6 x; a6 b% a& X3 l* C/ x3! m  m( f  ~7 ]# a
    4
    - K# o" d# p- q4 S* ^' _8 z: ^; d3 W5
    , W# |% F+ H! E- o5 P7 u1 [3 d# V6
    + v, v# l- N" U# J/ J77 n! N7 Q2 g  E  a
    8
    0 ~  ]; j; e8 }8.2 正则表达式基础4 `6 j* l- L2 \: F0 n) b
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
    7 b2 @0 L- }& J  R( V6 F, [+ w) t
    ) d  E1 q: \4 i( ?( t  B) T5 x8.2.1 . 一般字符的匹配4 z# q6 b! R7 ^) T
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :7 D# ~2 W/ Q* `% ^: z4 f7 a

    - }0 g) [8 l, h4 Kimport re
    " j# c0 ~5 {: E  B( |$ I
    % y; A$ d6 j# z! dre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配) O, ~, Y6 D; |* L
    Out[29]: ['Apple', 'Apple']
    & h; |- U# J! }/ ^5 l+ R' \" n1. B, F3 ?! }, R  o/ [, F
    2( Q9 w. E3 j* u5 f6 `3 R( ?0 O. V
    3
    ' I! x, p& {' V- r# D3 h9 c4
    ) g9 |7 E: n. C: g+ @8.2.2 元字符基础4 k4 [8 J+ [  N6 k4 }
    元字符        描述
    8 D- u  ~( |! G.        匹配除换行符以外的任意字符) H& u, A1 J& {' v, N# L
    [ ]        字符类,匹配方括号中包含的任意字符
    $ y  L! Z# J! c) t[^ ]        否定字符类,匹配方括号中不包含的任意字符+ y: i0 d9 y, k/ D; R7 t
    *        匹配前面的子表达式零次或多次5 ^/ ~' {: u  a( c5 L9 ~3 q
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
    7 f" p0 y  X2 z! E- A# W! {2 V?        匹配前面的子表达式零次或一次,非贪婪方式/ _3 B$ n- Z' k( t: W
    {n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式- G9 h, m0 V- J9 z6 U( U( u
    (xyz)        字符组,按照确切的顺序匹配字符xyz
    * U5 B9 T2 y3 x1 W, V1 z|        分支结构,匹配符号之前的字符或后面的字符
    ( v! s/ I& L/ o6 @" L6 ?\        转义符,它可以还原元字符原来的含义  y( Y7 {- a8 D
    ^        匹配行的开始
    $ h; y( A3 L/ C/ b$ b% z, n, g$        匹配行的结束
    1 R5 F$ B) ~, [$ R1 u6 W3 k5 ?import re
    2 v" S: B) I. e2 c! ]re.findall(r'.', 'abc')
    4 Y: J. O2 z4 QOut[30]: ['a', 'b', 'c']
      r' Q& y( p* c( I' k7 q% I: c; p4 h6 S# T& m
    re.findall(r'[ac]', 'abc') # []中有的子串都匹配
    ; R( M3 }- a) P8 U$ @4 |Out[31]: ['a', 'c']1 t/ C& ]: L1 N" r/ |/ i/ g. k

    - ^, o9 M- _- V, i( ]6 kre.findall(r'[^ac]', 'abc')
    3 S1 C* {$ k( x$ @' Q8 N+ tOut[32]: ['b']1 t  a3 m3 \$ c' ?' h1 @

    " T8 a, k! w5 Lre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    4 C; B( l/ k3 G* n+ _Out[33]: ['aa', 'aa', 'bb', 'bb']' P" L. j3 O9 R: i; A/ e1 X9 u

    $ V: ]3 `" I3 Kre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
    0 ~$ B8 N  A+ P8 [5 N& g5 z7 n! {Out[34]: ['ca', 'bbc', 'bbc']$ V! E) c1 D( r3 T; A9 d

    % d% a8 F) |& p# J$ ^7 u4 H- |# 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
    & |# ?- r+ _- g/ E, M, u"""
    & T4 d* V7 a, ?" }2 D1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
    ; C3 P0 ?' M7 S2 C0 d( `/ r0 \2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边# A  m( o/ G, w2 n/ i
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,) c1 P4 d% C' f* V' ?5 K
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    7 x0 s2 {3 R, M"""
    0 q; ^+ m, P$ I9 M) f- Z
    3 {: q1 |) T3 L$ k% T  W- f' G# R4 O) nre.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。
    9 ?" g( c; v3 O' X4 c* LOut[35]: ['a', 'a', 'a', 'a']
    2 h9 L! N  n+ Q8 g; O
    # ~& }) \2 ~5 R3 e# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
    - C, P& w: X  O% b1 D5 q- @# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
    $ Z- P, O( J% U4 Z& Z2 a3 ?re.findall(r'\\', 'aa\a*a') . j- t8 H2 o2 s: A
    []* o5 ~& R$ T- B; a
    ' H; w7 n4 n, X2 n3 a8 Y6 r
    re.findall(r'a?.', 'abaacadaae')4 D/ L; y9 E: [# c' K: C1 m
    Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    5 w+ m$ \( @' b& Q; Q" c+ W6 s
    " b/ Z, C8 e5 G( K! Gre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表. h4 j# g2 K3 |! |) b3 R8 N
    [('width', '20'), ('height', '10')]5 r0 }0 G1 o: a6 W6 y( r, Y

    3 k2 Z2 }$ U( k6 u6 T, D1
    , \" s( l/ S1 G3 n; D0 j" o' f29 r. J/ w( [+ J
    3. _7 @2 k4 g  P6 W- d3 \
    4
    6 L7 q! r: v3 m& _# g5
    9 V- I% w; t& m; |8 Z; [* u6$ L0 z; Q: M# i& Y! y7 k
    7
      r% q# ^5 l, l" ]  w- L  }8. m& _* |; X5 N8 m1 c
    9
    8 b4 H2 d/ c, h& q10
    5 O; i% J5 K# }114 H3 P5 D7 }7 z" l$ `" T: T
    12
      y) {( u. o8 c& v13
    + p3 n& l: `$ x) A2 m  t14; I  K1 {! f: H# Z% m% e
    15, S. ]2 g# y+ l! i
    16
    1 N& q" b7 J& s17
      O9 i. i/ n& L$ q, n; {18, f/ ?7 q. e4 T; d% d3 f
    194 H; r) H7 o1 n
    20
    ) E* l# w( k$ q5 M- X" c21
    4 }% a% G/ l$ T# W22
    ) R( i! B9 n3 [+ D23$ ?$ X8 x' U9 p, P
    242 V6 z2 o- a5 b$ Q2 R: @; Y# M# n
    25
    2 s7 u: C0 l  W# j& t. f26- F0 n- e; H7 f, b. e# z2 ~
    27# `. g6 f( r! ]8 R/ k
    28
      e- v+ N* }. Q3 a1 G299 X$ |$ k! w2 @" k1 d; ?
    30
    3 _- m! l3 a2 d8 N7 b31
    5 k* Z4 w& i7 {; c32
    5 h/ n' ^9 ?* J, s' q; M% C9 h3 j% v33
    ) I, w. l+ s& l. A34
    , A% Q8 e# {( Q  C$ Q7 F5 z359 ]1 O3 J  a4 ]* r* ~, R5 Y2 u
    36' u: z. n  l/ u/ O
    37
    7 G: n3 X3 U: I! [8 `8.2.3 简写字符集
    % G: g; n9 r0 F$ a4 A) n- d5 f则表达式中还有一类简写字符集,其等价于一组字符的集合:- t2 Y! l& p: ]1 {3 ]2 b

    : y, q, ]. e' C2 C简写        描述
    3 U# k. B. V" E7 G$ M\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
    3 Z, l3 [; j) v\W        匹配非字母和数字的字符: [^\w]  H' y9 |& K, u% m" R# P- a, a
    \d        匹配数字: [0-9]
    7 D- {9 F; D" |1 O0 P\D        匹配非数字: [^\d]8 ?) x9 ^0 I/ b0 d! W1 \
    \s        匹配空格符: [\t\n\f\r\p{Z}]
    7 p# ~* d0 @5 j: h0 R8 B. K\S        匹配非空格符: [^\s]4 `# j3 }6 V3 w/ J' D
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    1 e3 m5 Y: x& [% r# |re.findall(r'.s', 'Apple! This Is an Apple!')
    + E8 O1 {; M' F, @Out[37]: ['is', 'Is']
    $ n- O, R) z/ j8 i4 V$ i. ~$ v7 m3 P' _% Q  A- g& X
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
    * {. ^. m1 P; E% D3 N" K7 J7 ROut[38]: ['09', '7w', 'c_', '9q']
    : g/ S+ T: e3 Z- B% Z# j1 U+ n
    - j  g) b. l& f$ ]; {' f& Qre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
    ( H& F5 V4 \2 cOut[39]: ['8?', 'p@']
    3 K7 H. ^, h; s- `2 n2 i' P' g$ m1 R: K+ b, d! @3 ?: J. |
    re.findall(r'.\s.', 'Constant dropping wears the stone.')
    5 @: ?$ J; r6 E- y) D9 [Out[40]: ['t d', 'g w', 's t', 'e s']
    * s2 Q3 U" x! |: ?* k
      N, x" ?  ]- s* P8 C" Q* yre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',7 d: l- {/ w" V7 E7 N- n- c
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')  q0 ]2 b/ [* l; d% m+ u* I* e

    / e. O! R, L8 \Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
    # g3 v; t5 C3 w6 S
    $ Y! T9 F8 L2 r# v1
    ( Q5 N0 p# R5 C; M: v1 w2
    ; T9 s9 d! r6 P; ]( D1 J: _, p3$ s+ Q4 r% y7 ?& y& ~: b
    4
    7 n3 G. c: t8 ~1 r! m7 Y+ p  z5( i1 Q& M! G$ U3 N$ B1 z
    6
      ^5 \' B, k  N+ ]1 V7 B+ h7! Z0 F3 M6 `% Q' C( I. \% ^) M$ K
    8' q& f9 g0 |' I
    9# W$ T) D3 s8 m. |9 H
    10
    4 s9 k5 b: [+ ?  B9 [" H113 l' O, t% n* A1 U* y& R7 e
    12, p& F! z9 R+ j+ h* |: @4 {9 R! H
    135 d3 h5 n, I+ W; w  ~* x
    14
    ! p  A" Y: [2 C1 Y7 x155 m4 Y) G# K% z$ n
    16! l% J: Q: s6 `6 S1 ?) n, `, R0 \. m
    8.3 文本处理的五类操作
    ; o" }, e; @( J* V/ H% D" v9 n8.3.1 str.split 拆分$ s* B8 ]; [8 h
      str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。' q# V5 @' Y; x
    5 {. r+ u& k+ `. t) M; T, ]! Y* n
    s = pd.Series(['上海市黄浦区方浜中路249号',5 _0 ?( X$ L2 e8 i9 a' M7 c+ L
                '上海市宝山区密山路5号'])
    : ~, f+ L2 z8 u1 w* x# h& j3 S! h8 t; g( {% ?) D

    4 C/ \8 E) ^; y/ Y- m+ n/ ls.str.split('[市区路]') # 每条结果为一行,相当于Series. k5 R1 L3 v1 n7 F9 V) ]
    Out[43]: * H6 r! g' \% S8 |# t& k
    0    [上海, 黄浦, 方浜中, 249号]. _7 K1 g% U* D2 Y( y
    1       [上海, 宝山, 密山, 5号]
    # O* I2 r1 ]# C( h9 ?# Ydtype: object
    : i7 N3 v2 u) _8 F5 ]/ `2 t- ?4 u+ U2 H! c1 U
    s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
    2 O& J. i2 C5 t+ p/ s. B& jOut[44]: 0 a; r  {# j4 e# S# n
        0   1         2
      j, m4 i, f( J( T$ N0  上海  黄浦  方浜中路249号
    : @3 D* P" S0 f& Y  g9 b3 x1  上海  宝山     密山路5号" B2 \: S$ O# q  j& k
    1# ~) ^# v$ `8 V2 I, ?
    2
    # C9 B/ X/ h- d& S9 Z1 {- r$ ^3
    ; F/ x# P$ y" ^- U" G4
    7 u8 c. c& d9 H. M9 E5- Z, d4 u: W4 g! s0 ~: R
    6
    - ?5 M) P8 i3 l7
      b) Z: e8 k- Y- c+ Q! o+ ]8; V1 }2 ]2 E# E% ]' B
    9- e6 S0 j& v: j: J
    10& i" a' P' ~4 C6 z* E( J% `, o
    11
    3 ?/ c4 v/ k( T3 s8 K12
    - A3 N- o6 d  A6 t  U! b13* ^! k3 ]3 T+ r7 l/ s
    14
    3 G* w# h# Z& d8 @) t6 c. Z' k15- ]0 W5 K1 P" H# O* X+ @6 Q
      类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:* F+ m/ z7 j+ a5 p
    " t6 w: m. w' B# E& G
    s.str.rsplit('[市区路]', n=2, expand=True)
    $ g8 A6 X6 H5 U* cOut[45]:
    : T7 z. c3 j' W2 ?1 ^( _                0' Y8 E$ A( `" x
    0  上海市黄浦区方浜中路249号6 i8 _( x4 T# d
    1     上海市宝山区密山路5号
    / E/ d; n7 {1 h( c1( f0 F( F  ?* s  \( L
    2. t4 v$ ]& {3 x
    3. \1 c! \- ~" O( D6 k7 l8 ^
    4  s) e0 T5 R- [: y- |& u9 A
    53 d3 E3 P2 o2 K* ]+ c( P
    8.3.2 str.join 或 str.cat 合并
    $ m+ `& n, h5 t. E$ c) ?) Pstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    / h6 P. O3 S3 X( X1 nstr.cat 用于合并两个序列,主要参数为:
    ! h) H6 _0 i  W$ Psep:连接符、
    & b: x/ K" X  h  U4 J+ kjoin:连接形式默认为以索引为键的左连接& K5 s' ]* \  j+ w8 u: a
    na_rep:缺失值替代符号
    & T2 o+ i+ L' _# s% Ms = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    - p! i" a" A7 ~9 L: l+ p) cs.str.join('-')
    0 p) H" f- {. N! r3 \. BOut[47]:
    % f# Y8 Y# F2 y" b) a) j: Z9 |0    a-b
    " B" Y0 r( m/ F( A# A1    NaN
    3 ?* U; X* J, g9 H  ~. Q7 z2    NaN' S3 ^* H" O, V. [
    dtype: object
    9 X8 J/ m5 q' S% g1
    8 t9 R+ \( v1 f5 D; ~  y2: I5 f1 Y" T$ `6 p( \8 K( X* I
    3" ?6 x6 r& p/ o+ t9 Z. X- A: m' }7 X. p
    4# c& U% A! V$ T% C% q5 x) p
    5
    0 _7 R* c; X8 s/ `" R& [4 r# o0 a* q6
    ) b1 u8 [% I3 t7 P7- n8 g# C2 W. O0 r" F2 X3 R+ U
    s1 = pd.Series(['a','b'])6 P% `/ C5 t& |, s/ l
    s2 = pd.Series(['cat','dog'])
    0 C: M4 N2 \; P& ~4 @s1.str.cat(s2,sep='-')
    ' i5 ]* g1 n+ B( T8 v- U( `* XOut[50]:
    $ r* d0 G" I  ~. U: J5 ]8 b! l0    a-cat3 t. K+ P, u. w- k7 V
    1    b-dog8 j/ v1 Y7 H/ n3 m
    dtype: object
    0 |8 ~2 A$ h9 P3 n8 e! S% r& k# ~" Y( C
    s2.index = [1, 2]4 e0 m3 _% G  J  i
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')
    $ \3 z% z. V# b: B, n. m) p( FOut[52]:
    , q4 I0 X* a2 E" A7 }' c0      a-?
    ) e& N0 z5 B. @) T+ h! n1    b-cat+ z! }. v& K% c9 a+ Z, \
    2    ?-dog
    6 r! t1 R4 i  S  U, e  edtype: object
    * Q; b( F2 w& ^* y/ A9 H. I10 M6 p4 b" a/ S; w/ N- q1 e
    2, [; }0 I( U; `7 B
    3
    - B4 V' P9 T' y* D4# P6 M4 d" y6 F( g( F$ @
    5
    2 i7 `+ V" _8 @, x68 U7 m. b) a" w% u* _) A8 C
    7
    # q1 S- J1 S- N82 {/ V9 B+ i, m3 P/ V
    9
    ! \  {" w" W- Y6 M10" k2 k+ E" i9 v0 L, h1 }
    11; }; Y; p2 E+ Y
    12
    ) _. x$ I, `: C136 d& R$ i' J8 d; P# B) H! H$ o
    14
    * w: e; y0 V% W1 n15
    8 Y8 I# F% |8 p7 l) x8.3.3 匹配9 x9 l9 o( D4 L5 n0 E; E  M
    str.contains返回了每个字符串是否包含正则模式的布尔序列:$ z3 f  i( w. o* L: Z' y9 F" f
    s = pd.Series(['my cat', 'he is fat', 'railway station'])
    0 J3 z1 ?- j) f- {% N. Zs.str.contains('\s\wat')
    6 r: H! e6 i- y% X- j7 `' |; M/ |8 N& E1 m0 X8 H
    0     True- T. R& y# [; {& b0 N! i2 q
    1     True
    ( w( X( l% f$ c; o, h( U- S2    False
    1 h, j# b# A( ]6 i# ^0 ^0 i- A# Odtype: bool2 \- v# J) r6 X! ~
    14 r. e0 h7 t2 o1 t3 O4 S
    2
    0 X' o% Z7 q: s6 _3
    * L/ B, N' E8 H* ~4
    : B- W. W: ~5 K# ?$ D5
    , g6 [: Q0 Z! p6
    ' U/ N* N' Q/ I8 ]! Y1 d7; E7 r1 ^1 i) ]/ t4 [, {
    str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    4 {, u% T6 z' Ls.str.startswith('my')
    ) N. ]% M' [9 R) C
    ! B" c1 e  [' N) v: N7 W0     True' T* M& Y) l  G3 i
    1    False  m( i$ D* x7 B  Q7 |# |  D
    2    False
    ! e5 H$ ?8 M0 g+ C. Zdtype: bool
    0 ]6 W$ G6 }2 Q2 n. i: f17 J* _$ A$ w4 N
    2
    8 j+ p& E' F, O* R$ r9 Z& S& ]3
    6 @- O- C4 E9 q2 i4( r" J% d. w! H+ H/ R; K
    50 [5 K$ Z+ Z  ^& O; m" }2 J+ r; `6 f
    66 v# R7 L% \: \  y8 |# z! K
    s.str.endswith('t')
    " [$ R5 p% A8 B( V- m1 J% p5 D
    ; V+ l% s' c. U9 F4 E0     True
    / n- o( u& V, r1     True, U- w, ?# \9 @( f: ?$ A; ~
    2    False: F* p' U0 y& A4 R
    dtype: bool
    ( [  c% I7 z! I- V" W1
    * d# C$ E7 M% q+ @2
    7 y; T9 x2 b" J35 R7 O3 u  l  v2 v4 }
    4  M, Q) V& Q2 r7 [, i
    5
      V# o: K2 x/ x/ k6 H4 P, W- z6- t  j4 ]  o2 T! Y9 E
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    % s# \7 x, V6 s- w" V8 P6 R8 \s.str.match('m|h')5 }0 e* u" c" v& i
    s.str.contains('^[m|h]') # 二者等价
    " ~! D0 I2 q. L" L! ?$ m7 w
    3 n4 H2 ]+ N& G" f0     True! S. k* k1 G1 x
    1     True
    * s' R4 {1 V3 s& O" f4 u2    False
    + s2 z2 ^, e1 d& ?$ y7 Udtype: bool
    * F5 t0 A# }9 Y! M2 U" C1
    # ^# i, D9 p! a4 B7 G2
    " {+ K9 V9 a  e' G  G3
    6 z( |+ n( u) T4! U$ ]  _* v) @2 D) u  B. R
    52 l, B/ `& y  G2 H
    6
    & ?; z/ n, O0 h; J& b75 t. _! F" L( S! ~) W
    s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配. B/ b+ I4 c( G. c+ d2 f3 W
    s.str.contains('[f|g]at|n$')       # 二者等价
    ' j3 }6 c! H8 W6 i: g- ~) V2 Y* [
    ) x$ m$ U7 J0 s" p2 z- o4 U, M( u0    False
    5 j! {& t  u5 X& m0 Y1     True& F" _3 _8 f" y2 S$ Q
    2     True& q  \& ~: H/ m- R/ Q& g
    dtype: bool8 \. i  \3 A' N& M: y; s8 O
    13 A4 w1 T5 m8 V0 f( u
    2. L$ q6 U, v3 H+ g
    3, h( P* e5 O* O- T1 [! N0 |! J9 K
    4
    4 e5 ]8 u2 l  \/ h( ?' M. x5
    ) C5 \4 U9 K% N) m$ {, {6
    8 H1 B* t7 x* l; `) N/ t7
    ) J+ }; b. \$ h/ Pstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
    ; C6 m9 `; i  d; s2 j8 e) j9 M/ es = pd.Series(['This is an apple. That is not an apple.'])
    5 Q0 P: S4 ]: M9 P, q% {' M2 @0 t
    & h+ c  |4 `" z9 m) n! R; x* ls.str.find('apple')- B, ?) ^3 i& ]" N# n
    Out[62]: % e4 L2 f" }! w7 x9 d
    0    117 ^$ u- h. o/ n0 S; _+ D8 \
    dtype: int648 E2 L* S8 u/ T9 h

    " J- r  u- Z2 C+ O# g8 O2 os.str.rfind('apple')( v9 Y- N2 N" [8 }8 O
    Out[63]: 8 D; X- ]4 _0 S0 u
    0    33
    - H: E2 r$ J8 \; S5 C/ ddtype: int64
    " o+ H. Q4 D0 `0 S. b* d) z15 w9 n, T( w. W/ y
    2. y7 @& p) l$ k; i7 t: ^  E5 {% j2 @1 {
    3
    4 p% [/ C0 M1 a42 y% g- K2 ^2 ~
    52 }$ E# J0 J' E+ C4 m7 [
    6
    " A  i* y" l4 m3 X7
    $ |' {1 D% f. L7 i8 A+ Z8
    5 n$ x4 O. A/ `' X, G) T9& [( H1 x% ~. R+ r0 ^9 @9 u! O3 u
    10
    & O3 H3 O5 n+ l0 H7 I$ F' b11' [* [$ G9 I+ U  a2 A9 \/ G% h! g
    替换% Y5 D$ \  {9 n& ]0 P
    str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    . T6 s% `$ v4 j. p: O. Cs = pd.Series(['a_1_b','c_?'])2 v! |) T! h  S  a2 N2 U4 X
    # regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
    # F5 {: L) p1 ]9 S0 {: Gs.str.replace('\d|\?', 'new', regex=True) % Z6 w+ @$ ], S/ n* \0 l( k
    3 k2 Z# V8 |7 d
    0    a_new_b  P2 Z, \& H) T' m6 j6 Z
    1      c_new/ o5 j4 ^9 L: N$ D: I
    dtype: object
    3 B. g& Y3 B4 \4 V( j1) P. h: G/ I) h! S5 E1 t( T7 k
    2/ W2 b, q, H% k) s; U6 c2 L, h
    3- I8 P$ `8 m  I' X
    45 l* M$ \2 B; w+ D% c
    5
    3 R% X8 s7 z  j! m9 @6
    ! G4 [; Q, e8 l- c9 F& W1 J0 h7
    9 g3 {/ l; E+ Q4 t/ j1 t: t4 ^6 X  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):* w$ s! j' x- o4 V* H. U* T
    : G. [2 q( |" S* n1 C6 K
    s = pd.Series(['上海市黄浦区方浜中路249号',$ \. Z2 n5 e3 w, t* b' u
                    '上海市宝山区密山路5号',
    6 }0 b: q- g3 z$ g, L6 a3 K$ t                '北京市昌平区北农路2号'])
    + P9 d, {, q% c/ vpat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    / c, D0 l( c4 d: @& Y# ocity = {'上海市': 'Shanghai', '北京市': 'Beijing'}+ m  E& r- T/ }! H6 n) _" i
    district = {'昌平区': 'CP District',
    # h) i) w# R0 Y& m6 Y: P: J            '黄浦区': 'HP District',& }; }6 g1 P" b4 E" a) w
                '宝山区': 'BS District'}
    1 m8 X1 ]% Z# ~road = {'方浜中路': 'Mid Fangbin Road',; Z! T% Q- ~( a
            '密山路': 'Mishan Road',
      V8 H" `# x' n        '北农路': 'Beinong Road'}* T% o2 o8 r2 I
    def my_func(m):
    1 v- B3 L) ]. z1 u9 I# D    str_city = city[m.group(1)]
    ) H3 x2 _: x8 f5 `' ^! N- V0 ]  O    str_district = district[m.group(2)]
    1 m5 s8 H, I, B4 u* a& b: _    str_road = road[m.group(3)]' {+ }, O) a+ O( D
        str_no = 'No. ' + m.group(4)[:-1]
    ; v$ x2 V. R' ^$ E: z1 x    return ' '.join([str_city,, E  V' G( J/ P3 O
                         str_district,! J% k, L0 N- c" H- X. W
                         str_road,  J7 W" \! @" E0 L# Y& A3 Z
                         str_no])' f: j% ^5 k: E" q0 a
    s.str.replace(pat, my_func, regex=True)) R8 ]9 h) O+ E7 x8 j3 N8 c

    ' t4 X9 |1 q. u" p& W6 D1
    % E6 N' t& e2 j2
    $ q/ D0 R% Q+ s7 d1 e; m- E30 @& L* {  e4 R0 k' i
    4' G' {- J$ i# X5 R
    5; b2 f9 l$ b# G5 A( O% h
    6% P% H8 ^3 l. |" r$ r: I$ P
    71 r! m8 m( q2 C0 q' Q$ ?
    8& R- r0 U! W/ ^, g$ y
    9
    * O7 }5 v* E6 s' I. Y! k: Q10
    + J0 l8 U: e9 p' X$ E! D11
    $ d4 k9 }4 a/ T$ c& a9 n12) a& Y8 a: x5 B8 i. P
    13
    * Q7 q7 A  ?; a4 W9 t9 h& z2 x3 p14
    1 [) ~: e0 B2 z  w& W8 W4 c) R$ z15
    8 f) L* R0 m8 t# o4 }/ y* w) |16
    ( `7 `8 n: m3 A# ?* ~' }17; ]3 \  r) U. v' Z' W
    18& C$ N7 ~4 O4 K* O: V
    19
    9 t- D$ k) k+ |20
    4 X( N/ t$ N8 v) k2 l/ A21
    2 ^& `  ]5 H& g; E' t0    Shanghai HP District Mid Fangbin Road No. 249; l) o2 y, R: r4 u
    1           Shanghai BS District Mishan Road No. 5
    1 R/ W4 e9 ~2 _) ]2           Beijing CP District Beinong Road No. 21 b3 f6 o; G1 b# S: b( D8 K* ~
    dtype: object
    4 a1 Z* ?9 S! O) H1+ ~7 O9 g2 Q5 e5 q" Y& x' C8 E
    2
    ) w) S+ l! ]( `3
    . c0 f6 T- N9 ]8 M: X5 Q; q1 T4* P- N; w6 ]' k# ~
    这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:$ {+ r8 K+ v9 |1 x

    2 p6 V0 S4 \+ T- M  u7 J0 L# 将各个子组进行命名% S% C. ^* y5 \% I
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    * F0 |2 m8 l$ p! v: {6 w" Z5 j" @def my_func(m):2 j/ I7 C/ }0 y  M3 R. I
        str_city = city[m.group('市名')]+ v) |6 K5 ~# @/ ~( a
        str_district = district[m.group('区名')]
    6 D$ N# }( _4 H4 g' M7 l9 k# p8 W    str_road = road[m.group('路名')]7 T+ \5 E( g& }$ W
        str_no = 'No. ' + m.group('编号')[:-1]1 k5 f2 R: o% }0 R/ T
        return ' '.join([str_city,
    $ c( u$ ^% P0 W" E                     str_district,. x0 b+ H" o( _- L& l4 J3 M
                         str_road,' q, g+ A# ?' Q1 U/ J; F: s
                         str_no])  i; F; ?* {4 C6 H, K
    s.str.replace(pat, my_func, regex=True)
    ) [7 y' p& q, d, L! y% L& g1: z# t& b2 i  L$ H, R" Q% k
    2
    1 `8 ?: n( t- P( y% l3- T6 U# D0 u1 i5 t$ E
    4
    # i  y7 q% P$ o. }( z50 g# M% F2 j8 A: E
    6
    ' Z& r6 N3 A) z' l7
    $ y/ Q4 o* {, E2 o, B# V/ ]8
    - r# a/ E3 i  r4 _9
    7 I% T2 _& ~( D7 d: g6 f8 L, F4 f109 \- d! A1 M7 w2 R8 p
    11
    : D5 L) M" |6 v8 i: q  ~9 g3 S12
    ! E. ]. I* W1 w. E0    Shanghai HP District Mid Fangbin Road No. 249
    0 t( s: \+ |# }+ m9 Y5 Z4 J  t% [9 [1           Shanghai BS District Mishan Road No. 54 z5 Y0 e+ @' s
    2           Beijing CP District Beinong Road No. 27 M8 c; w+ }- \$ j4 i, z7 d( x
    dtype: object) W$ e& {4 n9 g* y/ Q
    16 O2 h/ `6 i+ r; g9 L8 r
    2
    : _* x" S3 Z0 `) d2 c/ \. U3
    5 o0 E1 W% F1 w/ i3 k! b- V4 ?4
    # Q& c# {" u; T5 `8 c* H. R  这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。" Z4 a. Z. z! x# I

    + `! v; C- Z1 c+ ~8 E/ ^  R8.3.5 提取- v9 z4 `$ v' E
    str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:. X" d, c  A7 z. n/ J! k" G% H
    s.str.split('[市区路]')  V/ q* \# v5 a* E' ^  b7 y
    Out[43]: - N0 [2 o1 n2 p& p4 k
    0    [上海, 黄浦, 方浜中, 249号]
    5 K( m4 J4 M* a/ t1       [上海, 宝山, 密山, 5号]; d$ J7 Y6 _3 }# c
    dtype: object6 S$ `4 W! g+ Q+ c% V) p' D* b

    . v% b7 K( x. ~: N/ _5 ipat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    # o2 @) ^- Q' v/ Ds.str.extract(pat)
    - N  r* n4 ]8 x/ }5 K8 L0 ROut[78]:
    - e% }, H. g2 A" o  I# b    0    1     2     38 E3 V8 J% l: u& T4 I
    0  上海市  黄浦区  方浜中路  249号: Z! k9 k# c3 v
    1  上海市  宝山区   密山路    5号
    * r1 H& q* W2 a2  北京市  昌平区   北农路    2号1 K6 {9 v+ ]! ?7 X" N  [3 C" z# j
    1; Z6 M- v2 ?4 t- [( d
    2
    9 c# [. L6 C/ l( O' Z  ]. a1 ?3
    1 q  O( O' f4 d4
    1 [9 C' [4 L2 W" @5
    7 h! J: J) J" Q7 @, E; u9 ~6 i6
    : S/ j! `( I# \7
    * |4 H6 K7 ^, h! P$ S8
    * E' n+ H9 x2 c9* F4 K) j: U6 q+ A
    10
      |% E( d; d+ Z- i, J11$ G/ c) K! J5 Q- v, n& ]; `
    12, u  Z6 B  I; t0 U9 }
    13
    + _; E) e5 L1 r2 D3 g通过子组的命名,可以直接对新生成DataFrame的列命名:
    . h) A5 k) I; U/ W5 X3 t+ P0 @# d5 G" m
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    : j% b; _2 q( [! m1 }s.str.extract(pat)
    * g: \; X; I+ T5 B1 h/ dOut[79]: ( W  W8 m4 X1 P  j' j( h( d; E
        市名   区名    路名    编号3 K* c$ E- l  h7 s
    0  上海市  黄浦区  方浜中路  249号
      `/ |9 M! N! B6 x1  上海市  宝山区   密山路    5号
    + `' d; Q  w+ M2  北京市  昌平区   北农路    2号
    2 R% V/ I. l& U2 ~1 f6 r1' f* Y: [1 \+ J
    2: d8 A; v% h# u- t4 p
    3
    7 S3 C, X) h6 J/ b: w4
    0 Q- h( K8 R; T) r; ]6 O8 x( J5& ?$ l1 x, d: W" x  P
    6
    8 }) W- o& @! E7
    . f6 d4 |6 e% l; Dstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:! `5 E4 ]& I* h% ]7 W1 ?
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])* d4 Q: E5 }( F5 C7 t# ]
    pat = '[A|B](\d+)[T|S](\d+)'
    ! H+ i  ^% H+ t, P& z5 i0 C% us.str.extractall(pat)
    - |1 j, v, d: M  c! o' t0 Y$ YOut[83]:$ Y) [/ Z; |$ w1 p2 o2 n: t
           0   1; Z6 D/ z5 h! s
         match         
    9 b+ A( ~5 L8 u  e; hmy_A 0      135  159 F+ l+ S+ ]2 [+ M8 H9 T  e
         1       26   5
    " f) q  ^+ r, U2 ~2 R! Fmy_B 0      674   2# d! o4 n7 z, F* O! b
         1       25   6
      d7 {% l6 y% o# }5 K1
    ) @5 s- [9 w& p' i1 {9 \25 o% q# w: Y. J/ s8 O9 ~
    3; I7 S* }7 M" C2 H& h2 d7 b' W$ H! P
    4
      a  k9 |; E" B: d/ ~7 F; Z! x5; O7 q6 X7 \$ _- o  L4 o6 ~! @
    6* ]- h4 t: Y! {' X4 g8 f# H
    75 K  t2 G1 w8 _4 F
    8
    , n" k# z2 [5 A8 Q9, z2 \- W5 n- c- o- e! g
    10
    8 D8 P; G% M+ spat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
      N0 @6 Z) w; {' Zs.str.extractall(pat_with_name)
    6 c* z  V5 Y/ ?% r* V% ~! {; c% ]Out[84]: & I. |9 J5 z( p
               name1 name2
    ! u$ I- o: j: @4 V6 ]5 G4 O9 `9 O     match            
    ( n: J; Y5 F+ s0 f0 \' omy_A 0       135    15! ^( B6 h0 I7 n
         1        26     5) F" S+ ]0 o. t
    my_B 0       674     2( U: J' [  ~  C( X6 S6 ^: v: f
         1        25     60 v7 w; ~; e8 P6 l, `' O+ l
    19 Y7 v% w4 z+ W% d/ Z% O
    2
    1 E8 b/ l, H, J" |8 F3
    4 Z5 e, a5 n8 e: A, {2 m4
    5 N, V/ i2 Z9 b. o8 l/ w5+ w4 q7 h1 M2 B% y0 ?& H
    6
    & l# s9 \$ e7 u) Q7  G" H: ^+ p" g" F: ^7 k  F
    8' F6 n$ L* j2 N. z
    9
    ' a2 M+ L( N3 b9 o! J" dstr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
    . V$ C$ o( S2 g/ u' ks.str.findall(pat)
    , _3 I# v, E9 v  P* P$ w1
    0 b3 ]" W9 ^( D  Dmy_A    [(135, 15), (26, 5)]
    . U5 R2 _' }* i, N7 w- n! w' R  `) |my_B     [(674, 2), (25, 6)]* z8 D2 N/ e: @) J0 V0 H
    dtype: object
    # m6 @2 [% v2 j# v1 X) F, V1 X0 D1
    8 O, n( {; _, U2 l; t2
    ! G5 J$ N3 I+ m( T9 p3 `3
    " D9 t# K9 k! R9 r7 k% n$ i8.4、常用字符串函数
    . b6 O" s- U3 C3 j  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。- F4 c  d+ l1 C( I

    8 C- b$ X$ H3 [" |! [+ [, D6 U6 C' I8.4.1 字母型函数
    , E5 s( g- y1 Y  z  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:! F0 g& Z/ Q3 G+ G0 L6 G7 ^" K
    / }8 f0 [  N! C' U
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe']). _+ E9 F3 r# Y, P6 T  k
    3 o3 h& N& C. Q  L, j. c
    s.str.upper()
    ( t' E4 j2 X8 WOut[87]: ) L1 q% T7 N8 |  C+ L5 l* b; `3 ~: v
    0                 LOWER' q/ q9 x  R( B2 R* P6 Z
    1              CAPITALS
    : s" y7 E0 A+ G: Z8 ~7 y; C2    THIS IS A SENTENCE
      O) T) R7 s5 o) @* T- Q* H5 e3              SWAPCASE
    $ v7 }+ V4 ?) r2 odtype: object
      T  ~" F* D4 Y% R1 C7 P6 x% s
    3 h6 q4 h8 j5 H3 J6 u# Rs.str.lower()! C' G: P* D5 e2 o# Q
    Out[88]: & s% v' W& Y$ y
    0                 lower
    5 j# W# {3 Y1 R7 U1              capitals4 _. |1 U- ]& z
    2    this is a sentence- Y  s6 C  f2 C, L3 i5 i
    3              swapcase- E% d8 X- q6 Q1 t" g5 O2 S
    dtype: object
    9 X- J- U2 D' F$ M; n6 Q9 t, V1 R0 q! I
    : a: q: N# X5 @* q% u, \s.str.title()  # 首字母大写
    , @) ?! T8 Q8 M- `( w. r/ f. sOut[89]:
    2 P( [' g9 A% }; n$ u0                 Lower
    $ R$ ^3 @) b- t7 G1              Capitals5 A5 Q+ m0 F5 h, ~2 }5 I7 ^) v
    2    This Is A Sentence
    ( x' d8 Y( c" E* s1 e4 P3              Swapcase" l* P" X7 l5 b2 }
    dtype: object
    3 T) o; M" R" N1 J% H1 ~: {" \- X& X' Z
    s.str.capitalize()  # 句首大写
    8 t7 F& P$ O* q- Q" tOut[90]:
    0 q6 v* `- S4 [1 {0                 Lower! a! w+ c: j( \
    1              Capitals
    0 f4 Y: Y8 N6 r" x: z2    This is a sentence
      I0 Q6 C& D7 z3              Swapcase$ f$ `) V' ]9 O, P+ u2 A7 U
    dtype: object
    % M, N4 H6 s* R* W2 Q
    " M/ b+ _7 }% d& f) {- q& D( c. qs.str.swapcase() # 将大写转换为小写,将小写转换为大写。
    / D6 {- x1 j; {% lOut[91]:
    3 N* L  T* h/ `) `3 |/ g0                 LOWER
    & ?# o" k7 n" z1              capitals
    8 d, R2 ^3 v: l2 h9 x' ?: V, W; t2    THIS IS A SENTENCE, s+ w4 q1 E  M" D
    3              sWaPcAsE
    ! A( T; I1 H, b3 s9 U4 O2 Ndtype: object
    & ]$ E( k% w$ i+ Y; M. ]! W) u! Z+ h+ T
    s.str.casefold()  # 去除字符串中所有大小写区别
    " ^3 ?' X0 R/ X5 A5 J% l
    2 V: N! b& T. J2 n0                 lower
    ) ~9 q0 P! ^7 L0 l( }1              capitals$ y% j0 H6 Z. X  U4 W1 x$ a% z
    2    this is a sentence3 Y+ L) }# E- w: {9 A2 j. v
    3              swapcase
    . b  I6 T1 N% f" O9 \3 x
    7 D& L1 Y0 C0 ?+ P# g1% L, W) }5 o( m  e; a
    2
    2 W/ e# a# f. h) P) n3% j# |4 I' j& P( E
    48 ?; p9 p8 B" p* q
    50 P+ y7 x6 ]' t$ U6 \" x
    6
      Y/ X- Y" d1 m- Y/ o( K9 E7
    0 z* r1 u) L! \5 D. l8
    & {/ K0 y. a" I9 h! Y98 K; S+ y( d5 h' F0 Y
    100 R! Q2 M; z$ W0 l2 D
    11
    ' Y( ]8 O/ _4 N. b" K+ y2 b12( L- Z* E, k' u& C% j6 F& O/ o$ _
    139 B  o/ J5 z: @  }! A! Z0 D
    145 I- {3 H- v* [
    15
      ~. l$ ~9 U0 ~5 |, p16- e* l! i( ]1 x) x
    17
    9 O  S' s0 P+ L8 @( I18
    3 e( P5 y, t4 I, b  z% x- l5 }19
    ) t  F0 I- e3 P2 [( L( }& o20
    , H- _* G' w7 O9 _/ C21
    + z. O1 |/ S- q0 J22
    : I  o0 ?. J6 m3 i23
    ' H! [4 R% N" p! s  _5 W242 k3 s, C$ {# r. g3 b, r8 s
    25
    7 P3 T: }6 O/ r9 a263 {0 L6 T! c0 a$ M
    27
    , W0 p' w6 ?# n: i- x3 s28
    : x' y# A# x" t. R0 `' ^$ S. X299 O  \6 m/ u) j9 l# J$ Z* b5 b
    30& e' U/ I4 B! ]! `& i
    31
    ! y# y2 s8 J6 u- n9 H4 d! X32% x  \0 y+ s1 A' \6 \0 F
    33
    5 Z& n0 b+ z8 Z. ^6 S348 Q  G; ~: x5 c3 K
    35# ]$ F0 P8 v: J" D
    36( J! w4 W& ^1 n- l+ k1 }0 P
    37
    . ?# _1 w! [$ O5 O$ `5 w# d38  n$ T, m* m2 f) K& s
    39
    : ]. U% B9 O. ]/ T( \$ V+ [7 W409 S: H/ V% v! Y% _# F9 ^/ y
    41* Y- @, U- \" T( X0 Y2 S% }
    42
    ! M3 h: m7 X7 o7 e1 q43: W6 E2 V3 h) ^1 P  Z+ e
    44
    + ?  K! m8 ^$ ?! g45
    , {/ B2 S% c- O+ d' x% F. y) j5 ]46
    - _8 W$ l& H; o4 Q47
    ! n- X. @. T+ {; D48
    8 M- p& Y- C) I0 _8.4.2 数值型函数
    " K! c  o- |7 ^) z4 U. W4 ~  这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
    + D2 s. H- G% _$ m8 v
    * O0 y& Q, x& |9 l  {" \+ Verrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
    / Z' V9 f, ~/ R, P/ a: Eraise:直接报错,默认选项( k' q9 g7 G  R! [* x
    coerce:设为缺失值
    7 e1 S4 Z/ u5 U8 f$ @* }7 |ignore:保持原来的字符串。
    2 W* J( K( x9 j- b6 G4 z; {downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。2 w: ~% W( l& ~" p1 P$ j: v
    s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0']). {+ }) U7 A( k* z0 p0 Z

    9 V# K5 n1 I  w0 z( l3 Dpd.to_numeric(s, errors='ignore')7 {* t+ T# M& S, ?. z/ s. ^  B: j
    Out[93]:
    6 v2 j" u+ M& C' g0       1
    ; u' V1 h' g2 G1 \' u1     2.2
    - a  i1 W% E; W" w2      2e
    # Z+ t* L# a6 h" g& Z3      ??
    . x, h' I4 p1 T: m4 O. M- O4    -2.1
    & u$ L# ?" j3 J; n0 u5       00 P% U- z, ^; _! Y+ U
    dtype: object, e4 u" g" h5 S* F" ?

    ( t( c) g2 `1 P/ g! q  qpd.to_numeric(s, errors='coerce')
    $ c) N5 ^3 l3 _5 E! V  WOut[94]: / @+ N0 O; @2 }
    0    1.04 S- `$ ~* r' i. W8 L4 U6 b
    1    2.2& E% u+ Z5 G; b: }6 }' L+ H
    2    NaN
    3 }, [4 I& ?( W  y/ c3 Z3    NaN
    ' m/ N1 }& d! }4   -2.1
    6 H, H6 f- Q$ ?0 u" |0 d5    0.0  I1 ^' J7 `) {2 r! x# U. X7 A
    dtype: float64+ h' G0 y+ m7 y' [! S! }8 F

    ' W, H" ~7 n& T5 c: x: }! }1/ P8 _; \: a* b' p  I$ E
    2
    * R2 _$ v3 J$ O3/ t8 R' `; ^- q2 [) E) k6 {
    4
    2 j9 b% c9 Z& l& w7 J( N5
    2 ^1 y" C4 k+ `: C# y6
    9 H- }3 f: I( p9 b1 u, {9 a: Z7
    + F5 R$ Z9 d+ I% J' G0 Q8 O8' Q4 [* H& y& s$ c/ C1 s* ]  N  G
    9
    / A# U  y2 ~7 B$ {107 [2 X6 S; O6 c; f6 C! u0 t2 s( {
    11
    & a+ r/ d7 s# O- M" x5 |12& F: M1 I2 b6 m4 ?0 q
    13
    " u& D3 n! }# @$ q' A6 Q2 f8 ~14$ `3 G* S+ E3 Y' U, p( p
    155 ~* N+ a! e% ^- h7 e
    16
    ( G6 M; f1 O/ M+ s! s) l% d9 h; e173 S! e; f9 v8 K  Q
    18
    0 c% W8 c# P& _) u+ ~$ ~: h19
    , O1 F  c+ w2 h9 U" F2 U) B209 h+ x0 O9 O; P
    217 T4 X4 X' j. L' Z6 h9 M# D8 z$ K
      在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:* x% q7 |. y/ z$ b$ O# ~

    ; g! o1 Z- x$ E, |/ E! Q$ rs[pd.to_numeric(s, errors='coerce').isna()]
    7 x2 S! [4 E6 f  Z0 l% XOut[95]: % t0 W) w4 P" l. ?. W5 \/ x
    2    2e& G( f8 s; {% Y! Q0 T
    3    ??
    ! `$ r2 a% B/ u4 q1 k( |! odtype: object. z9 ^/ o9 X0 p* O- w
    1$ [; l! n0 b& b
    2
    8 Q. B* D2 J5 [7 h- |3  C; U  G+ I5 L% d) c
    4- c' A: x8 f2 b4 h
    5
    " p) V6 p$ @) ?! a' M& t8.4.3 统计型函数! m: R+ s7 P- Q
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:
    2 n. o) |; n( O8 k( j+ U4 y0 t4 u) ?1 E
    s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
    + N4 S' n$ h3 P0 L* C9 Y  I" Z
    / M1 j; R/ d3 X. K* @  W0 e6 ms.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次6 `: ?/ P/ }8 W
    Out[97]:
    0 K) w5 R$ i2 K$ [$ [, U0    2+ ^& E9 t& U, H/ Z
    1    2# L: `% [) J) C7 h
    dtype: int649 c3 V# u/ ]: P( l# @
    4 S8 E. J$ O% m" b4 j
    s.str.len()& t! X, C. F" `# w- l  b8 X
    Out[98]: 6 p6 C$ l! i4 o/ ~
    0    14* j' Q. G! u1 H- C1 s; a
    1    19, [$ Y; b, N6 V1 B  Q& q3 X3 i+ x# o
    dtype: int64( p0 Z3 U* x  Q5 n5 t
    1
    ) f- j- A' [7 o2$ X+ o' o+ T% y9 s+ [. a* ~
    3
    3 `& Q, O/ ?3 y4 }: g6 x/ T6 _4; `2 R2 P! @( F/ O9 a
    58 y5 H$ p( m7 l. j
    6
      |" V6 T1 V+ @- ?. ^, E+ B; x  D8 C7  h3 H( K0 w( p2 q" h. Y  o0 I* ~
    8
    ; j8 }9 o0 m" z3 _, d( Z9- c- z1 S5 f4 i, S8 @" j& ]
    108 w% d3 r# @' F" V4 G
    111 k1 Z, A& D, [0 n) ~
    12
    0 ?1 F9 D% n& B* A& h8 m) d132 z, f3 i  b" B9 D# i& D/ j( {
    8.4.4 格式型函数- p+ {1 ^: b% ^( U
      格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。" A( X0 G& n5 ?# u6 j- X: R; B$ B

    ) l9 a% s0 z3 E+ P: V2 Dmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
    0 `( b+ C% e  m# s/ E' H, C  P5 Z/ Z; ]9 h9 l5 t/ T
    my_index.str.strip().str.len()
    0 f# v9 j; @0 _) `) |& }+ BOut[100]: Int64Index([4, 4, 4], dtype='int64')4 B' U' o( @% u
    $ m1 c* \. n: {
    my_index.str.rstrip().str.len(), }: X) ~; q7 Z4 k: Y
    Out[101]: Int64Index([5, 4, 5], dtype='int64')) \1 B$ @3 V0 }, v  f
    + ~6 R! y! a4 y) [# s& M0 b7 C
    my_index.str.lstrip().str.len(): z. c- |: O; ^# N
    Out[102]: Int64Index([4, 5, 5], dtype='int64')
    & s& S1 c5 A7 V5 M. n1 g2 A1
    + O0 x* a" D: e7 i5 E% Z2: {  Z; K( h2 e( I2 R
    3
      r- g+ }1 {7 j" v- s3 Q8 I8 A/ @4
    ; g; N1 W. u$ ^3 w& ?5
      i$ v+ x, q8 Q' [, }; o, \6* Q% t% r+ x( O$ ~/ J- G+ s
    7( m+ B# I* m5 v  u% S! @2 M
    8
    * h+ \  e$ r- i2 R7 m7 P97 H* D% H( c! B+ y8 ]! J% ^
    10, k/ T" a6 G# J  M  m& |* k
      对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:6 {: |, b5 A4 k7 O% _2 _1 w9 U

    3 w/ v- a& |3 Z- ^& w* B$ u' qs = pd.Series(['a','b','c'])$ N7 t  e0 u5 N$ X" L* M) X. G" b6 q

    $ W3 r9 g  D3 l7 m5 K% fs.str.pad(5,'left','*')
    7 l5 g) R6 u. {  o. }Out[104]:   j) P3 ?; L  m2 l3 ~! \. v
    0    ****a
    # K  u3 X0 M1 j, f; p1    ****b
    ' C& J  J, u: [/ e2    ****c9 g5 X' Z( b/ I$ {* O
    dtype: object9 ?* i( u2 ~# Y7 G' C

    . ^9 u, E8 d0 C# `s.str.pad(5,'right','*')
    ! p# k6 D, w" f  {+ @* o9 oOut[105]: 2 A; W( G4 o) H& w( B
    0    a****3 f5 i/ A7 j  W& [) t
    1    b****  k  u- X0 g/ c
    2    c****
    8 D' t2 T0 f; H3 {( z8 K% ydtype: object
    $ Y/ S% ^7 J  M: G5 }: B- b! J! R
    / W7 H- o6 m2 E9 J/ ?0 j# As.str.pad(5,'both','*')0 |3 Q# P: A4 D( j0 B6 X& p
    Out[106]:
    % q# E( d7 H$ I3 z- m0 X9 n; i* x0    **a**
    2 l! S9 p0 y' Z: J* O" }( V! z4 f1    **b**
    , }1 x/ T& j6 @+ H2    **c**" Z2 V$ ^* b1 p) Z( @  M4 c# W
    dtype: object, ~. Z1 q" P; }( N0 h, A

    5 m" H) r5 `4 ?* e1 y0 s5 v* j1
    * R" s, q$ _/ g4 f4 ?7 ^  s1 F* H2
    ; X3 F2 G' s/ j$ `: W' n( U4 s6 s  o3
    0 {5 L. [5 v- z( M& V) v6 N7 \* ~42 j" u' ?( }8 C" `  C# `' G
    5
    * t  i* a, J+ o6& Y6 ~) |; q$ T2 d
    7
    ' r- L8 P2 w+ J# q* d  v8$ ]$ [/ u$ b& h6 j  r4 h0 u
    9
    # T- J' a4 [" y5 ^& O2 {3 T10
    ; P0 E! D, v3 _# r/ k6 H11
    # R2 @: X/ ?# Q- l% F) \128 g! O, e/ n/ j! R7 F6 w
    136 h8 C- c5 _" t8 d& k
    14) R% ?1 S+ Y! z
    158 E* J$ m. u0 v0 c
    16, X% [& X( X* f6 }: r" p: t- {: `
    17
    6 D, U3 Z* J. ]; C  H! n; r18
    6 E, J5 {3 ^# z  O" U19  K' k  W) R! w. K! E& P
    20
    0 A$ ^  u; U; {) L21
    / _, L" M& t& J3 K* j8 f22
    : ~+ h4 F9 g" E2 K9 j: c! y  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:6 c  S# P$ x% I+ ~5 q

    0 Y" N& f! [1 ks.str.rjust(5, '*')5 |# v; w2 G. e9 _0 h7 W
    Out[107]:
    8 k( I0 W8 H: u" k+ c1 s+ `2 q0    ****a
    . G& n" A0 L* d+ X1    ****b- R6 l" U# F. z* X) g% }6 n, q$ i* t
    2    ****c& o& j2 N6 X* a( w8 `  h  G* h* ^
    dtype: object8 `3 Y% f+ \+ L* Z$ v
    8 c- r% [5 J* S# M5 F3 E% i, m
    s.str.ljust(5, '*')1 p+ F6 f9 E3 }1 V
    Out[108]:
    ' @, N. V0 C  m% n1 ~9 H1 U0    a****
    - p4 S2 C" \& F6 v. b0 d1    b****' c; j# k* m" {8 D2 I/ P' q$ t
    2    c****
    ! _7 B6 v+ `6 H/ x9 e; O+ Q' Gdtype: object
    9 S, A- E) l+ k1 O4 s3 t$ ~3 Z3 z( A' _0 }
    s.str.center(5, '*')
    6 G0 [0 d' u. AOut[109]: 2 l4 n* p' I* b. l$ {! _
    0    **a**( L- i; H( X- h2 G. ^4 f# L
    1    **b**$ r& _5 ~4 G! ^; {& F# k
    2    **c**! q( d! S, t3 b2 V% n" B7 b( d  _
    dtype: object
    " C3 ?9 F  T& N; Z% n. r' k/ q. J$ r0 d
    1
    / x! l6 F6 _& I3 i0 x' q2 Z23 G# e- h& O8 C, J" B
    3
    & F# d7 W2 L5 r: T' X* D- R4
    & J& N) ]# v) W+ v' i0 t& Y4 o! O5. }2 s6 y& `# q" a' G' F) z
    6' j& r0 r1 \9 S
    7
    . e( O, f6 X+ b4 X1 a, d3 g88 g: R; H7 x: R* `$ W
    96 U& C1 Y" w+ H. x  p
    10
    ) d- e1 K' \7 A3 @* W0 v% _" y11, l" X$ ~, N/ S7 K/ J. a+ {
    12! h' i/ }: d- {6 i4 E8 n' v
    13" m8 u$ r8 {% K+ H
    14
    ' N0 _: j! L9 o; j  h15- V+ [. I2 w+ u! `" J
    16  m0 Z' w4 T/ O0 _
    17' Z% O% I0 w% }; D- h% w4 t8 c
    186 I5 D* k( x' U1 k6 u
    19% L: l* Z- S# D9 e* ?' K$ f. ^, m* j
    20
    - Q+ e7 H5 c% f. [7 W2 i$ c; h  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。; H* c  X6 ]0 J
    $ @3 G, O2 ], G9 c
    s = pd.Series([7, 155, 303000]).astype('string')* E' b9 G9 i+ m' E
    1 p3 F- y8 _0 Y5 m7 m# {7 M
    s.str.pad(6,'left','0')' N$ o3 g7 V3 t
    Out[111]: 9 m' T% _) v. O. b* N
    0    000007
    $ E$ Q1 ?6 p1 W% X  P1    000155
    9 G* n) s- [% }) [, g; Z- G+ P  Z2    303000  u/ t, y' [. |6 z0 C
    dtype: string% l  V4 _- g7 f% e- M6 `

    0 U; X5 \4 o! }" D5 N6 ns.str.rjust(6,'0'), l' U8 P" k" Y- _% b
    Out[112]:
    0 U) x; g- I" V% H. O0    000007
    ( b; E3 V: w4 v, `, u7 @1    000155
    3 I3 |) A4 Q' |: N2    303000
    4 F  v8 @3 O3 ~) Wdtype: string0 s4 [; c( ?: V. [/ G

    & s- B. y  u7 D. Fs.str.zfill(6)
    7 L8 d9 H( l+ W1 `; a* a6 m+ O& c; ZOut[113]: * k! B4 H6 F: p1 [( N
    0    000007
    ) |1 h0 P" Y9 W5 ^+ O. K1    000155# F, L6 @6 j: n; n
    2    303000  N* X! b, R& g* v  r( ~, ^
    dtype: string
    " s- i2 [$ W$ @  ^2 |+ t" }; S+ }: A
    1
    7 a4 v. E: D& f* {5 [9 H9 T2( j. K9 W/ e: t7 }
    34 I# m4 `6 {: M# h
    40 E2 N- S) L& z& b4 U' @
    56 v& n8 x  W" s. A& t
    6
    0 R% X, ]! b- \$ [9 o7
    " u. E! a  |6 J' f1 J8
    ) f( V0 ]. O' g/ l4 n90 {3 A5 |7 b" g: H
    10
      f  P- N% i& A9 g11
    " {0 v" v0 [% x: o12
    ; v+ X$ H+ L2 S# M& f135 v, g4 o& E% T+ z
    14
    4 q" O6 L0 y7 ?; h7 g( R15
    * s2 T, u; i" g3 W9 i16
    4 q7 O1 r/ v% ?$ W) E  D17( M& |6 @: `! n/ t/ @
    18
    4 J1 [) M7 R3 ?2 h  F' @19
    . A/ Z7 O% h* P1 ]5 O( r8 N20
    + c8 }) O7 ?8 Z9 S$ o" o/ R21) _/ b1 F* [) |8 x6 A% k
    22) g, H" S- R! ~3 \! I) w' g/ G$ C
    8.5 练习; X* Q! ]8 _3 u+ w
    Ex1:房屋信息数据集
    1 i# }3 f& g! H, }% {, I' f: Y现有一份房屋信息数据集如下:8 [5 @3 w  E" j& a5 f- o' ]! j8 F

    / e' `9 ?8 v1 Adf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
    " W4 C% B. P( T- s$ K3 Xdf.head(3)
      A1 C1 c1 ]" v8 O4 w  c, YOut[115]: ' Y3 A3 }8 n' c4 f8 y0 g3 c
          floor    year    area price4 S* n1 r* j$ l4 U2 X0 y
    0   高层(共6层)  1986年建  58.23㎡  155万
    8 @# s( z+ R3 X" X0 K( G$ _8 t5 R1  中层(共20层)  2020年建     88㎡  155万
    % E0 c. x; F0 @2  低层(共28层)  2010年建  89.33㎡  365万
    ( B. _) x$ I/ H4 W; d: P! @1$ n$ [0 \! t, c
    2: ^! C  T* Q# T, c; e: T# e1 ^; {9 ^
    3
    $ j, q: n/ q8 a; k" X) n3 S  r4
    9 ~3 i8 W/ }& Y5
    $ [; c1 X: r0 l9 q$ }6
    1 K9 W  g4 n# f7
    $ C( i. f5 b; n* A1 Y5 r% H- q5 `将year列改为整数年份存储。
    3 W6 I2 J7 C7 Y2 n9 @将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    8 U% e( _' Z% X5 h, Z  p' T计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数4 E: p. q7 Y4 l$ k( b
    将year列改为整数年份存储。* i% I! V0 I4 b# B4 H/ @
    """, R2 [7 {# `5 X
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。1 X8 Y5 _: \: F8 d, Z. Y# b
    注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
    ! h+ H' z% H, T! G0 A; l9 g/ O+ T8 M转成int后,序列还有缺失值所以,还是变成了object。
    - Z  Q* ]$ p6 m& N! \, a! f2 D而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
    9 @8 }6 a9 d1 ~" B, `"""! M. j" f) ?: Q# o7 A
    df = df.convert_dtypes()
    " U7 m% }$ B* F& R! x- odf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
    6 {7 x* j( a9 Ldf.loc[df.year.notna()]['year'].head()% s# U5 ^& y! ?6 T2 h3 A, Y9 \

    * c& s9 `' ]3 Y' n! }0        1986$ _- r, h6 c: W! v+ T3 j' d
    1        20202 Y7 A0 M: T+ x* I/ C$ i/ p1 b8 R4 t
    2        2010" S1 i' Y% j# F% m5 t" |& Z) l2 `/ h. a
    3        2014
    / k) _, H4 o& j! M% [, b4        20150 e+ `: t. ~6 E3 }: E
    Name: year, Length: 12850, dtype: Int647 R% c4 H# C, K& C! R6 D
    " |# j- ~6 Q( m7 A5 ~, d
    1/ M- h4 c- O! M9 k  D
    2) E  N- @' a$ f) @0 V3 c
    3- @# G$ p/ g% q, J+ \$ N
    41 `- ?9 j& j' i1 Q
    5
    ) i  a' b3 k, x6$ o8 T5 q. |6 L3 o" ?: e
    7
    : u9 P. J+ a- f, Z8
    % @' z( F+ }; V5 T- A; D9
    0 `8 p2 Z2 O& F2 s2 N4 k; B" K10
    ; {  z- u# ]- ~. d11
    ! u  @& ~( E3 K12
    8 I6 y9 w: D0 {, \( B7 T13
    0 s2 `: v& K+ D9 o$ _! {; W14% b" R* A( l! O
    156 p5 I1 S: m+ h
    167 p7 J; p+ x+ v/ i
    参考答案:
    ( a( p! p7 E9 B4 G7 b: M9 O5 {4 P/ Z2 h! ]7 I
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么8 @( v) K: W0 q3 U  G3 K; i
    1 e1 R/ ~- p& D1 P2 `7 ^
    df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') 2 X+ C" j% d, G+ i1 B6 Q
    df.loc[df.year.notna()]['year']7 e2 q: M0 |- l* I
    1) p+ {# X5 M9 v$ H, ]0 x/ n
    2  ~/ `8 J: b, }8 l7 k, N5 `& e
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    ! c. Q: q" Z6 B; K4 lpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'" ~- H; S9 E0 K4 q0 a9 a7 Q) a
    df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次. S9 \8 e& _# c" u
    df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型
    . z  J  G7 I! X5 k! h6 qdf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              5 q5 r+ A) P2 ?1 E+ K
    df=df[['Level','Highest','year','area','price']]- [* R1 w  U# O9 m
    df.head(); p- R- l3 z  g( J' g, {! T4 Y

    & @0 b& f" _+ y0 n* N* L% M   Level  Highest        year        area        price
    , f# U4 |7 e! |- C" ~0 v# r0        高层                6                1986        58.23㎡        155万
    : f2 t0 A$ p: v: i) q- r1        中层                20                2020        88㎡        155万
    2 N, _2 z$ B1 ?2        低层                28                2010        89.33㎡        365万& }3 T: e5 e' s# P: Z
    3        低层                20                2014        82㎡        308万
    ! u' d) I" M) O+ w5 N: S+ A4        高层                1                2015        98㎡        117万
    ; {" ^# F( Y! t6 s; n2 @9 W. Y+ N6 ~5 o17 Y8 `; a/ \8 B: q$ F1 `
    2
    - g  l: g$ e7 ?+ y) \3 V3+ G8 q8 J; V2 M% `- `4 g6 T- m6 o
    4
    & E( b% M  M/ C' h: |5) b( _( u3 B3 q# _! B
    6
    - [7 L) \5 V: {( d2 H$ ^+ [7  t8 L% E) [& }1 [. ^8 \) d
    8& u' \3 }$ L0 P3 q; M
    99 S# d, J& \4 N
    10
    ) M, I" t2 j( I0 l8 Q11( I3 ^) c9 `& G! `8 I% Q( Q1 s9 d
    120 [+ ^. q! C/ K4 ]" x# b
    13
    / m. p, f) y4 c. J6 W6 v6 e; W) ~# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
    9 K! P' d: P- Fpat = '(\w层)(共(\d+)层)'0 \# l% u% B+ l. K! F" z! |* o
    new_cols = df.floor.str.extract(pat).rename(( j. R; W8 w( o; U9 X2 W- U
                        columns={0:'Level', 1:'Highest'})4 s' O0 u8 L3 X6 `

    5 `" U& u' {1 y# O6 i3 Edf = pd.concat([df.drop(columns=['floor']), new_cols], 1)2 c6 G; Q  {  u  f( y2 m# o
    df.head(3)3 e( D( g! u- i% Y& V

    - W! x; g% v7 K. ZOut[163]:
    , @. ~5 O* O4 Z; x5 {$ V, w& l   year    area price    Level Highest( j) ?3 g2 W; _! T2 N
    0  1986  58.23㎡  155万    高层       64 F6 C3 b, s  d" b% @/ z
    1  2020     88㎡  155万    中层      20
    ' Q0 B7 v( Y/ g1 e! ^) J+ }2  2010  89.33㎡  365万    低层      28
    5 |; R' [4 _  r/ t5 j. O, X- u1/ v0 t; m+ `$ i" ~
    2
    & a; E8 K  n( V- t1 k9 ]2 J3
    8 |, T- C% A6 }& I5 c% Q* G5 C4
    - a( O9 m) P) b( m. S. |5/ I: o* R" E# e7 B
    6
      Y1 }, n" U. V9 Q7+ ?. e$ W! H# \: g
    83 c6 w. m. w: C: T  j& }6 F) V
    96 X8 a6 }6 E8 o3 _7 p) N7 W
    10
    4 @3 P, D6 Q  g2 [+ y3 x. V11+ q4 V. _; V7 k5 i' v
    12
    ' R4 U5 o  A* n/ P13# w, {3 ]0 V9 F
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    5 d0 Z3 M! o0 o6 `2 z0 M"""8 k. b* G% x7 L1 @8 g' c
    str.findall返回的结果都是列表,只能用apply取值去掉列表形式
    2 \$ ]) X: y9 D/ \0 o参考答案用pd.to_numeric(df.area.str[:-1])更简洁% e% j3 w9 Z. l7 O
    由于area和price都没有缺失值,所以可以直接转类型
    " U2 Y+ v% H) C"""
    9 I& B# {% W  g& jdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))) h$ \! |" @" O" q/ M' g
    df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')  s/ O( V# d. D' S' N% q
    df.eval('avg_price=10000*new_price/new_area',inplace=True)' }6 t! k  E' K, o% r# N2 b
    # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
    % ~$ B' G& O+ [# 最后数字+元/平米写法更简单! C' R9 N" ]4 B3 ^4 c$ D/ {: T
    df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
    ! P+ J$ z1 Q8 e1 udel df['new_area'],df['new_price']" `. s4 o! \/ m( M7 E  V* s* M
    df.head()  r% f1 ?, U0 u7 o" A/ s4 B: s0 m

    , e, |5 ^: o1 x+ i* ~& }8 N   Level        Highest        year        area        price        avg_price
    ! S! ]  B  s1 A0 U0        高层                        6        1986        58.23㎡        155万        26618元/平米
    2 y- V# |1 t3 x8 R: X) a1        中层                        20        2020        88㎡        155万        17613元/平米0 t4 u6 M$ o' D! T
    2        低层                        28        2010        89.33㎡        365万        40859元/平米! [& I1 {( W: K0 S2 p0 ~
    3        低层                        20        2014        82㎡        308万        37560元/平米5 `% y7 k; M0 d# ?
    4        高层                        1        2015        98㎡        117万        11938元/平米8 }1 v1 ]' p& A- s- I) P8 L5 j" d

    1 d$ r# Z; [* S5 e5 u1% d, \! x2 Y9 R* R, t
    2
    + u, P0 k; x9 r. s3
    8 c' z. B6 p8 _- Q4
    ) |# o5 f2 b( v) a4 n0 Y5
    , ^' ~. U  H1 I6 Y0 @6
    5 g# b1 E% X! M7. _3 ~/ c2 v# F; d8 K; Z+ R; O
    8. }# d% a) D0 b0 Q5 q6 q! S9 V) J
    90 M9 L4 V3 [8 m0 y9 ]& m8 X
    10
    $ J. K1 v* [8 g% P+ W6 N/ b114 V; j) F% C7 {- i% y
    12
    7 {  H4 b' J- c  ]7 t# s13
    ! A/ p3 p- [$ k7 t14' m$ e$ f0 @* N; P! `
    159 `- R/ }; Z1 M0 r! Q7 g
    16* `- o' N# [3 Y/ \7 ?2 j  V* A
    17' g7 n- B: l- a! Z5 e
    18
    * z) j8 Q0 M* X4 p" Y% j19
    2 ]# W% A5 q- u4 w9 C+ U1 l' u20' r( }; b' ?0 ~2 ?: z
    # 参考答案
    + ^# g' M' G4 x, L0 @s_area = pd.to_numeric(df.area.str[:-1])
    8 F1 I$ @" U: i6 f- F( j% J* `s_price = pd.to_numeric(df.price.str[:-1])
    + a5 v$ {4 `7 |* Udf['avg_price'] = ((s_price/s_area)*10000).astype(* u6 [$ F+ {% ]  X+ r! O8 g/ u' h
                        'int').astype('string') + '元/平米'" i' A: m' ^% ^8 Y. ?7 u5 ]  c6 B! h
    % E* w) k$ B9 c* j5 m# @
    df.head(3)
      l: e- r; w* c8 p# ]+ |. S. @Out[167]: & i$ q5 P3 y: X- F4 q' F
       year    area   price   Level Highest  avg_price& A7 z4 s9 R! I, X
    0  1986  58.23㎡  155万    高层     6          26618元/平米  }& V$ Z) i1 t, `+ _$ C5 {
    1  2020     88㎡  155万    中层     20          17613元/平米/ K6 x, x: H! T9 d7 P, B
    2  2010  89.33㎡  365万    低层     28          40859元/平米
    , Z7 l2 O! b; L2 r16 h" [8 A5 p  @" g; t
    2& K6 A$ e9 {; h* j
    3
    2 c3 j* h6 S* ^4
    * h1 X% I6 |2 k, @0 }5/ _9 p+ e& B9 Q# a
    6
    ' N6 p8 U1 h: j( @7) k' G( H2 l; d7 P* j
    81 ]- r0 v: G7 C1 V
    9; x$ r3 Y% x" ^4 I& J  @& A7 M
    104 f. S; I7 n' Q3 J1 Q
    11
    ; U6 Q; ?, }& j; O9 ~8 W12+ U- g3 n, r8 _2 ]7 ?8 R
    Ex2:《权力的游戏》剧本数据集
      h% h7 h0 T: n& E$ t2 L" a/ G9 {现有一份权力的游戏剧本数据集如下:
    , q$ o9 x) \4 }
    - Z. o& g5 S: E0 [1 `df = pd.read_csv('../data/script.csv'); k' r. b8 w# L5 b8 y! i6 E/ ?. V
    df.head(3)8 \  k  B# r0 D' Z* |# z5 {+ j0 [5 @
    , B: V; D1 I1 V6 X
    Out[115]:
    , e& k7 V. W4 l. j' IOut[117]:
    : \, h% A! J: Y8 v  Release Date    Season   Episode      Episode Title          Name                                           Sentence
    * f' |" y; E: f( n  M0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    : a, n$ A7 f/ P1 X$ R7 `( ~7 _9 w1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...- b+ k; w# b, G3 r3 i2 [
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
    ( j) b1 o) M, {* u: B18 ]( O' F0 x& F* M
    2* I9 K. S/ W& \7 J. i
    3) e+ J5 m' H  j- n
    4
      D1 ^4 [% R2 Y& Q5& |7 b7 @. [$ h3 x* r) z
    6
    + G  i; M2 S# X- [4 h7
    0 d( }8 t. D" u7 D5 ^! H  J8
    ; O. G7 z( z! b6 E. j- H9
    ! L1 L9 j" u( D3 w7 a计算每一个Episode的台词条数。0 j7 g/ I% N/ J) i/ R
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    * R$ `2 C% x7 U4 P1 F+ Y+ o9 {若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    7 h" U( u- \( B& w! C+ r# v计算每一个Episode的台词条数。  n5 D. u: K+ Z, C0 G8 i/ r- }; q
    df.columns =df.columns.str.strip() #  列名中有空格
    / n" E0 d- B# A4 s- @$ K% gdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()# k. a8 K/ m- C' D+ L% l: A, m

    & D# ?0 B. s8 a0 r. q) H# T* tseason    Episode  
    4 g7 N9 D& J  X( U" J% QSeason 7  Episode 5    505; a7 t+ A3 E8 y5 M
    Season 3  Episode 2    480
    . F7 n6 j/ s0 B7 M9 x& V, uSeason 4  Episode 1    475' L/ C- G* @& S8 b
    Season 3  Episode 5    440
    ( T! }3 x, {/ FSeason 2  Episode 2    432
    : q9 H+ ^/ `  ~* z9 w1" O: N: L& h  }3 Q: _. q  M' e
    2
    - [* E2 p8 S  X* O3
    6 J# u5 Z% D; a4 B$ W4
    0 E$ `0 E+ @( H8 I% q; x5' t( @( E1 b5 Q6 v
    6' P( u- H1 x' c
    7
    * k8 |( ~( m  s8 I8
    $ f; n/ x/ X, {5 k5 I: h& c94 Z% T7 v; N  X: j9 B+ E( |
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    : N( s9 m4 w8 j3 g# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
    3 O2 H5 r' x8 B# h4 }df['len_words']=df['Sentence'].str.count(r' ')+1
    , `, g) n, `# g( h# zdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
    ( [3 u; T7 T2 R5 l& ~7 a! t' B- ]  I* E
    Name! E1 z8 F4 r. d# S9 m- D4 W2 c
    male singer          109.000000
    & G6 s. A  q- V" ?; yslave owner           77.000000
    3 ?+ \7 j8 f1 k  G& Pmanderly              62.000000; |. {0 e- o+ ^9 h  x  G
    lollys stokeworth     62.000000, o( X. k* S7 _' b
    dothraki matron       56.666667/ |5 Y3 C1 A7 O# Q
    Name: len_words, dtype: float64$ i3 K) @5 k, _3 j7 f* o
    1( A9 [# W( q0 F$ N) _4 [
    2% k* C! f7 {; l1 i6 [$ [2 b/ i
    3; W* `, {3 _( s  A; m$ E
    4
    : s% A  ^% C" f' ~8 W8 g# o5+ r) U. r+ y/ F- e5 T! t
    6- l/ ~5 }, j9 H, g8 j8 k# T" D2 r
    7, v3 D$ R( R+ D3 }' J( w  j' D  `
    82 U" |7 _9 s: V" L1 j
    91 o7 j0 ^5 l2 o3 o
    10
    7 X; r7 [* K8 `& R$ W( T11
    $ X0 D! b. c; Y6 V. C* Q若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。* \8 U' w6 o. q- }6 c! n* M/ q
    df['Sentence'].str.count(r'\?') #  计算每人提问数8 K- H3 T- ^9 O( _+ H" {
    ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
    ' H& @1 c' @9 U3 [+ N/ Zdel ls[23911] # 末行删去
    ) b/ E( |, h9 W& H. ?5 G! mdf['len_questions']=ls7 b  x; A# m( o$ O, B) w9 g/ S
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
    # _8 o5 Y  l6 p  X2 ]2 N4 F0 s' \- {; {2 W! V+ h
    Name' n- k, w% f0 l4 h6 b; L
    tyrion lannister    527
      S% c& p: F. C( V5 B. ]jon snow            374
    ) N" f( D% I/ r6 Kjaime lannister     2835 h4 Z, `& m7 v1 \. r
    arya stark          265
    % _. W7 J/ B, b' j  _- hcersei lannister    246
    8 \  H. C0 P( k/ `6 V& B) V+ V8 p9 wName: len_questions, dtype: int64
      f' u! K( `+ f3 z& S0 D7 ?9 M+ q, N" s" C2 ^* y- g% B5 M
    # 参考答案
    / N! _6 a6 ]* Y- ~9 is = pd.Series(df.Sentence.values, index=df.Name.shift(-1))  Z+ H. M  D! c
    s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()% _6 R3 J9 e; I& `! V: h" r$ x- |+ }, M

    : G( T! D# ^8 ^+ R6 J1
    4 i8 P$ a2 o4 u6 I. e2+ w, P6 x6 t- S; [% g* u5 f
    3
    , |: m& |  L4 J& k' |+ C4
    8 J& a) n( A$ t' |3 A1 P+ [5
    + W2 k6 z* B% J6, f# [  V& K  |! `
    71 E6 D! M  W3 T3 @
    8
    # ]" x4 z6 i$ u4 o3 ?9( C/ Y% P, Y: V7 z
    10, b5 v+ c# P$ w2 Z5 f
    11
    1 M# ?2 r0 R% o0 d9 E5 f12
    9 L9 u0 `5 }, Z% S; ?, `0 ~13! k: B& X# T# x( v3 `* r7 ~; F+ X
    14+ T' A8 S5 q- P8 q8 p  o
    15
    " g. J4 A; s+ e4 T  R  }16
    ' Y- [' N: g+ X: o. h/ O17% J$ F' B' L7 P$ m! }5 N
    第九章 分类数据7 l$ s  d! V$ E, c- W
    import numpy as np
    1 _, F: h7 q2 timport pandas as pd0 @4 W( R& O; J2 t1 M, \
    1
    & ?8 |9 S4 _( [0 j$ h; L2+ o3 E$ ?$ p! h
    9.1 cat对象  u% v) m3 p/ H$ S& @0 Q
    9.1.1 cat对象的属性' _: H* o& i! v1 s+ s
      在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。" D. ^# R& w; Z3 p% X! F2 T
    : @5 Q; K! I$ K7 a7 u/ q' N! z
    df = pd.read_csv('data/learn_pandas.csv',
    . E9 [7 F( ?( q, k5 L: v0 i( M7 `* c     usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    : h! r4 f5 W; Z: _" Ds = df.Grade.astype('category')
    & E) h( z# T. H+ a2 m" X
    5 t+ t4 `! R7 k3 R5 s+ @9 _s.head()
    ' H5 f! j* |# y8 [1 FOut[5]: / l/ e6 s5 r+ S( y
    0     Freshman
    5 @- @/ {: {/ W) w8 G5 i1     Freshman2 P: F# P2 Z0 B. o) J
    2       Senior
    # q6 [6 ]: B3 A' s: d. @4 N3    Sophomore' ~7 W' N* V8 }; K  M2 ]
    4    Sophomore
    / n9 N% _; d- ^8 v- O; I7 NName: Grade, dtype: category
    - r0 W3 i% g$ M( k$ i/ U3 `Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
    / _' P; I& L6 @: Y  g, e1
    6 ^9 h+ x) e- ^7 I2" ]( A1 k- E6 h# u: Q
    3
    8 T# l9 Q7 C5 F4 E. ^4! N  B1 U' K1 Z( w3 H! P+ t! |5 T
    5
    8 L: ^1 @; f' C& Y; [69 @$ a. m4 @3 v" \4 N2 E; ~, b
    7
    5 r% W! C% n# [& N7 G  J8, b% A! G: {0 z7 P
    9" ?: G/ Z6 h! D# m
    10
    , i1 r9 z) ]9 l" Z3 p11
    ( V) L: q' S* s: H3 U# G" `; K12" n+ g0 ]# T' G$ w" P' u0 R& y2 B/ C) I
    13
    8 s3 O. b: P7 |  W! @  在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。% X- U0 a( z: B' ~! q2 M, E4 R

    8 V, w& a8 `3 D7 o7 Ys.cat
    ) P6 n% [5 ?0 l6 e) }8 WOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>. B+ @+ u* U/ t$ J% t8 C0 X8 b+ \
    1, u2 k0 K6 u( `: a) m9 x9 a5 h0 L
    2; n1 g# N: @( G8 |1 z4 V( y
    cat的属性:
    : u1 L5 Q1 f6 q+ |4 ?4 d1 Y
    9 X- U0 B! D7 ]cat.categories:查看类别的本身,它以Index类型存储5 N$ _. B% Z6 w( l* @
    cat.ordered:类别是否有序
    8 i3 e) P0 t! h/ zcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序( k( n0 Y  }* M/ N+ d: u  d% M
    s.cat.categories3 \( H; j0 s( _, }. m8 n1 C- e
    Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')7 d. }% f/ D; s5 j! Z( `8 C5 v! n) Q
    $ N! j  e. X3 r% }4 V, A
    s.cat.ordered- u. h2 A/ @" a$ r* ?+ q) M: N
    Out[8]: False
    ! ^4 N0 A& w* g0 A
    ! o' w  I& l% n( j  V4 V% zs.cat.codes.head()! {7 J3 v) U8 J7 H
    Out[9]:
      l1 M6 P' B$ x4 C$ ^0 A0    0
      D' t$ J2 {: V8 c: X, l, s8 n1    0+ J+ Q7 P" P3 B4 y$ a( F6 Q; X
    2    22 a% |7 g3 e: r
    3    3
    ( [% ?2 F( c2 Y$ e; B4    3
    0 k6 H, R# h9 p; gdtype: int80 a7 m+ G9 j; C
    10 N4 p# l  u1 l+ z0 L) |$ H
    2
    . x$ b- F* y! i3* M: b% A: A' H8 g7 ~
    4( j  z, [& k. L5 p3 c# @4 B
    5
    1 G/ _, X% i) G4 {# |; X6
    6 C2 I/ [& x  Q9 w; T7
    6 k4 H% r0 z2 X( C$ ?+ Q- W85 z1 y$ f4 R: i) o" K6 Z
    9( B& j7 z. A$ t( x, @! D
    10
    ; {/ n$ P0 k7 J" ?4 T112 J, W0 x5 M& j" ^( w0 l$ T% Y% M
    12
      A+ J6 ^" K; A* f( g0 G* ~6 e13
    * g6 P3 G/ X( @! ]& @14
    2 ~% i3 p  K; H1 D! c) Q9.1.2 类别的增加、删除和修改' Z& P( X! A6 N/ T8 C5 z4 C, ?
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
    + C+ K* t# l0 g2 V- j$ o5 ^7 d; J. ?  |5 t
    【NOTE】类别不得直接修改% ~2 ]1 ], F; H
    在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
    7 A# L9 H( w# Q
    3 ]2 i9 z- f( B; |; }0 ladd_categories:增加类别
    8 \8 a) [4 y; s$ d- `s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
    ( d2 z: u. h! ?/ ^s.cat.categories
    0 H; i, S2 K+ D/ a/ y# j( H9 ~
    2 f; N# e/ L. O; D: N5 hIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')5 @, Q( x/ i- u' _
    14 @3 i0 C6 ]# N: \$ z
    2
    5 f+ j/ K8 U! W6 L& L2 J% e38 k& ~2 p# S* ^+ q( K+ J$ K- m
    48 ~" L8 O6 ^8 H3 q" M8 h2 f
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。2 V* {! k% Q( D. _9 \
    s = s.cat.remove_categories('Freshman')
    ' c, G" F: x" |* Q
    * G! m6 R2 p8 ~- ?) H: ss.cat.categories  x. z; Q/ g# M& P; [
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')( D9 I5 ]2 L& x3 H& Y  n
    ( C8 ]$ H' ~, D/ B! A% d
    s.head()
    3 D' ^* j; J* {, t  [& ?: ~Out[14]:
    ! y* p2 a1 s  A. ~$ @4 L. i0          NaN/ l, E; |+ a: n  ?  `0 A
    1          NaN
    2 n4 T5 D; C! [: R: q% q  G2       Senior
    4 F+ S' F+ M* ?1 l3    Sophomore
    & k# _1 ~! e/ ^& X- B4    Sophomore
    4 {" {6 t7 B" E6 kName: Grade, dtype: category1 _% c; R  \9 D, H
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    : p1 w* V$ Y: j) e14 d" j8 d; R5 c9 `
    2; b4 |/ F8 P0 l# d: h
    3
    1 L& v4 @# z' P: u# X4) n" U5 i2 M7 d" p% p
    5
    ! F4 x8 C' i( z8 S( w0 V+ W6 d6) f* m7 D9 W: f5 t8 j& c
    7
    1 E% a: D3 `1 E7 I! x8  \9 A1 A- ^$ y0 K" m( w
    9
      y3 ~7 v5 k1 |/ V% N8 D10
    + r0 `0 [  y5 H: O9 K116 S7 L0 b2 J9 W5 k6 r1 j: [
    12/ d% p, }# ?) u4 e
    13
    8 z0 Y$ F* h: G& u14# F& Y6 S( e! u$ Z8 C- W  i
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。  H; c1 h% e" E
    s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士5 ^" k4 z6 N3 }, z  j; B5 w: E4 P
    s.cat.categories
    4 W4 ]) l, A1 j. aOut[16]: Index(['Sophomore', 'PhD'], dtype='object')
    : h& i% \* ]/ |0 w: d2 n7 F1 a# Z; F1 V# u6 F2 E
    s.head()
    # ?* s; K% Y3 I$ e. R6 DOut[17]: ) C" K. P# }. V' V" [2 l
    0          NaN
    ; h* C, n+ Z* Q7 X) G) a; B+ T1          NaN3 q9 Y6 Y6 b- I5 ~2 ~
    2          NaN$ G5 O) n) ^! `- P& `5 @" h( a
    3    Sophomore. P2 ]% Q0 I& ]+ I5 B
    4    Sophomore5 b" o) S  |: u( L
    Name: Grade, dtype: category
    ; V0 _1 j; s7 ~6 D% ACategories (2, object): ['Sophomore', 'PhD']) a* _$ J; q  }! g) X0 \6 S4 K* ?; x
    1, `2 v8 J# t6 z& O# n) a1 J1 F
    2
    5 ]% U  h* U5 }& S3
    . `% I, f  H; ~- f6 y4
    1 I5 q" c8 K- n( y/ K5
    " B% N- X, b* K; y4 }$ j% j) @61 J7 o* A- e+ q  _) ~/ e, s
    7# C" Z" @' v& A) m) n- g
    8
    ) k7 t% O! K( p2 V/ k9
    / {1 w. _4 H7 t6 s, _/ l10! ]6 e4 A+ G% I6 t* P4 m# j, z
    114 S4 v6 j/ |/ G# c! u* R
    12
    8 N% t' t0 o: a' Q* f132 d1 `  z' F7 n6 N( r! A" U" v' X
    remove_unused_categories:删除未出现在序列中的类别$ Z2 O- E6 t. o4 t5 M+ T' {, _
    s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    $ o3 g" ~) @( d8 f# Q; V" ?6 ss.cat.categories
      k( `9 A) X2 `7 T8 f  l
    $ M; S' D0 v+ a) @Index(['Sophomore'], dtype='object')
    # B, |' W9 I1 b5 f" e1
    1 m6 F( N: \7 r* U; m) L2
    7 N) r6 b. H: @/ r3
    # L# s  g- a. A$ y! y4- q0 N. J7 v: o) V. p' m
    rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
    . t+ e1 b* l3 E) z" I7 I% t5 @s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
    0 W% g" h# u% u3 g8 V% p- ts.head()
    ' e- x- U) [1 t6 D2 E  i! f! g* x2 @- u4 G2 {; F
    0        NaN
    5 @: D5 ]- ?7 V6 d8 f. `. r" ^1        NaN
    * u! K3 X$ l3 r* O  K( }/ o2        NaN
    - b& m4 J( n$ g" _7 @; U+ l3    本科二年级学生9 l% R% g9 W1 ~; {$ P5 Z
    4    本科二年级学生
    ! }* f+ r6 \/ L7 c1 f# @  |Name: Grade, dtype: category
    $ [# E6 p9 {4 U, `+ q/ t3 ECategories (1, object): ['本科二年级学生']8 e7 w/ ?$ Q1 _! C6 j4 V
    1& ?" q+ r% e/ [# S: W( g
    26 v% [( R; p0 T) U/ Q
    3
    / x! U3 Q- K( y4
    9 D/ q' M! y) w$ O5. i9 e+ o+ G. P- _: n. A2 Y0 H
    6
    1 L/ ?, k: Z! ?& h72 ~$ N" z9 ?3 [! z/ E% b) o" N
    8
    + _3 v2 f9 r) w. L( z4 \+ H9! w8 N& `) E1 Z+ e- H$ ~
    10+ B8 l& J  K# L  }  q: D0 U
    9.2 有序分类% \0 k6 S- ]' h0 X( W
    9.2.1 序的建立
    / |( j( p+ ]# E- l% r& {. Y  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:6 e6 m- v2 k1 T" b' K- ^+ W' ]' R* e

    ( s# K; V" s  w) ^- {: @s = df.Grade.astype('category')
    ) ~6 Y2 W2 L! k6 J1 _8 c# gs = s.cat.reorder_categories(['Freshman', 'Sophomore',& [' {7 S9 q  S4 ~0 `; v
                                  'Junior', 'Senior'],ordered=True)
    ( P$ a, f7 o* N3 Hs.head()
    + d$ q. H* B( Q) \+ X/ V! r% ^Out[24]: 8 T( y9 o1 f6 _6 T' y, c
    0     Freshman; E% _# B& I0 f+ L2 l
    1     Freshman2 _/ ~; P* p& [# r! N* Q: W" R
    2       Senior1 E) C) ]# U4 ?+ r; R6 ^/ h; c; h3 I" D
    3    Sophomore+ W# P# q/ q! B. N* S4 M8 z
    4    Sophomore
    ! U) W) c; z8 y0 [Name: Grade, dtype: category( L- }! i' C, l- x0 s
    Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']4 u# W& _' a9 J" q! y+ f5 `0 a

    + j; t5 \% G- ]' j7 }s.cat.as_unordered().head()
    - \1 t. ?. q" D8 Z. v3 G6 {) JOut[25]:
    ) E( O: F- S1 K; Z0     Freshman
    $ e0 E5 Y3 W! r5 a, R1     Freshman
    , }1 g% z1 V  J: M; O6 e/ r2       Senior
    - H% _# k9 p9 Q0 d3    Sophomore
    5 r' y( e+ w" I- p( c9 y% b- E4    Sophomore
    " `6 i# Z3 o& N& E! C7 `: f3 NName: Grade, dtype: category
    0 `* w) y7 v$ w, A" L7 BCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
    4 |0 _6 [3 y+ v! n$ d6 d" x' s  Q8 B) M+ p
    10 }1 u3 x9 n; q. ~5 U" o5 f
    2
    " A! I7 \, [$ |+ n3
    ; _7 G' ?; X0 a( O4
    " \; [2 Z5 S' e. x5
    5 V, V  U9 d( U& u6
    , o' Z- |6 a" v) \" m  ?" ?# a7: K# P* i/ @, B. @0 |' i5 n3 Q5 N( Y
    8
    . O  Y' A. L+ n8 l' Z: T7 x: I98 {8 g* V! f9 E9 X! Z4 O
    10
    ! H* A2 r" D/ t2 X* O11
    0 B4 i* q" }( D( m" q" N12
    8 p. J. T$ L7 |' \131 E/ L  M* O* Y+ U
    14
    4 y/ f( {; D2 G1 n  d15
    + q- p0 [2 L2 z0 G! S: l/ o2 `$ {16& \( e$ a. v2 N. X, \! q' ^
    17( u! A5 \# i' y5 P; T
    18- G  l# V  B9 v* k- D5 O( M
    197 q' ^7 v% n" T' b8 u' e2 E
    20" o/ l4 ~# ]; y
    21
    4 j7 [! j$ @9 A6 K) p22
    3 g: y3 P9 \, R7 N- V& u9 M' [  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
    $ _0 E- u& ]* ?* Y, J/ v6 C% o* Z' ^, ?; P, E
    9.2.2 排序和比较
    9 B% F7 T1 V& O* H3 Q4 p4 W在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    . m9 }3 P. e2 v
    / I& p$ h. T; T  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
    0 {( I. p/ y& l/ V6 _1 N" a5 }$ }5 I4 T; l6 j' M/ n- w
    df.Grade = df.Grade.astype('category')
    , c! ^; H6 i7 A9 e# t% pdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    ; M) u* E! m! Q9 Xdf.sort_values('Grade').head() # 值排序$ C3 q; E  g$ q: s( \
    Out[28]: 2 {9 g: U0 L0 S3 ]- `" A$ m
            Grade           Name  Gender  Height  Weight
    # F9 I7 k3 z$ S* f9 ]0    Freshman   Gaopeng Yang  Female   158.9    46.0
    & M5 l- X+ [- _9 ~105  Freshman      Qiang Shi  Female   164.5    52.0) P& Z  E& e" G# |3 _
    96   Freshman  Changmei Feng  Female   163.8    56.0
    " N) }- k3 @8 q0 N/ `2 S# V  _% Z88   Freshman   Xiaopeng Han  Female   164.1    53.0- v$ S7 V: T5 ?8 e% a
    81   Freshman    Yanli Zhang  Female   165.1    52.0& J; W+ H0 W# D+ U: L* W' v; g
    : G2 r( }* G; m1 \* c7 {
    df.set_index('Grade').sort_index().head() # 索引排序! _0 a9 Z, W0 _1 L3 f" T' I
    Out[29]:
    * Q4 A3 K* N2 @                   Name  Gender  Height  Weight4 A' o/ M. u; Z
    Grade                                          ) `; T5 D5 t& C
    Freshman   Gaopeng Yang  Female   158.9    46.0( F& a7 Z# [( W
    Freshman      Qiang Shi  Female   164.5    52.0
    3 d3 N- W( r; G3 G' @+ C+ qFreshman  Changmei Feng  Female   163.8    56.0
    ; X- U! D& t( x* G- `$ R" TFreshman   Xiaopeng Han  Female   164.1    53.08 J, p- Z0 M9 e! V) K: Q% r
    Freshman    Yanli Zhang  Female   165.1    52.0
    5 |) E" N. V6 s6 @. n3 Y& [
    6 n+ X, N. s3 E1
    9 W# b" f& y+ X- n+ f22 q) t* a2 G9 n% r3 h
    3
    5 j  o: l$ J, H) O) ?- W5 A4
    3 j. J  y7 Y) @1 V' ~- V5
    ' L7 P. w8 ^# _$ ^2 ?; \6' O+ W3 A1 x& D5 i
    7
    8 M7 J/ [/ X$ d9 l# M0 j8
    ' e) y( H8 ?- I+ K9/ A! u! w4 a! `$ n1 D
    106 K5 K: q+ f; u7 v9 I5 G; u/ B
    119 z& d$ F9 S  z
    12
      w9 U7 I1 F) x% d139 _; V9 v6 B% P, y
    14
    3 j0 a* j: |- g5 b$ j, ]15) E( H( |: ^% m1 j2 w- P
    16' n9 ]! Z0 K6 Z: @+ R3 E
    179 K$ z& ?: j5 r) J4 Y- F
    18
    6 g9 @! o1 r/ ^4 f- d  T& x, a19
    # @; e/ q; C7 Y; ]20' d( I9 ]6 g( X
      由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
    ( }. I+ P* V5 q$ v! s6 g! v* k( Y. x- }+ s: \# e* j9 x
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
    " X" a) y6 g. M1 u! }$ z>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    * U, {  u) p8 f! |! Eres1 = df.Grade == 'Sophomore'
    * w8 B0 f' e6 n& ]2 r0 b6 Y! W. s0 ]" ?$ K" N: T2 c# X
    res1.head()
    % D. ^1 L& k9 ~+ y" z/ z7 ?4 F7 _) u; d4 lOut[31]:
    + I3 B% N, J; I- @0    False
    ! t5 l0 Q4 j/ u0 \: {* O/ z1    False5 r9 ?, j4 P  b# E, E$ B
    2    False
    * t# D$ ?: S7 `+ l  |+ r3     True1 C! }" i! j& z
    4     True. j" S) T& C# {4 _# l/ s$ _7 s
    Name: Grade, dtype: bool
    " @. v4 A+ X( q$ q# f( {9 [, x: p. V4 e2 J
    res2 = df.Grade == ['PhD']*df.shape[0]; j) p: t: C% q# {+ W
    9 n* D. W# I1 Y( l' m& b
    res2.head(); h( ~! H" c# l% ?& E  b4 i
    Out[33]: ; }. F# h5 ~1 j2 |( V" N, F" I
    0    False/ ~$ I4 r, r; J  j- N
    1    False" \; J1 A' L& G6 Y1 s* d' Y* A
    2    False" h: s  f* ~: P& O7 H* N
    3    False2 \% E* Q& C+ ?# e* r
    4    False
    ) ]* y  X) i5 [) MName: Grade, dtype: bool7 F$ h) v$ H9 S  j+ p0 F( \. D4 ^

    + O$ o4 x( c: V( J. j# o5 Z/ ures3 = df.Grade <= 'Sophomore'
    0 K9 O" L4 m  z; A: B$ r  @9 G4 s4 u* X) T" g- w
    res3.head()8 o* l' H& k9 u% G
    Out[35]: 9 Q# T+ X  X0 R) T, U# }
    0     True- O! Z4 [9 L7 B" D: y
    1     True: l6 S- c  y% i' G6 @
    2    False' I9 G2 E$ k9 g) |+ ^5 h
    3     True* x$ q9 k% ^, E; }
    4     True
    " M( v& Q/ s8 U* _& O' K* ]$ n$ HName: Grade, dtype: bool( Q9 P, z& M, `% A. E: }

    4 c: Q$ e  {. Q# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
    4 z' h6 T  E6 jres4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) & S* l) O4 q7 `
    ' {+ I# l' R1 |$ F( b' I
    res4.head()
    5 ~3 S& s* p" Z( l) S5 mOut[37]:
    * @7 q4 x: w& T! D: X0     True
    - `  e0 f' i* S1     True, w- ?4 o0 C2 J% }4 z' M
    2    False
    2 t) c! z4 W$ s( p) H3     True! M6 B5 J6 L3 y
    4     True. D- U$ @9 ?& ~4 y- @# W
    Name: Grade, dtype: bool
    % T8 ~6 n8 K5 z4 M  l* P
    & l3 K* P9 e$ ]0 ~3 F: ]1& G. |9 X  y4 S9 u: u
    2
    # |& Y5 c  R, c( V8 c6 a3
    % B; @0 p! n$ s( l  T1 I1 X% f4+ \, X  t; A! i( C+ o/ S
    51 W% v& m8 R  g
    6! _4 j1 h; x9 E5 N
    7/ G3 N0 u% W$ p. H/ u) }
    8+ ]# J( P; n4 j; _  N
    9& A. u0 y2 O! P+ r' y7 ~" p
    105 m, S% n9 o9 i  d8 G0 @$ o
    11
    . k0 ]0 b! ^% U+ H) j12+ X( h) `: a8 I
    137 R, T+ v: K6 w& i
    14
    7 t7 @2 H( G' a15
    2 d/ g7 ~: Y0 e; H' K, _16
    & X- f/ G4 d* X% E' ~  J17
    8 `: a8 D' y1 l! t. T- E; \4 B184 `2 r# p7 F3 z# P% D8 ?" v
    19% G$ i: s7 g- ?
    20- J: q- L/ y# W0 N4 {$ X! B
    213 n' F( H' B) ?7 R' r
    22; [" O; d* S( b0 E) d
    23
    7 i7 ^" s2 l, G- m* `& d: v24) Z" D' p# ^% f& Z7 F' P4 ~
    25
    6 `! F# l& S* ]" _26
    7 y8 M; s: a, C3 y9 Q6 ]* c27
    ( z5 n0 k( g7 Z! Y8 k7 K# H28% p# {7 x. _( F/ Y$ R- O
    29
    0 F! B5 r# V- a$ U1 d( `# a30
    ! Z3 r% q$ D1 ?- S5 {31
    9 y8 C- w! x; S& R0 ^- ?7 o/ ?1 o4 V- g32
    6 L/ a7 J8 p  W8 C* }8 r. X, }334 W' `4 D; P1 w0 v% y7 T$ p
    34
    1 N  P8 v( Z7 R, ~8 ]1 l35) W" ~4 K9 C4 B3 m9 q0 _
    36
    . e* d# x( ~+ d6 e- Z! i37
      d! \& c5 M' V( t( }# v4 D) v* y38+ S) J: O1 I# a, Z  n0 N
    39- X; r& b" k; x% C* d0 z* r. z  D
    40/ g; M4 U3 S- }
    411 X# p/ l- G9 E5 o6 t
    42
    * o7 H/ Z5 V6 r43# b8 B" l4 z* U& U3 Y
    44
    4 A! L9 R+ P/ n7 z9.3 区间类别
    + o7 r- @/ ~+ D( m: W  y9.3.1 利用cut和qcut进行区间构造
    . J( T7 j0 u( D' v1 N/ o# p$ t  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。6 K; u" [& v3 ]: l5 _: u
    2 h6 f. C5 H1 K+ y/ q* ?* i
    cut函数常用参数有:. q1 \: O9 S' u" w
    bins:最重要的参数。5 L, ?+ S0 [1 _( x* T/ Y, h
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    ! l* q) ^- z+ U$ Y3 @也可以传入列表,表示按指定区间分割点分割。
    1 x( L6 n5 P1 O  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
    4 n! x, E" Y" `. J" C2 ?. x  如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
    3 H9 S! r" ]/ i9 A: y# B
    & t5 B2 A8 j: h+ W0 m8 ss = pd.Series([1,2])
    7 u& A6 k1 ]) y) u- ~7 {3 A# bin传入整数  H# s7 I$ K) h8 |, N; H
    ! I6 C; M  T' y* X5 ?# H0 U
    pd.cut(s, bins=2)* z: ~1 V7 {- s. X2 G
    Out[39]:
    2 H2 M2 [7 T( D! }4 ^: n; a7 u0    (0.999, 1.5]
    3 Z+ e8 V# m$ v1 ]7 z1      (1.5, 2.0]
    & I+ E/ M: M! |3 [0 w2 tdtype: category
    3 v9 N1 z( g5 SCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]5 _& {1 y; ^( y& B" ^1 o2 ?

    - g3 C$ X( W% `pd.cut(s, bins=2, right=False)
    ' F# W8 z" y" a" JOut[40]: % M! [% V: B: \
    0      [1.0, 1.5)/ p% B* w& L! H" X8 I/ u
    1    [1.5, 2.001)! P: l- k# ]. @# ~4 _9 R+ ]2 A/ y
    dtype: category7 G. s5 c; c! F; z. D9 J
    Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]3 O7 k7 `! [2 C$ X

    ' l7 o. c; A6 u3 Q& c- V/ X5 }- q4 ?6 U; p- N. b" X" S
    # bin传入分割点列表(使用`np.infty`可以表示无穷大):% g1 Q$ ?" u8 C6 V
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])' N$ @: H3 M) q% h4 t7 V
    Out[41]:
    ! M/ s; D( E' `* K' n# E0    (-inf, 1.2]
    4 G- V. {, y0 x  l1     (1.8, 2.2]
    1 l+ _" T+ p6 z. q5 V# r- Cdtype: category( y# U! ?8 I- F
    Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]2 {; d- ?# t. z9 D' Z$ }
    ) E+ m5 \7 p; u+ F& j
    1, a3 N% _# a* W% N9 H0 I
    2
    0 k3 g2 L4 L$ k% O/ Y% K3+ p9 E7 V2 G* a- c& d. e2 T
    4
    8 F6 f6 ~- t7 l4 }5
    / ^& g: d: x0 X# k! u0 D. m* G63 ?: k# B6 m' Z5 P9 s. r# b
    7& p3 I% i4 z/ ]' W
    8% h9 a2 w* v+ g
    9
    5 r- v& I: P/ d6 M$ F- Y10
    4 z  k# u7 H1 R' k119 N8 G. O$ W4 x3 V
    12
    2 @; V, x8 ^+ ~% _" s13+ ^7 F- Y1 t9 |0 w
    14- u4 s- Y7 b: s  {0 P
    15- _; J0 o8 r( ~- G
    16
    9 N* M6 V4 D! B- [  I9 V174 G! C, x. C6 p* o% M4 U
    18
    7 L% q2 c9 Z1 Z) r' x, t19
    + Q; ^( |% G; j0 }206 x% D* [6 z/ ?4 l4 {. Y2 \( c
    21: E# d& R7 S4 f
    220 M- }0 Z  k, V% Q+ u: e
    23
    : P; o8 K+ W  e& q, P' I24+ i+ |0 ^( J2 ]8 H9 o
    25
    ( n0 n) _3 }7 vlabels:区间的名字: p8 U/ P/ r% G
    retbins:是否返回分割点(默认不返回)
      d5 X0 g, L! l: ~  k+ E4 \& ^默认retbins=Flase时,返回每个元素所属区间的列表
    7 J  P* C8 Z3 E4 wretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值2 ]5 c1 C7 t! x8 `4 h9 n. w

    % v, S4 R; y* C; `' ?s = df.Weight
    7 {& n9 O0 {1 T8 u6 \, E$ dres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)- F' X5 m- J+ F  d- e0 O
    res[0][:2]4 |- n" K4 K4 C5 u  Q3 \6 P" P0 q
    0 n/ o1 F3 b. P
    Out[44]: # Y+ z8 d5 S" Z
    0    small
    + C, J6 k) I! W# c$ z. _8 q% N% E1      big* a  K2 {- y. @5 z
    dtype: category4 P4 {! Z' t( r" S) G' C% `5 _
    Categories (2, object): ['small' < 'big']8 [% F+ V# i0 x* }$ x
    ; Q+ R9 I: Q- ?& [9 H/ w
    res[1] # 该元素为返回的分割点/ w$ L* w( M! Q
    Out[45]: array([0.999, 1.5  , 2.   ])
    . a. J# X% E/ z$ T$ m1
    : n9 s9 O3 ^! `9 X: d1 ^) p22 h0 Y" k' i% O4 v4 E4 S
    3
    ( \5 a2 \- f* N/ I3 j4
    % c! {+ X% H. b" f) o4 t2 K59 t- a1 ~" s' y# H' d
    6
    - d& ~6 m* g- A+ o1 M7% ]  n  R4 ^/ C
    8
    8 H1 l7 K( i% `7 t" q  s9
    ! H4 V! d; O4 X10
    ; d2 s; k2 E2 }$ c11$ k; M1 B: |8 d' j. l
    12
    4 ^, y8 U/ h7 s( E& t$ h1 ^3 J# uqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。# a1 U' v6 {9 S- N9 d6 e
    q为整数n时,指按照n等分位数把数据分箱& Z& n6 @+ M5 ]. J! w' W$ C
    q为浮点列表时,表示相应的分位数分割点。: o6 W) Z2 z. k3 ]
    s = df.Weight
    2 m* t1 y* |; y* e0 N. p7 E* k+ n( a- q# J; L3 n
    pd.qcut(s, q=3).head(), k& a$ \6 X& y
    Out[47]: + H/ E9 @4 v' ^6 p; n
    0    (33.999, 48.0]3 t3 Y5 ]; h& d! }7 G8 @) L
    1      (55.0, 89.0]
    % O# I% D2 k' n7 \. J8 j# |) T2      (55.0, 89.0]
    , R8 H  v$ Z* O. }% \1 _, y3    (33.999, 48.0]
    6 v4 Y$ @: h' }& R1 L4      (55.0, 89.0]* }3 q# T+ E/ E1 C6 v
    Name: Weight, dtype: category
    0 s8 u; g5 L  k+ v. r. KCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]0 k( F/ y* ?8 |& R) m* |( i

    $ U" {7 R4 O$ Z7 Rpd.qcut(s, q=[0,0.2,0.8,1]).head()
    & m4 A7 a7 f9 S% ]5 l) ~6 bOut[48]:
    6 E( N: A4 E- j& ^2 a$ S0      (44.0, 69.4]2 N7 V! P8 B8 \5 |% z. L
    1      (69.4, 89.0]
    6 A/ L: z" ~  l. l, Q( Q5 t2      (69.4, 89.0]
    8 Q# n1 d; \* M3 _. X3 V/ j3    (33.999, 44.0]
    + J, Z% q2 o& `; c" D4      (69.4, 89.0]
    & J- D$ e8 F& Z% t) nName: Weight, dtype: category, l9 d6 V9 V# ?1 T9 s
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
    . }& ]( t( O# A8 o8 b
    ! Z& \, U8 t/ M" u2 Z4 g1
    - W/ U5 E" Q1 e5 a5 F! L2 H8 B2  f1 @! i5 Z7 j. A: K% l+ }
    33 k- L' t" |) l  Y& X6 z; M
    4
    + w% B$ `3 [# r8 N# y0 M5. F0 m- F+ A2 V2 C: y
    6
    : d7 O1 \! f3 O# c7
    ) s6 m8 f" V. v/ h# I8
    3 Q& t. R% u3 {9 c90 K9 V; C: u7 A$ |* s( U
    10
    % P; h0 W2 W0 b7 P11: B! s% h6 D: o1 s, L% E
    12
    , r  q& J" V" k. d0 h$ L8 ?13
    ! J7 ~' R% m, |) s' N3 A145 B' u' y% r* x
    15
    + A& K& o, g' L, E: C16
    5 H; W) v5 D* q0 ]17
    2 F+ [& D3 {" _2 A/ r18  O+ N# Z. v9 ?: F4 a
    19' G7 E% \' j6 y
    20$ M0 Q8 z# N9 }" h# H! Z* N
    215 P- K. f  [- `) m4 y( w  w
    9.3.2 一般区间的构造
    ( L$ b: o6 m. a3 z  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。* g9 ]( V0 f& a5 B* R) L& b' ~- }3 M' J
    & z( L% Z( ]" ]8 h$ ~" z
    开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。' I* _  ?  v: P/ }: c5 p
    my_interval = pd.Interval(0, 1, 'right')
    ; @1 V$ r+ N- N: Q* c' v- z3 N( [( |# U
    my_interval+ g! g. _5 @4 }* @6 ?8 m0 n
    Out[50]: Interval(0, 1, closed='right')% t) o# n- b- D$ F
    1' o1 \9 E" w) B& `3 E7 G2 r
    26 X0 k, N; T7 |# i. I/ q2 u+ @
    3
    8 W, Z" k9 L5 T5 @, e4
    6 [3 y# t# {6 k2 G2 e+ e区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
    $ c% D$ n# `( I8 ?$ y; e使用in可以判断元素是否属于区间
    + c; C9 m7 D/ Q用overlaps可以判断两个区间是否有交集:: ?, `* c$ C( c5 y! ~* n
    0.5 in my_interval
    # E2 a% ?; e. A& J# Q- X3 Q+ H$ y* r. Y; U" z
    True
    " r" o; q3 P* s6 x% j. i: v14 w& p4 \4 e4 x2 d
    2% T: o$ s, X. K8 t1 B# I
    3
    ( z1 J0 s% y; {- L( M$ tmy_interval_2 = pd.Interval(0.5, 1.5, 'left')! ?0 l, b. J, F) q
    my_interval.overlaps(my_interval_2)
    9 Z# Z. q3 D8 |2 n4 t; C7 V+ i- }: ^) X; ^
    True+ D& _4 U5 o1 q0 e+ q9 K( Y
    1
    ( Z; v$ k, G8 n1 Y/ H+ l2
    ! X; N! `2 r  e3
    0 g0 ~. y2 |- O4 z' U) ^0 D2 s49 [! m5 G% {! ?+ O% L6 y
      pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:$ D+ o& y, m! Q6 c: X" N: h+ c  v3 }

    5 @& [* f1 R0 ]  c8 ^  p9 gfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    ! A% j  Z) {: H" Z; wpd.IntervalIndex.from_breaks([1,3,6,10], closed='both')2 a3 R" R2 _* w9 g4 r0 L3 Q

      F% p% t" _; G( lIntervalIndex([[1, 3], [3, 6], [6, 10]],
    + s  e) ^8 D; \               closed='both',
    ' }8 V2 r: w, \6 `2 @6 s3 A               dtype='interval[int64]')
    * k. r$ Y6 E1 q" g0 B9 f1
    4 q: Q$ ?& {& x+ m2
    ) A" p7 [# ]1 Q6 U; ]9 i3
    2 j: G5 T9 g7 E) A0 l3 Q4
    5 w! G- z' n' b5
    $ _) k- T+ G$ A4 cfrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:% A9 q. f* G$ p& {- }6 {" ^) o
    pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
    5 Q+ F% j% Z# o! I3 i" H- v- ]  I! k* }
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],8 r; G6 z5 N4 k3 M6 @: p" }5 M
                      closed='neither',
    ) Q/ o9 _+ }1 ?4 e0 \% g1 @                  dtype='interval[int64]')
    + N  O; N- _5 s1& |8 ?" M6 d5 e) n2 ]; @. L
    25 `: A/ E/ M7 O' r
    3
    # L) t5 X. M7 U# y8 P4; ?/ s  c- K0 g- N; H* G- e4 B$ Z
    5
    ( r' O" V0 A; \2 ~from_tuples:传入起点和终点元组构成的列表:7 ~4 A7 |( y; T; m" u9 u
    pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')$ b; ]* q  {& @

    0 J2 u! Y: m5 ^- C4 MIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    " b- B- d% p% b5 {/ F8 z% N              closed='neither',) E) B0 e& g- {8 v2 m
                  dtype='interval[int64]')
    9 d2 b  Q, v; m9 ?7 R3 ~1# F% }9 N/ Q- C7 B% n- s
    23 \# Z( N, {) I' X$ M# _
    3
    2 x) E, E% b. R. Q4+ Q- F# @1 A5 I5 L1 T) K" n
    58 J' C" m0 ]- L7 v8 S; E$ G( n# C2 m
    interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:0 s$ R% F7 @% ]( V( o: R
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
    ! [+ y( O2 ~) M' l! Y  o4 dOut[57]: ) N* ]( ^! F; k" U, f
    IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
    - F: I+ B. \) J3 Y4 H) f# w% P              closed='right',
    ( F/ n* n9 o. n4 }5 k, X              dtype='interval[float64]')
    . D3 P4 n. H2 s3 {& _) A: p, P( z  V  I& L' \7 J
    pd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度% h' ?  }! B7 i/ p
    Out[58]: & U; P6 D" N2 ?- I- ?
    IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
    6 O$ w$ {6 P0 ~1 K7 B              closed='right',
    ' l  u6 k6 z" ?  N( Y              dtype='interval[float64]')
    : [5 ]7 p$ z2 z4 j1 w1
    : B8 V: S0 x+ V- d( G4 c4 i  `2
    9 |$ c* h  Q2 ]/ V- }! t8 M+ J3 ]( P# [3
    ' y8 B% P; I6 e( `/ Y& V# X1 H8 x4
    5 @) @: ~( i* F; r3 e% S$ t- n5' K1 ]9 G' n, B3 ~6 S! K; T6 g$ K
    6
    ! |9 x7 {; s* C  B7# c& b: f% _* E" a2 S7 @. r! g
    8; o/ {6 |+ C1 F% s
    99 q) b& B2 J1 W3 O9 |) B
    10
    ' o. ~0 p, K$ [: {5 z1 v2 k113 J) R0 n8 t6 l9 J" s  `
    【练一练】
    ! f' O. J. b" l: K2 k8 ^" T  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。- C* }1 j  y' B
    ; F( f0 F; L& j. F  E9 {
      除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。! ~  L: `( Y) I* ?! P' B* _

    3 b' n4 {& C6 `0 O/ Vmy_interval3 X, f  y5 j$ S8 }) S3 O1 s' \9 Y
    Out[59]: Interval(0, 1, closed='right')" Q5 {' ~# o9 g! V% V+ j

    4 ?; {" K2 d* i/ u/ p  W* bmy_interval_27 E7 G0 |: h: n  A6 d
    Out[60]: Interval(0.5, 1.5, closed='left')
    3 h# K/ j- w" X& v) P% ~, O2 L2 [+ [3 Y) l" j. |
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')$ `. \3 i$ z8 L1 ^1 s$ Y) u+ U
    Out[61]:
    * H) z/ ]8 T; {, v. pIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    % }* l0 K: B2 ^1 H  y7 m' p6 {& y              closed='left',  ^1 ]( Z3 J; N# c. S% I
                  dtype='interval[float64]')
    - j0 T& }) D. ?& \1
    # E0 \% K2 Q# f; b  ^2$ B+ G! _' m: B; T$ q
    3
    2 _' O+ V) T- ^6 K+ E/ ^9 A0 I& A4
    : S" Z- C3 }: C  b) j& S# R5
    6 t! T' W8 U( J6
    9 _! n6 d+ Z* a* M) S) U% D" @' B7/ T1 }1 @( J6 D- m# I
    8; @6 b/ K' e" ]  t* D- D- `! t1 P
    9: ~4 M/ Z6 x0 X* p1 X
    10! r/ K2 g5 ]6 }7 V. e) ^- U! A
    116 B) k% v5 N; |: e$ \
    9.3.3 区间的属性与方法
    , x9 U+ r4 z2 V) @: }  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:- ]7 y. H; H$ d

    / D, Z4 `  v" n8 p7 Ys=df.Weight
    0 g, P/ n* \0 p( qid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示6 b: J4 W7 O# m# Z" T$ j0 d
    id_interval[:3]
    ; w- z- n" T; O' C, |( f/ W; w: f9 O1 b) K7 K5 y2 I
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
    + S# [* C8 F  n                 closed='right',
    % K% ~4 f( E% J1 g$ l                 name='Weight',6 v$ `. ?: R" e0 Y6 I
                     dtype='interval[float64]')) S1 p6 A' f, I
    1
    . q5 G# ]+ h8 X' s2. K2 y' [  I* Z4 i: u
    3/ r6 O/ G, e# s) t- t" \) m
    4
    " X1 Q# K! M5 U4 R9 W5
    . q, |0 ]. {8 D) n' h6
    ; r! U- z$ g  O, I7 b7 v7
    ) p0 D! D: Z* f# k* O$ k  w# @0 e) b88 G9 a# [0 m# l7 M
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
    & T: l) n8 q7 D9 x5 A7 H% ^! @" @id_demo = id_interval[:5] # 选出前5个展示
    . l2 ^3 q6 n- S; |0 w8 s7 o. u4 v4 `# K8 G0 l* m) h! W
    id_demo, }; k* Y0 i( }. a- s0 B" A% N/ n' L
    Out[64]:
      t3 H2 F: E% h6 W; EIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],$ ^6 Y/ |5 {, `& J2 Y8 ~
                  closed='right',$ e3 I* j: w0 o2 u; D" m# B$ e
                  name='Weight',3 G! T" J9 P: B* D& @
                  dtype='interval[float64]')
    + D3 ]! ?$ m5 w9 g/ E% J+ j1 \# z& o; t5 Q( L
    id_demo.left # 获取这五个区间的左端点
    ; M# Q% K* U3 ?9 N4 x7 LOut[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    , q: ~) P7 ^1 S, t
    ! _+ R7 A* L7 m3 r9 wid_demo.right # 获取这五个区间的右端点
    3 m8 A  a5 u0 J/ Q8 AOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
    8 f) l4 m; c" s- F" L
    4 ?& X* p0 V( |( ?4 O6 aid_demo.mid- e0 [+ k  n: |0 ~  L3 P
    Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')& R, b! Q' j( b6 X  ^9 Q6 o8 g
    ; [1 F# W- s) ~- B8 L1 z
    id_demo.length
    # l- s( C1 G/ p7 d3 {% ?2 ?Out[68]: 6 d9 U5 ]; s5 Z. x
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,
      c3 O6 f+ r) ~- J8 p. g              18.387999999999998, 18.333],
    ; U' u: u3 K9 B) d. s             dtype='float64')
    " R# ?7 E; Z; [( x# _1 }; }0 b
    2 \3 s$ A3 p' q, Y" _& p  u1
    ; G! E. n9 I' M. g0 v1 ?9 D2
    ) O3 }; J* ~# w0 E3+ V: _  M9 m- y3 B7 h+ E, x
    4% O! C+ O  O3 x$ a( l" I
    5, P: D( G8 y; {
    6
    9 g# f* B, y) L6 v" z6 U7  c4 j9 a8 k$ b0 ?  j. _
    8: N' I8 p, T; a
    9# \  s0 g( I# j+ `0 d* _8 Y
    10
    . ^2 n9 G+ w) Q' J6 |9 ]) Y  F& T11
    3 ]( B4 W4 s! N& ^% O12
    ! M( a- ]+ X; _9 {13
    , E' C1 G3 J& s4 D& n3 [( }14
    4 i' G2 ^9 }5 b6 f- |4 m15
    % Z* P( g# Q3 `/ s/ A16
    " J  g5 Q1 k  w, f/ w3 m4 P179 k" h  y$ a$ l
    18
      J6 Y# A0 a4 X19- l. ?% t2 F  a
    20
    : }5 w1 ?1 k* x; u21  a; T* _2 m0 p7 Q' L( `' a: m/ K5 Q" q
    22
    7 k9 f0 d/ |, [6 o2 B, i# C; V/ |. b  }23, r$ d: q$ X5 w3 J# J
    IntervalIndex还有两个常用方法:
    - @2 q3 B' V/ Q9 b1 ^contains:逐个判断每个区间是否包含某元素
    ! k' D! @, k5 Noverlaps:是否和一个pd.Interval对象有交集。. M- i+ y5 O& f% R, k) x2 ?
    id_demo.contains(50)
    # V5 [: X+ H" _, ^; SOut[69]: array([ True, False, False,  True, False])
    # N+ Z! H  Z3 F: a6 L
    : P# O2 n2 P, ^/ x0 z; z% @id_demo.overlaps(pd.Interval(40,60))
    0 ]5 _* h" b( V8 e- i4 ?Out[70]: array([ True,  True, False,  True, False])( ?* t$ o) `6 L. w- D
    1
    7 o$ \( |! L' N. d: e1 i2
    & e- A( k9 X  O+ u9 R3+ P: E6 T1 _# y2 r$ F7 @( g
    42 w& G- w. m& T! e' o8 o) R1 n
    5  H2 l: L1 v* T+ |
    9.4 练习+ D* j, C: a! j
    Ex1: 统计未出现的类别
    5 ~# e, ]) b' j% Q9 H  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
    ' @; b4 i6 N2 G* o7 ~+ v  n0 w7 A2 V" e$ R# c; ^. V; o. ?3 H0 {
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}), ~% `+ U# g4 F; p" k5 D
    pd.crosstab(df.A, df.B)
    1 E/ W8 S6 D5 f+ z# j; r3 u& q1 ~6 ]4 H
    Out[72]: 0 D. `2 s/ Q+ s( P; c
    B  cat  dog
    ( s. v. b2 @- [) a& `2 d  rA          ! q; T1 Y* d5 w2 Q8 ?( @
    a    2    0
    * O8 s( j7 Y  o$ x, ab    1    0" Y* J  T, O0 A4 `1 h2 X3 G
    c    0    1
    : p8 X! Y2 Z/ c- D17 h  d/ s2 c. v) g. I% @! y
    2
    6 K* \: Z' [! s$ c3 \2 [30 d" |+ u! c; O+ P+ w
    4( }. B; F' \! i! \1 K$ U% r
    5
    4 Q+ E% c4 b7 O% c: @/ a$ y6
    % M& d5 o! z' ]& A# v7
    , J! t) y5 e6 D0 _& B8
    8 b2 V! o9 u" p) s7 U) D% G! }97 Y8 B) C/ t! O1 u/ V3 G6 J* c
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
    " h/ T/ {+ U7 f: S
    9 E6 ~/ L7 O. M$ t7 G* Ydf.B = df.B.astype('category').cat.add_categories('sheep')
    0 G+ E, d" u- J1 K  ]9 _0 cpd.crosstab(df.A, df.B, dropna=False)9 B) g. O# ^/ V6 z4 }# n( W% m
    . w, V/ L# _0 [$ y
    Out[74]: ! u! M5 u. ]/ n/ ^4 P# O0 f
    B  cat  dog  sheep# }; w, P" G( B$ @5 a5 `
    A                 
    % Q6 Y3 x5 r; I2 r- L# T0 va    2    0      0
    + Z  R6 F8 m2 E& K' ~, b( T6 ]. ^# Hb    1    0      0
    0 H& c# f0 k( G) z& l# ~, I" xc    0    1      0& s" z+ L6 D$ n! @& c
    1
    + s( p1 p7 a! g1 e/ R$ s2( h7 ^  J8 b; I
    3
    3 ?+ r, {+ P+ L% f, k- F9 w4
    2 u  A' a# j9 i$ o, z5
    ( b& L( O) C3 v' D0 A7 d4 @6
    7 s- A4 b1 j5 Z6 P' A; K3 |& v7
    7 j: x: {& V2 T# r8, V3 R2 H6 I. f9 b* h
    9
    $ a* `# L* o. s5 N请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
    % u8 q/ D6 n  g5 @* L, V
    0 E- G! u$ @3 nEx2: 钻石数据集" ]) _7 Y+ y" O8 Y$ W
      现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:  F+ |! K" X! u  ]
    ' b- t; }& x0 Y: }: W! d1 N
    df = pd.read_csv('../data/diamonds.csv') ' J/ s  N( K% g) Y2 D
    df.head(3)
    ; `* w7 b! D- n/ \) a
    * ?+ M. T: x) O/ d3 ?! TOut[76]:
    2 Q4 Q7 [5 B* I5 j* B+ \, H2 y- q1 W: r   carat      cut    clarity  price
    . N9 U& z2 F! n( F& d, t# m0   0.23     Ideal     SI2     326' j) H% A3 }9 n" Q. H- f
    1   0.21    Premium    SI1     3261 M% n/ Q0 w# E% c8 w' s4 L6 r
    2   0.23     Good      VS1     327: c% M+ h! O- Z
    1: h8 ~9 n( X6 K; j1 O- ?3 k3 z5 G
    2
    ! A- o$ W8 ^# l2 h' v) [3' t/ d: z, K+ {$ y
    42 \- z8 d' I$ k6 g1 o1 \5 \
    5
    " t6 z+ v4 ^) K4 r' n! Y62 C/ |, E- m& a5 e) U# `
    7( v; E" ]3 y) O: p
    87 d( s; n9 `9 u, a) c. i, u; j
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。9 c# {( P" M( F4 _( d. z
    钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    8 q+ L1 O5 ~/ ?( A0 a分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    # Y* {, [9 t- O" u: m: x, _# F对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    6 u1 r2 m7 o) O2 T7 |( b第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    ) O$ m0 t, Q  S对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。; C3 b. k8 x  s' W
    先看看数据结构:$ m6 I( h" U& F( d5 v
    . f7 A0 A1 f' k! _* ]! q
    df.info()
    ( Y. q( n/ I5 X; ~. W! \8 LData columns (total 4 columns):  F  t2 M* ?4 ~# x0 [# m
    #   Column   Non-Null Count  Dtype  * a: l; _$ T  R- j5 S
    ---  ------   --------------  -----  / h1 u7 U8 ?6 B/ g1 {
    0   carat    53940 non-null  float64: s  M: [! r6 M
    1   cut      53940 non-null  object 4 l3 E4 T6 y) f" u. o3 b0 [
    2   clarity  53940 non-null  object 7 @/ C6 n% X7 }% k" P
    3   price    53940 non-null  int64  3 e; x7 W$ V& f! a
    dtypes: float64(1), int64(1), object(2)
    ! ^/ K% l' w. x, X: A19 z$ A  E" x: v7 {; I: U' m. N
    2, x: i6 m0 v8 A
    3
    $ H$ Q" z& j* J) M8 s5 D4
    9 q1 T: V9 s! Q# I% X7 \5
    ) d* }) P  v4 }! @% h' p, i! l64 ]; U1 w% g1 K) M* }8 m/ q$ R
    7
    7 ?) G; X) [) i. J% I7 Q: q- W8" i  i& Q/ W$ _- l/ A, o3 A
    98 Z. @* ^' {2 q5 Y8 i
    比较两种操作的性能  f$ m- r# c. t% h; h1 E' G
    %time df.cut.unique()
    7 |" Q% ~8 i: |3 ]
      r- Y# c) }, Y) sWall time: 5.98 ms
    1 T- p( w" q' yarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
    ( I- L" L- b! E1  Q  A2 A; w! q! }0 H
    27 T0 j+ J2 Q0 Q, e" g
    38 @$ G% K6 ~# {; n
    4
    $ P7 G1 [3 n# X: v" g, N%time df.cut.astype('category').unique()* T: ^: n# y) ]1 x8 l
    4 t+ T3 ~: v  Z0 {% ~! ^; S- p
    Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms
    : u- e% V7 a/ n. \3 s- P+ G! l['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    2 G: {1 X. c4 k( W6 ?% Z, M6 ~Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']2 ]7 t8 E2 B/ u6 K+ _
    1/ F$ |! o  {; X& t* l
    2
    ' C+ K9 X& _2 e* d5 L$ o% m3
    ) A4 S# O% N; z( v4
    ) u) _% S  W8 I7 A5
    " t* W  ]+ E) ndf.cut=df.cut.astype('category')
    4 B- G0 a4 S4 F%time df.cut.unique() # 类别属性统计,2ms
    + \9 L+ y4 |. P1 `& P- f
    * V5 I' W  I4 i9 X1 J$ SWall time: 2 ms1 O) l. Y% k2 C" w
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']* v5 Y! [2 `, c! l2 Q. |2 Y+ \  J! B
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    2 z3 o& Q; J& r% z3 p! Y1! ?5 a0 w* z5 z4 L
    2
    - f! w' P7 D* r! M3
    2 [# `& ?2 h. d4
    ' t+ D$ A6 ~% f4 x) F8 U5" [9 h8 I, d$ M$ b
    6, [' M  O+ {1 U, C/ _( h9 X
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    8 l6 F! f% `: a9 r9 }* I9 O* L1 ?3 Fls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
    8 m4 q, Q* D6 W* E9 _6 Uls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    4 x) `# R/ V, C1 |df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    2 N* P* U9 _+ U* P% b0 D( |9 tdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)# x1 |* ^+ c$ m6 A

    ! P1 j' X: B; U  Udf.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    " k3 z* e, b: i+ ]* W9 G1 Z/ E
    7 ^6 H. E5 G4 S2 ?6 {$ }/ W4 a        carat         cut        clarity        price# a0 v2 Z; z  ^9 I: ~, v+ X
    315        0.96        Ideal          I1        2801
    8 _5 U6 p; k- n* B9 O; m535        0.96        Ideal          I1        2826
    6 e# ]' a0 i2 C2 v0 L' G551        0.97        Ideal          I1        2830; z7 z1 b' R! a1 L* t6 |: n- \0 d
    1
    8 O$ x5 [3 K. L  t  u& l2
    * }3 [' @6 f( A+ o3% {+ g" j6 Q2 W4 F0 i) o8 X
    4
    % b: V1 U; y# o# d* @51 W" E# n4 n; Z0 u. l
    6- j" k! ^8 t" B) x
    7" P2 @5 H' s8 U
    8
    2 x5 e" H! K& B% X6 {( G9
    + k9 X0 l7 R* Q5 q4 f5 z$ I10
    # B; v, y  k; \0 j( E# g5 j- L11
    & f. S& `# R1 F% Z; ?1 \1 [4 [分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    ! b7 m) D" X- p# 第一种是将类别重命名为整数
    ! ?+ l8 M" Q1 \0 M0 bdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
    # j& V$ v& y2 m5 \+ z% ]% udict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
    , g  K$ Z  W2 V0 k- C- n* ^
    - \7 Q# d; l0 ^- sdf.cut=df.cut.cat.rename_categories(dict1)* A  J* E7 d; I. N) t
    df.clarity=df.clarity.cat.rename_categories(dict2)+ B  Z/ U& |4 R3 o$ q' u
    df.head(3)/ u4 }1 h+ ~2 T1 b) [6 V. T, N& I

    9 Q* w5 x) A# ?; B; L# g        carat        cut        clarity        price
    ! [, u8 q. j) F& `/ ^) ]0 Q0        0.23        0          6                326
    8 k/ b- f3 |, \4 S7 \* t1        0.21        1          5                3263 S" s0 C9 P( Y+ M9 {
    2        0.23        3          3                327
    8 z! _3 w6 o3 r: v% I' O6 t1
    , _, }! s: \1 ~4 |" G: o+ `26 v4 b7 C; T" I/ H/ }+ |* n
    3
    ) d  W1 _6 I5 b8 ~' P, E4
    # |" [5 @$ h' |& K3 ~: T5
    ( l. A! F& z9 X% B8 h! ?6
    , m1 w/ N* N7 l" d72 J8 G0 `/ t8 u6 ]
    87 C, l7 v5 x3 p
    9
    4 a- S" d1 J3 N: Q+ q# Q10" _( k* P$ T/ Q: S4 u% e: b- |
    11
    1 R0 x2 m6 I" @$ l8 s0 K121 N0 J2 S' J3 X+ {/ A
    # 第二种应该是报错object属性,然后直接进行替换
    ! \2 h( {$ R) M, l! |: gdf = pd.read_csv('data/diamonds.csv')
      Z; F5 f9 r8 i+ ?* `7 Q8 bfor i,j in enumerate(ls_cut[::-1]):
    6 _3 |7 @" ~! }# D5 C( B( i    df.loc[df.cut==j,'cut']=i - Y, m  v) f4 Y/ Z! d

    $ K. q2 O8 d$ X. }for k,l in enumerate(ls_clarity[::-1]):/ D4 o& _( K. j$ A; v6 i0 F
        df.loc[df.clarity==l,'clarity']=k& j0 g9 u, e0 H9 a. z4 v+ u* G2 K
    df.head(3)# J; t* U9 N0 Y6 k) i5 O

    8 K5 m  Y' |1 S4 H7 x4 o        carat        cut        clarity        price
    % s- ]$ e2 ^+ }, ]+ ^0        0.23        0          6                326- x7 J9 c/ Q+ a+ w& A8 ?' F: S
    1        0.21        1          5                3262 I" P0 V( ?) X( e
    2        0.23        3          3                3270 j' n4 D3 I3 J& |# D, }% c
    1! a* q& w" F5 f9 I( H0 C
    2; f/ V5 o. z  T0 O4 r3 Q
    3
    2 R5 [7 d* H+ d4* e  @- \. R# Y' k+ x% \6 o0 C
    5; Q+ c. a6 E* S9 P
    6  n- f+ [9 V4 g) S0 d
    7) G: ]* S7 J# F3 B, m( v9 M0 x$ c
    8
    3 H! q0 K9 G' I$ J, M9 Z( d98 c3 x6 u* h5 G; H, s2 i; v# T
    10
    # b6 T9 [, o) u. J3 I! e3 I11
    9 B$ @4 d* z6 L% ^4 F* K12
    ) S5 Y0 l( `3 x6 X, H9 _13
    ; y5 D: `! O3 L对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    - r& Z+ d: g! @# V6 c9 _9 R0 K) p% A# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点1 o% X+ u. X2 w
    avg=df.price/df.carat5 H/ t: v- q3 l

      T" `- I) V  c1 m0 d* jdf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],, w: f! d* N3 T, |. b" j3 a9 Y
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]1 j& h: X9 f- I9 ^
    / q1 Q9 G) }# }
    df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
    0 i" i* Z' U7 H0 L                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]0 S; _; c0 K" ~. N9 K
    df.head()
    6 A0 s$ o9 m' U4 b/ K- D2 U% x3 S4 @1 n/ V4 Z6 @: F/ ?
            carat        cut         clarity        price        price_quantile        price_list
    5 P0 u$ |( n+ I* n% V0        0.23        0                6                326                        Very Low                Low; F) n, ?4 ?$ _/ H; I6 _$ ~
    1        0.21        1                5                326                        Very Low                Low
    4 l8 S, ]8 f1 |5 Q! u2        0.23        3                3                327                        Very Low                Low: I# S( u2 M: L1 Z- d
    3        0.29        1                4                334                        Very Low                Low0 I, A+ F, p) P' D
    4        0.31        3                6                335                        Very Low                Low                                       1 T& r6 E( V+ Q& M* Y: b! f3 ^8 z3 \
    % G+ ?! s/ Z% m5 Q8 Z) D  E0 n! ~# \
    1, z! R" w- X; z9 L5 M8 z' s- [
    2
    , i, s2 N) j! p' z+ Q* X* n1 x3
    , Z3 F2 v7 K8 X4  Z0 K6 t1 f+ C4 P! L! p
    5. S+ N, D* r, V* Q9 q/ c1 R
    6
    ' i7 P, J  Q# m9 E3 H8 P7% I. ^/ y9 J  W& f* s; ~
    87 \8 u+ |' b& |# g
    9. o' e4 `2 }4 v0 R6 `
    105 v6 \" x, y& w9 S9 M6 j3 S0 r% P0 T
    11
    8 g- I3 G! d: j9 j/ [3 W127 R( R& P. z2 y: |# `' O
    13
    $ d- x9 t6 ]" L' r7 s/ x14! `+ Q0 X( q7 y. R& a" {
    15
    ( C, Z7 X. M- @$ y' Y163 ~+ M: }$ x8 E8 _9 t7 l3 f
    分割点分别是:
    , T/ N" H' x% L7 p  X2 I( I
    : r) B: [) }7 Y! f% Q0 parray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])
    - M/ ?8 f. A* w$ y- G8 ~array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf]), h2 P- q5 w6 H1 o0 q( I- i
    1
    * j9 F' U  o% c3 e1 k9 \4 s2
    7 p& N8 i( y$ P; ~' i第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。9 v; C6 ?' g- [  r
    df['price_list'].cat.categories # 原先设定的类别数
    / g% f& O& J9 Z) R3 ZIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object'), z5 H8 J0 U/ c- d9 G

    / {5 f, ^% j+ F2 h2 L) N* u6 ^df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别
    6 [5 n! k% C: [7 {Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现( p! _! \9 W  E0 E7 X5 |1 b# |) ~2 O
    13 X( O) `* @% ?
    2
    * P: g  V' k5 e. S# Q" n7 e9 K6 q3/ b2 A) Q3 I% {' z
    4
    + H0 I  [6 i9 v% W5
    ' y7 B4 d; k8 A: X# h# F6 a. s! a$ ?avg.sort_values() # 可见首尾区间确实是没有的
    + _2 ^) _2 Q, N$ p% G' d3 V31962     1051.162791* A. V* A* N1 y$ J0 u5 g2 x7 C
    15        1078.125000
    4 F7 M+ C. z$ z  @9 T/ ~4         1080.645161
    5 B- [6 K+ w$ `4 t" ]1 A28285     1109.090909! B7 J  U3 b8 |8 P
    13        1109.677419* f7 n$ R: J7 K4 c/ ^
                 ...     8 z# K2 O0 n* r
    26998    16764.7058820 |; f2 z6 V2 E
    27457    16928.971963
    1 Q% e! c& R" a5 X  @27226    17077.669903
    + K3 ^0 D8 A4 s/ F8 O( B0 \27530    17083.177570
    ! `! N4 k4 g$ t8 j1 p27635    17828.846154
    2 K. y; b4 A0 ?; R: i$ E! G1' d" W7 o! X! A8 [
    2
    6 l/ [* k) L% T: Q" N33 |' y4 _7 O, a3 f" E  p) X
    4& Y. [* S. [, L6 S. ~: p: D1 [& D
    5+ b7 A+ t' e" m
    6
    7 I& x1 s  F6 @5 c7! g' Z* M  q4 [3 [, v/ j
    8
    3 U* T2 E! v* @2 R3 ^( _/ w) R9! l* _9 u5 I; i8 f
    10
    : M8 H3 h0 }+ b4 g* m9 K119 P3 X) g4 ^6 n+ y
    12
    & s( W7 ^9 n5 v3 q4 A0 i对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。1 d+ Q8 o+ ?4 ~! N: b8 j
    # 分割时区间不能有命名,否则字符串传入错误。
    - r1 r/ O1 U) r- ^( c! P9 G/ h: pid_interval=pd.IntervalIndex(9 F- N  e6 f- r6 s
        pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]& p$ k- R4 p' k: n
                                )
    & R# i% e6 i0 l2 H* U4 ?; tid_interval.left$ @( N# M! m! z& \# q
    id_interval.right: Y- O- i3 N" q5 C; @# J! z3 A) w9 c
    id_interval.length                           
    1 N  R9 g1 r+ G5 q: d# E% ~9 z1/ H) I) c. B6 ~1 k" Y0 t
    2
    + |! y7 k: ^3 v3 Q3
    2 n: D' N( c1 _4
    - |0 U7 ]; }; u5 S. m4 Q$ `5% O3 {# [8 x: I; n; X7 Y  |5 M
    6
    ; d7 o. D3 x  S3 C; D! L' b7( e6 z( m8 V! J* f2 S
    第十章 时序数据
    7 S, z8 `$ j- K" N7 O9 W2 q7 fimport numpy as np' C) g# f$ V, `7 H3 k! p/ k% z
    import pandas as pd
    3 s1 `' b9 T! p, Q1 D% S8 }$ c1
    8 ?, ?! \8 t9 b2 w" g# q* V2
    ! h" }$ ~* f4 e' X) Q* Z( W1 W& t# a9 B! H5 W( c+ q/ _! ^

    : ?. d" G4 p- F" H4 }3 D10.1 时序中的基本对象  c& O2 m' G  |( Y
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?4 b6 p* q+ N9 y8 C+ K2 G

    4 b3 {% S7 E. z9 L4 Y/ T' n# r会出现时间戳(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的简写。
    * P5 f5 m' G8 c' N! `- t* L& A6 a/ W2 }5 s8 m% ^
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。% W2 G( O- O$ G( e

    6 j9 d- q1 \6 A9 i6 j; m4 [. q会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
    # A$ ~8 f3 U# N- ?1 l
    0 S6 @8 r. Q, I8 _9 b0 Z会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    ; Q1 H0 R2 v' a- a" S
    , Q4 [$ q5 `! {/ G$ s  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:# f0 q& r8 Q% @2 w; q$ o6 x5 _' \
    * `0 ?% k1 H% |7 j) X/ Z
    概念        单元素类型        数组类型        pandas数据类型
    / @' c/ b6 V+ M& sDate times        Timestamp        DatetimeIndex        datetime64[ns]
    ) }4 W+ g6 w; j8 T& F3 jTime deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    9 V5 A4 k) L% \$ r. d4 l% QTime spans        Period        PeriodIndex        period[freq]1 N6 o( Q  f3 m: A4 c/ L
    Date offsets        DateOffset        None        None" ~/ w0 h; U: J& M% X
      由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    7 T: u1 y7 J2 |* e8 W; E$ r- ^; X2 d3 c* {, D; b
    10.2 时间戳/ a& k0 z# L& m! X5 `
    10.2.1 Timestamp的构造与属性
    4 E, n$ o. f& v9 S* ]% M+ u* o单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:/ M* q2 }+ Y. X7 }2 b

    ; @* f" |/ ?  |4 A3 Uts = pd.Timestamp('2020/1/1')) {8 I$ y0 M" j3 u$ ?: H. F

      ^$ T' M9 A/ i- M0 i5 Sts; j) v" M: H/ {7 F
    Out[4]: Timestamp('2020-01-01 00:00:00')
    1 H1 p0 j! H' c) f
    ' E6 `- h0 _+ }# g- b: n& @ts = pd.Timestamp('2020-1-1 08:10:30'): D; d" V+ o' J4 d( s& ?. Z7 i

    . S" L* I) b3 @1 h$ T! Qts
    2 T: B3 k' P# V2 h1 e" q, POut[6]: Timestamp('2020-01-01 08:10:30')
    ) g7 J! x! Q# h2 A; ]+ Y10 w2 X! ~, y# c* T) L+ V
    2
    9 d8 F* |$ i* U9 f" O3
    . X; [0 `3 _! A$ k4
    8 q- n) S5 l: ^3 I$ j3 |( J58 U5 f" `9 {1 D* }# ~
    6
    ; U) \& _1 o1 _. p' N- B) M; z70 y6 v8 z2 o- d( q9 n$ P" S
    86 B: m  S' \8 ]2 h
    9' z$ p& a2 m* t+ J* e5 v+ E2 \
    通过year, month, day, hour, min, second可以获取具体的数值:, b' |% @5 ^# e9 [) P+ p& q3 d

    7 o1 }9 g3 B+ x( f/ g$ ~ts.year9 d1 @5 R, H8 [
    Out[7]: 2020
    ( ?3 B; g! \  y* r5 E6 N- _( ?9 C: Z5 t% F* a6 u
    ts.month6 s5 V5 ]9 M: h. u, W" d/ m
    Out[8]: 1
    1 L  d1 t, x1 X6 Y
    0 Y7 {. F! L1 ^# }& V5 nts.day9 z) S: k2 u+ Z, w% {
    Out[9]: 1# y7 Q% `/ f8 w- X3 B; E- u" W0 C
      r& O' Y. L2 Z8 I& y
    ts.hour
    1 B: |3 m1 K+ O  b. w5 LOut[10]: 8
    ; m* _. f4 w3 R3 X/ [$ v4 [1 b9 A
    8 p# Q0 F4 s/ [) j7 Y# tts.minute* n/ _+ a* n7 \' {3 o7 w
    Out[11]: 10- a* J# [2 }1 d% e- ^0 x7 A8 G
    & Z$ e, j  j  z. x
    ts.second
    2 ^5 r" J$ ~3 \! A& `7 q* zOut[12]: 30
    0 L' j9 z0 S% ~6 W5 W( @
    ) V2 l. [/ E9 L* z- b2 b+ d1
    3 A9 t! V9 `3 D3 P! S5 P2
    / B6 K; Q( D* g7 V3
    7 t" I/ G! V2 W7 O6 ]5 }0 Z! f9 w4
    6 m. K+ J; P& Q5
    7 D% v8 d& f! u% h5 [! Z+ i6 v/ q6+ b% N9 G5 }( U
    7- e# q8 G5 m; a3 C0 c
    8( v$ I; g; H3 B% S
    9
    - P' c- T+ X# G8 i* ^  r106 ~6 M% N6 w7 w, K+ O
    11
    , l4 p* }9 w% b' K& }5 y12* m- J2 ]  ]8 L! d
    13, P3 }( F' B+ `! O% g7 k) P' s) b
    14
    ! U, t- E& d/ F  ^. }, p6 y2 p15
    : j$ L! h* G5 D" c16
    0 Q5 ~2 H( w5 y; M17. F4 u( u' ^$ V( Y# K* {
    # 获取当前时间" k% E, H- L+ u+ g
    now=pd.Timestamp.now()
    4 f1 p0 c. U* E' t: }4 k2 |1& c" x! Z2 f" b
    2- @6 }: h& J' A6 B2 L' S
    在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:( r" n6 N4 `- A+ O) j) v; f/ L' J$ `
    T i m e   R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)
    5 T; j2 s; G. q8 D  s6 `# U- yTimeRange=
    $ b* e' q( D" D! s5 O10
    7 V0 m$ M( {& a: d: Q9# w0 L$ }" k1 r, j( c. q
    ×60×60×24×365
    $ l1 |; r2 x  I6 C$ A2 ! p6 u$ U! F& g
    64% m! M9 R8 G7 p8 }

    5 X. u3 H4 X3 H" W) u! D" }2 n: f5 y$ X
    ≈585(Years)4 Q/ Y* A8 e; q4 I, g* a  T2 x
    ! [/ o+ V, @+ v2 }- F1 ~( ]
    通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:7 u$ I) Z; _( f! ^6 ^. y9 ]

    $ x# t+ o8 Y- z" O" x$ t1 zpd.Timestamp.max6 l0 x* b/ Z; E& K
    Out[13]: Timestamp('2262-04-11 23:47:16.854775807'); w- S6 k+ t9 _% n
    , ]: [3 I$ b  z( T. A. G% m
    pd.Timestamp.min$ B/ E/ R8 ~2 \5 R8 s- X7 ~* {
    Out[14]: Timestamp('1677-09-21 00:12:43.145225')3 B9 R9 I5 {1 {7 O* m
    & @" m" |) E/ v3 [: p( x# {" s
    pd.Timestamp.max.year - pd.Timestamp.min.year1 I; j/ D- h1 Y$ w
    Out[15]: 5853 t* a* g5 x( i3 w& w/ c
    1
    $ |( ^! m) [" d! `- ^4 R21 @+ z+ _, ~# M/ g
    3
    . |& P7 h/ c. s1 O" ~4 B4 x: x4
    + k* \4 Q6 x" ^$ S1 X. K: _* s5
    0 t. V9 ~. ^, o) N2 G1 `1 g: Q9 X6
      X: |, D: U- f8 N  X76 r5 W0 `- \" l, e' n+ r4 e
    8
    1 k& A2 u7 L# s+ Z10.2.2 Datetime序列的生成. X3 t! \. _2 E( ?- D3 ~
    pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
    $ h5 C! J! |% q* o* V; v! U: p                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
    , B& L1 @; D  {* L. Y$ }! y( y1" I3 \$ O3 W+ |$ }( K$ m# [( c8 m
    2+ S5 Y5 ?6 U" C
    pandas.to_datetime将arg转换为日期时间。
    $ q8 n& b2 b% S+ x
    & A( A4 Q$ H6 o7 B5 Sarg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。" B1 F: c( _0 u3 P; d3 g
    errors:
    0 b9 f) e: r8 X+ X4 Y- ‘raise’:默认值,无效解析将引发异常" A; o/ d3 a& `9 w" y2 d- j) `# q
    - ‘raise’:无效解析将返回输入
    7 H  l8 a( S' ^  t; y- ‘coerce’:无效解析将被设置为NaT( D4 B# E$ j7 [! R5 F
    dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
    ; f9 p5 X0 b8 N* Z2 ryearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)# W8 a( Y+ V/ U1 z9 _
    utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档( g' h# b4 D) F- e+ t0 o
    format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。1 m" l* A- n) {* c. I+ s# A
    unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。- B( s( t; _; y8 P6 t
    to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
    2 g' K; a3 m6 I. l7 L8 opd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
    7 k' y" n- V& N* }7 v
    ) i/ p7 l1 N, B- I+ m! R7 q5 iDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
    . c2 a* s+ j% C( t& b: p1  D2 i: k3 ?( u- p! [
    2+ t1 R+ s! _: M& o1 C- S0 c( @4 L
    3
    8 |/ j# l4 z& ^$ k1 `在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:: w6 f1 @# y* l1 x$ K
    0 B; N; r; S  A. q, N5 k/ j
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
    ! i; U& N0 U3 l, u/ d3 S. y0 |temp' Z8 l3 h9 J. ^" l+ p
    - P; N! N4 i7 n" W
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    % n9 C5 w  G- |, k10 L9 g: g9 w  N1 b* n
    2
    " c. D4 @, k8 ]0 k" |/ p3
    % @: v: o# ~. B2 A& f: @# C# L7 D41 f/ H' U+ v1 J7 u# d7 e; t& g/ o7 T
      注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
    - p. U# x+ I' D5 \' ^+ i9 ~0 e1 K; y
    & ?3 f4 u1 m0 |; k7 [pd.Series(temp).head()
    7 P1 x) d( o# G; ^4 T. G1 L$ w/ L9 r
    0   2020-01-014 B5 o4 a1 h% T9 O% C& \5 G
    1   2020-01-03
    - O# a5 I6 ~" ]6 C5 adtype: datetime64[ns]5 e! z( k8 |% O% V6 \8 L
    1/ T1 G& j/ A/ k" N/ M6 c) h" u6 H
    22 A; Z# s6 x. m  ~
    3' r1 i) ~) b7 W# n  n
    4
    ' ^6 c  p( T- W3 ]7 s5
    ( t4 ]  G. A( E; ^" y# E下面的序列本身就是Series,所以不需要再转化。$ U  T& m3 q* G/ ?% ?$ ~. D" X* c
      I* m9 \9 W* n* z1 c
    df = pd.read_csv('../data/learn_pandas.csv')1 L: |# E/ G' C( g/ B) ]
    s = pd.to_datetime(df.Test_Date)" S; ?8 @. Q$ u6 j4 w, K
    s.head()
    ; i. k% |) s3 l1 P" j: ]- g8 T! c2 o4 u
    0   2019-10-05
      X6 i. p9 P4 M' \1   2019-09-04
    2 Q7 \( @* g& g' [7 g4 L# j( l2   2019-09-12) p# m" O; X, J/ d0 o# \. z- e+ v
    3   2020-01-03
    ) G+ \* z+ M2 f: c, v4   2019-11-06
    # D* W/ ~2 a& S- i: v% o0 ?$ `( xName: Test_Date, dtype: datetime64[ns]& Q2 q5 e/ m* k
    1
    + w; b  W3 F, U2
    ' v% |, C9 u# ~% ^# |3/ D# x( C& k7 r: K! ^3 h
    4
    7 N: }* _( w' H3 h! z( c. u7 T) E# `5
    8 ^1 m5 @; N# \, l! \9 V4 E; w$ ]6/ n: r0 U+ g  t7 m' m  W. ]
    79 ^* b& [5 P. K+ ~! {( t
    88 A# w8 _: D, f" t& N
    9' G( y& O0 l- E+ i% W, n
    10
    5 R$ f$ _7 Q, o) l. ?% Z* P+ O把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:- B4 x4 N/ X3 J( r. c6 V2 s( }
    df_date_cols = pd.DataFrame({'year': [2020, 2020],
    0 [; y% g, B8 P# p                             'month': [1, 1],* x' C5 m8 e* A  F- t
                                 'day': [1, 2],
    ! _1 r% d. m/ i: l4 f0 J# D                             'hour': [10, 20],; Q. [5 g- {: Q" P3 y
                                 'minute': [30, 50],
    2 M4 j8 z; N! e                             'second': [20, 40]})
    , D1 g& t( v! o& Ipd.to_datetime(df_date_cols)( I  V* t  u; ^% p" v3 D  H

    : S: C8 I1 s9 W8 [) x' c7 E7 `( T0 g6 I0   2020-01-01 10:30:20# X! u6 r5 |+ r; f$ w5 h
    1   2020-01-02 20:50:40
    3 I* f. S' ~. o1 h4 |9 adtype: datetime64[ns]( w8 c& m; Y" _' ]5 u4 J
    16 o; F+ S3 r, M: f! a
    27 q( W; y  K  D! r1 _( C. H
    3
    . z2 M. i% q) h2 R' T; v8 m4
    $ C: i3 i: Q* r5
    . G1 F: d( p: x- g% ?& z6
    2 R* \  ]5 n. {, H9 r+ T2 O78 |. U6 j$ I( T( U! Z1 X) M
    8
    " l4 v7 J4 m4 h+ K( |9
    ' `4 f; `/ [# b10
    ) N. e# j, |- w7 Q& P11  Q8 y( J5 m$ R# j1 V. F9 B1 c
    date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    4 [* z8 W3 j5 d  @pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    ; T) J' W4 B8 E1 z* s0 F% JOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D'): m  I  [; b/ p( Z. e

    8 Y2 i! h+ x8 S+ Q& o) L7 wpd.date_range('2020-1-1','2020-2-28', freq='10D')
    ; d$ F; r) i/ `& D0 i7 kOut[26]: ! ?& E8 r! c8 x6 r' u2 B
    DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
    7 d; G# t" w# ]               '2020-02-10', '2020-02-20'],/ R. k( w) ^* q2 B' X7 F4 K: }
                  dtype='datetime64[ns]', freq='10D')0 H* h+ g5 [* q, u+ @

    * n* F/ c' ^6 n' ?pd.date_range('2020-1-1',
    5 F! k" X8 q; O- n' _+ o              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
    % V4 g1 H1 ]3 P5 @. h
    # Y* I( E# N" B% j/ MOut[27]:
    ) C' s/ E2 A' K- ]: g) U* s8 eDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
    9 w* m  J( T, r% v/ X# v               '2020-01-24 04:48:00', '2020-02-04 19:12:00',* n( \/ |9 |7 ^4 U4 R
                   '2020-02-16 09:36:00', '2020-02-28 00:00:00'],
    9 \& ?5 t' ^: e              dtype='datetime64[ns]', freq=None); v) g+ Y; {/ _- z4 ]( W9 R
    - T: `- t- {0 E" R' B: \2 F. G( C$ \
    1+ p. _& o& S& d" L
    2
    - }" ^; C) E3 m# X! u3
    9 j) y5 d: R2 `. d4
    ( s; J; z5 `3 G7 r/ I) y9 t5
    # J! _3 H9 G/ q4 J* R6
    ' R' q" z/ N/ d$ ]7; w1 ?4 ?, D6 h' q2 n$ s3 p- c$ n
    8
    $ {' @" \9 o5 r& q: |( D5 z9 g9
    & W# B) ~0 p/ y* a% k( i# a10
    7 R2 P" o) U6 ~  \: X# {& E1 E$ j11$ T3 I  g8 u, a- I/ p* n5 Q9 \7 Q; I
    12
    . N7 K3 s/ F; R0 ~; a0 e0 l13
    9 A5 m8 Q  n/ v2 w14
    5 {/ ^$ G0 s4 ~  d15
    5 S; [6 `( ]/ Y5 t& b# ^16
    4 v" L% k, r2 W/ O  ~) N) P% s% A' z17
    . |' i6 ^7 k8 L8 e9 T/ J这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
    & @2 q  n+ k7 t  @1 v6 z2 u
      ]% h7 Z2 O7 Y; A8 H' i【练一练】
    ' i* G8 Y- W7 a! k6 rTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
    / S: Q7 l) e0 F* G2 N" s, L$ {% H
    - y2 s' q3 a) ?! X; q: _- P2 Bls=['2020-01-01','2020-02-20']
    : U9 p5 [# f- R: @0 u  fdef dates(ls,n):
    ; U: N7 [; G0 f  s    min=pd.Timestamp(ls[0]).value/10**9+ b' @6 l* z( p8 u" Y/ a% D
        max=pd.Timestamp(ls[1]).value/10**9, L$ W# Q/ ~. L" u
        times=np.random.randint(min,max+1,n)* K# ?& x3 Y* X/ n. B
        return  pd.to_datetime(times,unit='s')
    % X$ n$ y4 M" @6 p7 P) i& V& f9 O3 @dates(ls,10) - P& F1 g$ P" V' j4 a* |0 w  x7 ]+ x3 M; I
    ' ~* }% h9 m& ^; O
    DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
    ( a" {+ Q: H3 |! n! q               '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    / u3 o! B) g. X) k               '2020-02-15 00:18:33', '2020-02-11 02:18:07',8 b1 A2 Q6 D! ~. y3 b$ t/ K
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',
    % R& b- C. I* l( |& m. t2 Z) c               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
    ( k& h8 N& G! d; l! n& [              dtype='datetime64[ns]', freq=None)
    2 H, i2 y5 j0 j9 [, v15 o! w+ [5 v3 g. a" E4 C4 r
    2
    ! L) o1 L+ p8 x3 u5 H% B  e5 x3$ r0 G& x) {7 Y
    4
    $ s8 y1 @5 H1 o5
    - }) e7 w# ~+ V4 {% z( E2 ?( l67 _# `! H$ _! e4 ~! X
    7
    ! P' t, x' J  O8 g# G6 l8" o3 ]* z0 e0 K/ J( m$ X( J9 [% ]
    9
    " S$ V* z4 A" }" e/ _& {: X4 P109 R( Q4 ]2 F- j
    11
    , B. z& p  |$ f: I) F12
    ) K% m# M& k) z$ l. f- h13  p; c& k1 p. q7 p
    14  X: I( u! Q3 O! n
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:. h+ h( k0 F) O: s
    s = pd.Series(np.random.rand(5),  V' x% X1 e% z  {" f' ?
                index=pd.to_datetime([
    5 _" u4 A& d! a% M' R                '2020-1-%d'%i for i in range(1,10,2)]))
    * A8 W8 D! C4 l0 l0 B2 G! t; H/ Z# P! z/ b

    4 r5 W* k3 S) M! u  us.head(): w# r+ k! {' g6 N
    Out[29]: 5 V7 x4 F/ p8 |. H  w4 g, n4 b/ \
    2020-01-01    0.8365786 j3 d1 J3 x1 C) n& x
    2020-01-03    0.678419. B* t- M, s: E1 P8 N' N2 V0 x
    2020-01-05    0.711897- p' Z& Q  ?2 A" E4 s
    2020-01-07    0.487429
      K3 W0 j; V3 ^. N2020-01-09    0.604705& f: W) k; s$ u9 [# k( G* l
    dtype: float64  ?" |* V1 K( F% F* M# Y7 w6 O
    % ?/ V& t- Z/ Y1 Z7 _- k
    s.asfreq('D').head()
    . l, m, d; ^" e& T; m: J% @Out[30]:
    1 ?5 B! R- L" W; `2 P  J# W/ x0 D2020-01-01    0.836578
    ( S3 {; b7 v! M2 |2020-01-02         NaN
    7 Z$ K3 a; x3 t8 m! l2020-01-03    0.678419
    & ]0 f4 X% H5 O6 n3 O( T) J+ z2020-01-04         NaN1 I. x- s5 w; D
    2020-01-05    0.711897
    . c% }2 n" F& `" ]. wFreq: D, dtype: float64
    " |# e: p7 Q# t3 p5 I, J) {& _
    - j- k% I0 r" a. E7 Ss.asfreq('12H').head()
    0 m. O& y2 I: P, \7 ?0 ~Out[31]:
    * H/ X- L: h6 S0 C2020-01-01 00:00:00    0.836578) Y' m" h4 u! D. a
    2020-01-01 12:00:00         NaN5 {* @- t& N$ D
    2020-01-02 00:00:00         NaN
    , e/ _# L! R; [& l* ~  w2020-01-02 12:00:00         NaN  q3 F8 B) Y8 U6 h) R+ Z
    2020-01-03 00:00:00    0.678419: J/ p7 J0 M# U, }( B9 l' a' p
    Freq: 12H, dtype: float64
      T# ^7 y) @0 Z$ Y3 ]- u$ N9 b
    : u8 Z- F( j8 H" N; O5 |4 W. J1
    ' _# P) H2 i! f3 D! Z5 j2
    & A5 O1 D# g4 \# V3
    - y2 M) m5 S+ l9 ?1 F& o5 `4
    " i! q, q, f, \/ o( s9 x7 E5& c4 o: K- r) u) V+ C% G
    6$ w) s' N% p$ c+ x) Q+ M
    7& B2 Z  Z. k1 Z7 H
    8
    0 ^2 M0 ?  |& y# Q, a+ s8 X# m9
    2 R+ R( N. ]; y" X' v$ ~* M% O10
    4 E' D6 Z+ r; R% d+ }) X11: v3 n. H4 V9 m) \
    122 I8 }" e. V& A2 Y
    13; L8 s7 `2 B% L- O% ]* L' f, \
    14+ E2 v  {/ ~, G- R
    15" S* h4 v5 c* o' P  l% y  H
    16' k) f" Y1 @$ d5 A
    17
    ' Q7 j- n$ L* a! x! d18
    , @6 y+ f: {9 A/ y19
    / U, ?* u7 N3 h+ U6 e5 h' f20
    % Z0 o% v9 L* @* U9 o21# u% F7 e9 c7 a; F0 X' I# g
    22
    7 I) M$ [- o2 H! {23
    5 Y" w/ C# D8 v% q+ x24
    1 K0 u- Z2 Y6 J! B9 @1 p3 P& s25& H0 ~, L& O1 b- o6 r
    26
    4 q0 [3 _/ Z! H2 X270 r+ o% _" `, Y+ q
    28
    ) }) \5 b8 g& T* y9 I+ Y7 I29: C! X0 B5 \6 S) s$ c1 G
    30& I. j, g: E( L% j7 N
    31* ~1 W  E/ h* G, n5 w0 X, z
    【NOTE】datetime64[ns] 序列的极值与均值
    $ ]" ^: W8 w% l) M  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
    5 m. D# t& Y0 ~  X1 L9 E/ v' S6 t5 t' U+ q6 |
    10.2.3 dt对象
    / g9 y; A! K1 {8 [7 m1 q# C( p  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
    3 [5 a' l& T: O8 A& U" ^0 X- _. o0 W
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。7 F5 [" |; L- [3 A; i! ], H2 {
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))  Q% F9 }* |" U; h/ a/ H

    - k; m- H/ X5 O6 j1 @! _5 `s.dt.date
    * r8 @2 A; P2 o% q7 ?" B) xOut[33]: 0 X4 r9 z# S4 M
    0    2020-01-01" R7 R) C5 [* T# l+ ~; x  y
    1    2020-01-02
      s; x. F2 s. B: c; ^1 N2 G. j2    2020-01-03& ]0 G& V; r/ Q6 C0 ]+ V; O# d; x
    dtype: object9 x+ a9 N+ Y( s; a; O8 q
    2 s* P6 H: i6 l
    s.dt.time; x4 M( J& @3 j* \
    Out[34]:
    # w1 z0 C( F; J( l& h) _0    00:00:00) |+ M* ^0 y& F* B! s
    1    00:00:004 j$ ^5 a  p: m! q
    2    00:00:00
    ) _" k: a( R* w$ edtype: object
      l5 {' _6 S+ }0 B. J' }7 \! e+ s. G% o- }' E6 ^! u+ f
    s.dt.day
    ! _3 n+ V, o! Z8 T3 POut[35]:
    . h  {$ B9 f0 x5 I* D0    17 ?) F! E# R" R( C& [* O
    1    2
    ( r/ g$ m+ c) ]* m+ F& j/ k0 b$ Z, m2    3
    6 i+ e: S- j, ydtype: int640 ^- v0 l& Q% B6 r) g8 W( k6 j3 M, E

    & ?$ L! Q# {3 L7 w* u& es.dt.daysinmonth
    3 _# v& O3 U) u5 P+ ^Out[36]:
    * M1 q# P  T; _3 E0    31$ n& M' Z' Y: q% _
    1    312 X8 n% q) e- O
    2    31
    $ |! f9 m: ]+ q( p: `4 e0 g7 a7 I8 cdtype: int64! d; S1 K* a( K# z8 q

    * M2 F9 y) j( L& E: ^1+ C+ i( q  W) G3 e$ H
    2
    ' e( s5 q; b8 S9 o8 D+ ~. \3 w  t  I3
    ( G0 [: W' m* B; V# L: k6 y4
    9 L8 z; K  |  U- |9 ]5
    $ V) b: k0 K+ j. m- A: C6$ j& O1 M8 h" \9 b8 Z" ]
    7
    , G1 z9 v+ l( N# ^+ A8; ]' c# Q5 V; \: k
    9
    3 E0 T. u5 n3 r108 h; b- W4 z8 i) Q2 _9 }3 V0 q
    11- _4 H( h; I+ V: O" z/ g
    12! I; c; B( _7 Z7 o2 e" X
    13+ F. q: X( `3 q4 c' j1 O6 A- Y
    14
    4 \1 z9 k% f+ X( h2 H0 ]15
    1 C9 F5 ^% k0 k16
    " _1 v; w2 G# s$ i17" q9 g: v" ^- g- \) Y& U2 t4 {: k
    18
    ( u0 {1 F4 H" \9 R& L19; s2 V% g1 n) W/ O* n
    20+ J7 F' Y: Y- ~" C/ k8 _
    21" R+ @0 C- P! E; L3 f3 [
    22
    " G+ y" d& e: |23
    , }& w+ ]) U4 r4 G' L  B4 T7 Z24
    9 j" W4 Y( H( l% a: k" F* ]25
    ; S' c7 d" m3 i5 B% ]. J6 Q265 u1 r0 D* E4 _& S( M* m% m; T+ h
    27
    $ F" y2 @% @+ T7 A' M28
    . d3 _' G7 Y7 C) y# q29
    8 q; L' r1 Y8 R& p+ n, v$ b1 ?. I1 g  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
    4 q. A* q; o  h/ i& L. ]
    * q9 a- _* f: v, Bs.dt.dayofweek7 H! E8 B3 R! K- ?. C3 m% ]1 i
    Out[37]:
    " T. L4 a5 @3 ^+ g1 u0    2. e* S% \! `: G& A  F
    1    3# u3 n3 I) S1 K$ z9 U5 p! r
    2    4
    : c* R8 b& e0 [dtype: int64
    $ I( B% C7 z8 D: Q' p! ~
    5 n) A. T' |! E) h" T" ls.dt.month_name()- }" U8 D: N' Q& {
    Out[38]: 7 `, O7 e/ W- r9 E1 j; R
    0    January7 E" j5 a. U1 P1 F6 j, ~% d4 q* B
    1    January# {# _8 v( |' Y4 E' M2 F
    2    January) h& j5 j& G% m4 O3 J
    dtype: object2 g3 f- E% @; h
    3 F: e6 Z8 a. w' A
    s.dt.day_name()* R% {3 T5 `# S/ X2 o0 [" r
    Out[39]:
    1 v3 L3 s/ ~: c7 |& ^2 c) `5 U7 m* ^& `0    Wednesday0 n0 T, M' R7 c1 h  ~& R
    1     Thursday9 z# Y4 r" H! E: f! s
    2       Friday
    ( ~5 R3 C/ L7 Y$ C+ r  X: [9 jdtype: object' |; L: E( B- _6 r

    * b  m+ J6 E6 G' m* B1# Y/ E+ D* Q$ l
    2
    # O! `# t& N' G8 E& L3
    + l  j7 ^  V( A1 z" Z/ K4  i  q$ k% I7 k- k- [
    52 `) v2 Z# F& A. [. R
    6
    + L  ?, [  {7 J7) p" k" w% g1 a* y' K! _/ [
    84 ^5 A2 g& V" d% {/ @
    9
    - g# H2 D3 @; ^' ?. [10" l1 f- y) H) p, W1 |: G
    11
    0 @' d& J( \2 _4 Y* \122 w( r/ {, {4 ~( e" k' x0 y
    13
    + w, N& x2 r2 O4 E14
    : L$ a- Q+ R2 \" R/ k15
    . A. p9 N6 Y, ~) i: T/ b* q16
    3 L. y! z: J0 K4 o- y; y/ C1 o173 u5 U( h. A* o9 k. K1 V6 @
    18) D% f! u7 C8 o4 a* @8 O6 i
    19
    * U- Q& r* Q4 @% |9 C+ r) ?20+ U2 J' T+ P) [7 }5 f' F/ p
    第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
    3 V) B* @# v9 B1 Ls.dt.is_year_start # 还可选 is_quarter/month_start, u! P1 k: B5 B7 q# H
    Out[40]:
    ! _3 d/ B) D! T/ R. c0     True8 a) l6 U4 i, d8 i( `* P2 p" S
    1    False
    * J3 }, Z( ?* O( R+ a2    False
    6 l+ c" r2 N- l% Mdtype: bool
    & N9 b. G3 c$ b2 x7 S$ H8 N
    1 @& S7 A8 F  K6 b. Ks.dt.is_year_end # 还可选 is_quarter/month_end
    4 X/ ~" E6 g( R8 ]9 _Out[41]:
    0 N! E; ?! m+ z  @; u! x0    False
    9 e  U  E1 j  o4 }4 l6 J! ?1    False
    4 R9 S: |* }; p6 d4 B. q' r2    False/ V- p5 T$ r$ s5 z
    dtype: bool
    " Q2 H1 E( E3 S# z1
    $ G) U9 _' t! Q; V( W26 z2 F6 M% ?0 O
    3
    : T& j6 K) y4 l1 u: \: D- s8 Z4
    + B2 o8 U) G% G4 V! m( R5+ U% X1 S  }0 L. c# v
    6
      n( P, |/ x3 E  e# \" w1 q79 p, l7 Q8 m8 {. i8 j9 p1 t
    86 X; H% a0 f9 K" J$ U* E
    9# p5 }5 p3 y' Z" p% h
    10
    0 I. J. D, l; q1 p4 H+ \117 _, |3 d7 y2 {/ Y. l
    12
    / F( n% I3 k- b# q13
    $ @8 k9 B8 |1 g8 C0 `8 V( D3 s第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
    ( ]9 Q/ ?" ?' L6 |. J5 O* Vs = pd.Series(pd.date_range('2020-1-1 20:35:00',! u0 S$ e; x/ Y9 R
                                '2020-1-1 22:35:00',8 H5 R; L& h7 W2 z% P( m6 T
                                freq='45min'))
    ) j8 ^2 m3 g( q( V1 C1 W1 f; y2 v, F6 M+ P( k  h& A9 G1 M

    / s, H3 p* c' P9 k5 ls
    + G, O2 h: G# q) Z' v. \' v+ G3 p, ZOut[43]: 0 X- \* r' n, W' d2 H
    0   2020-01-01 20:35:00- E( r  T0 ?5 c; a8 C! A; B
    1   2020-01-01 21:20:00
    7 g. l( O* C$ t2 {; V) u9 m2   2020-01-01 22:05:00
    5 ~' p9 y, I5 m! h& {! Rdtype: datetime64[ns]9 \* w2 b" l4 F$ Y( Q3 _
    ) p: Z( {" U8 d6 l' `3 _$ l# U
    s.dt.round('1H')+ H, @' y6 \+ z. ]5 i
    Out[44]:   l. @) g+ i8 }- U- `1 ^, ^* @8 C
    0   2020-01-01 21:00:00
    * G: _) e/ u# b/ Y# R8 Q& Q1   2020-01-01 21:00:00& G2 w  M' K/ B# r: U' b: n
    2   2020-01-01 22:00:00+ [' z3 K, L( E8 z7 j3 S3 C
    dtype: datetime64[ns]
    % m5 W. |1 y- Y1 d3 A6 r3 a  u
    , k. H* k4 a$ {9 js.dt.ceil('1H')8 ~6 X7 V) m6 }2 @2 a5 V
    Out[45]: $ N3 N, u4 F& `: D
    0   2020-01-01 21:00:001 c1 S0 E0 Z* ~1 ~: I
    1   2020-01-01 22:00:00& F9 a8 s( P6 E$ _2 e
    2   2020-01-01 23:00:005 t+ f4 C1 O9 i
    dtype: datetime64[ns]! S: N+ c1 C1 g9 _" Q
    $ l; G4 r) n; q0 a9 j8 u0 O* ]0 V/ |
    s.dt.floor('1H')7 m, l9 d  ]4 C6 J
    Out[46]: 9 }4 U: ^) ]" ^& `/ O( J
    0   2020-01-01 20:00:00$ a' W4 e3 k) h# Y1 M6 w5 h4 J8 [
    1   2020-01-01 21:00:00
    5 \& q% J- n  g0 v; y( o# _6 |2   2020-01-01 22:00:00* y, h9 ?( ?- |3 @2 J0 v
    dtype: datetime64[ns]- M1 D: P* m8 M
    ( z6 r1 d# D+ }( r
    1
    ; Z/ u. _/ p6 N9 S* H# v9 i6 O2) h( B; d9 x, N, E' w! ]3 C5 f
    3
      k9 J" M( ~# V( ?$ h4: G/ D8 Q3 _) l, Z( \
    5$ R0 n$ l; W# e2 [: L
    6
    9 w7 h2 q( s% k3 q- D; ^( `, ?+ j7) p/ N' h+ E0 ^7 D( M3 x
    8
    . ~0 O8 u: `6 |4 N1 L% U: H+ d$ k9
    8 P. [2 @* }8 j) Y10$ a2 j9 a- V. e
    11
    $ ?7 R' y5 ^! O1 o$ j12
    ) x. V# z$ n  z7 P8 s% m137 }6 S5 b9 ?% y8 y$ G
    14# w: O- a5 Z/ L
    15
    , V3 z1 h# p6 P- N- {3 C16
    & u( e( l7 N7 d- B4 a17
    4 W$ b4 r( \8 }4 S" y18+ l/ z+ m, V6 }3 C9 u
    19
    2 p% G- C% g/ y1 v- E$ F20
    7 S. P$ w8 T, ^5 {! _! ^- ~9 Y213 _* K2 l# F, I/ j, B
    22
    3 A' Z- n) G0 S7 s230 p2 F- n' q: X0 P3 N4 Z
    24: A4 N: i+ b: C( A, u- l* `
    251 }0 B; s/ `* {* Q: I% g. b+ W4 o
    26
    & @  x/ L8 O0 Q% o27
    . Z7 ], Y* r0 W1 j3 `* M$ S! g28; m' U$ r4 S5 ^6 b$ O7 {$ K7 K$ K; X' L4 A
    29
    2 B4 \, j" _$ a30! @( c, @8 Z& x
    31
    / w/ s8 O0 k0 ]7 q2 w9 B32
    " l* d" G9 c# b3 }, ?10.2.4 时间戳的切片与索引
    5 ^$ p( L, m0 c9 S8 j  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:% Z, J, Z, C# A; E$ D4 z0 S

    ' _0 k6 z0 E7 F5 |3 Y0 j利用dt对象和布尔条件联合使用
    0 J# L& h# p9 \' u) `利用切片,后者常用于连续时间戳。- K+ E4 w: o# t8 Y/ e0 l" p  ~
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))3 \" w: h* T+ q( ?- M
    idx = pd.Series(s.index).dt5 ]& O0 ~. H" x/ r
    s.head()! }; O4 @& ^! H; H
    % Q, N* I* K* \, W- R7 I7 G
    2020-01-01    0
    % I% f' Y) k, n8 ]* R0 A) K2020-01-02    1
    $ V/ g4 r/ S  N& _: D7 p' |( r2020-01-03    1. O2 X2 T# `! f5 s' _9 U+ _" J
    2020-01-04    00 ^) W) j7 ]: ]6 u7 Z; o
    2020-01-05    0
    0 [* K1 f! ^" k$ e8 u4 N+ BFreq: D, dtype: int32
    % j# F) D' d  A  R8 L1 i( l6 |1
    5 \4 b" u$ b% i: P2) `! u# \4 h* y2 Q! I, d. L+ f
    3
    / i. j. s0 o# J8 d8 g( O0 d0 K44 f" C6 X) Y8 T8 \5 K
    5
    ( d& j) Y  J/ u( ~" Y6* S5 ~# S- h0 G2 |  h  X
    7; P5 ~; w( Z. Z9 y
    8
    0 B( w/ W4 u2 z" Q* l) I. e9# h: b# N+ J7 T  E
    10
    0 s) L' L: d, u( z3 |  ^: `Example1:每月的第一天或者最后一天4 i' m# o) t) a1 u* ], ?) E

    5 R6 p. _) V; E# Hs[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values6 q  ~4 v2 S" a+ U2 v  Y1 u
    Out[50]:
    ) K( G7 I) Q- Y. W: ~5 t2020-01-01    16 V2 J. J5 \- V( j; \
    2020-01-31    0
    + Y1 F5 V6 S! ^4 j6 z- S2020-02-01    19 q, U7 M% D( i0 ^! X, z
    2020-02-29    1
    3 m8 C9 P+ }: h  `  c' i( Z2020-03-01    0
    4 {$ Q+ s/ P4 D# r% c5 `  {( P3 T  pdtype: int32! o( t6 v. u" w* U
    1: b+ P" R. }' u) L& P9 E, v
    2. L% P, y1 e9 }: ~: ^
    3
    . W* |3 i& d. c$ c* V49 D# p# a7 L) B: v. S
    50 g7 g2 A* S! V
    6
    " O# E  p0 Q  G. q- X" C, e$ \7
    8 }2 u3 z* }3 Y; x1 o8
    ! y$ U" [# l1 l4 S, j$ NExample2:双休日: T6 s5 w( u& _; U6 V
    1 o2 ]! a# \$ {) C; W& c$ r7 K: Q/ U
    s[idx.dayofweek.isin([5,6]).values].head()3 _4 P' S: ]4 D) f4 B0 x0 R
    Out[51]: . O& ?1 y4 i) v. C/ h8 V+ K' m; |( }2 R
    2020-01-04    1
    . H  J3 t  e* `. \% f2 P, F2020-01-05    0
    2 F( }* L2 f- d0 Y2020-01-11    0
    2 \  w) e/ R/ J2020-01-12    1
    + [  m+ x' r3 y" Q. K7 \' x2020-01-18    1# o& w6 M& p5 Q4 d5 ?
    dtype: int32
    # @  }' I" s5 t& y6 s3 B( w% l16 R# G$ o) b7 u( p# R- V
    29 \) z5 [( d4 I; V- e
    3* x, B; I! s' h
    4
    & i3 g7 w  Z' ?! t) |5. l' x* D* _$ g( F9 D; Y8 g+ W
    6
    ) W* @( j3 e9 |8 I& y6 h. E7
    & n! q" [/ Z, T; U: D8 ?$ B/ [3 u8
    ; E$ o1 @% k  {9 g; CExample3:取出单日值
    " }3 p# q8 ]! C- q* }: q$ Y$ O; e, t( h$ ?" v
    s['2020-01-01']
    1 a* u% w7 I" AOut[52]: 18 o7 n! @6 S9 ]7 a& A- ^

    / s+ r, Y9 k7 D9 D; fs['20200101'] # 自动转换标准格式  R' f: q) b+ M$ A
    Out[53]: 1% Z6 [8 M7 Q# h- U' O
    1
    3 W/ j1 A: x( F; Y1 P2 B* m. ]1 D2
    - o  |# s8 o( }3; U0 p- |4 E" b4 p' `- [3 g
    4! g* R# n$ U3 _, T4 g+ b/ o
    5' p! z% B0 ^# m; \: t# A
    Example4:取出七月  j  L9 G( Q2 U( z# N8 ~

    ) F/ J6 ]# o4 O, D1 `- Cs['2020-07'].head()
    9 ^, H; [! W4 m1 h) L/ n/ t5 `Out[54]:
    - H# O! s5 q- f7 e2020-07-01    0( O' l0 ^. I; }  F* m
    2020-07-02    1( c' ~) o, }$ ~! V( w, Q& P
    2020-07-03    0
    $ n1 X2 ]. I6 A# f! m3 _4 X2020-07-04    06 u8 U' d9 J, {
    2020-07-05    0
    6 G& L: _# Q+ s3 p/ G/ WFreq: D, dtype: int327 A% k, V- B! G
    1
    , C$ ]1 O7 r& W) w! L8 b2) e- }2 e. _) i
    33 u5 A* z9 N# g& ?! D3 P& B
    4. e9 `& z1 \, M
    5
    ' A7 |- \) m1 v6( G' [0 b! h( n, F
    7
    7 z3 K$ {/ T' {5 X0 E) s82 p% r7 Z9 c# w4 s- L1 ?* @# k
    Example5:取出5月初至7月15日
    & K, a: ~' j( d) |* e* y/ H+ P% w% j( K: ]9 L  C
    s['2020-05':'2020-7-15'].head()
    # S9 H' z$ p- a0 I8 [Out[55]:
    . ]* M" B; T9 a# j% q; V+ H2020-05-01    0
    9 X. T2 v8 E3 w: e2020-05-02    1
    5 @! c0 J: W8 N8 [+ `/ v6 @8 n2020-05-03    02 m5 @( _/ ], L  z6 I: m
    2020-05-04    1
    % c3 H1 v( s! b$ v8 I8 |8 G2020-05-05    1+ X7 H9 H; P# `& B6 w1 p
    Freq: D, dtype: int32
    : Q( Z8 _" F7 Q, U
    ) M! P& A6 a* G( Js['2020-05':'2020-7-15'].tail()1 W" u$ \4 E9 N* ~6 E. B7 e8 `8 x/ L9 F
    Out[56]: * k6 \" {1 ^- Z9 A, o4 [
    2020-07-11    0- j2 C/ x% o' X1 @1 a
    2020-07-12    02 |9 I$ t5 {8 q. |* y, s/ _  q
    2020-07-13    1( V3 K/ u& G! ?! ~1 K) r6 J2 R  L
    2020-07-14    0
    9 Z% I& G$ E: H# g# F& n2020-07-15    1
    1 c- r! l, y. w9 X: CFreq: D, dtype: int32+ p; t( C5 e. n. q

    1 k) o9 v' w' H" H. [4 ^1
    5 z! b3 z5 z; K- [" ]2
    $ X+ A# g0 d( h. {$ E6 H) m3: S3 f  J. d3 a+ ~. \& z+ q( Q! A
    4
    0 D; B( t9 M+ o/ b- F5
    8 Z  A' m- l$ H% ^' P% p6; R6 Q- ]! Q5 ^/ r8 @3 W5 ^
    7( }6 V/ I. O8 L# M
    8
    6 `1 T9 M; X0 N  E( z( y- q9
    0 X! V" L0 R& Y10) o  F0 C/ C- o. U2 F
    11
    / D! q7 z5 w9 K8 |+ J# ]12( ~4 f4 d4 @: e0 o* E
    13
    0 Z+ \: n4 z9 ?; I- j" _14- J# C8 J2 N$ w6 c4 @& W' O, e0 u% W
    15
    , p) N8 c: D) F- L9 I& H% ]" G: R16
    7 l: W8 h; \# i17
      r7 M  O! z. d3 U0 ~( X# y2 D10.3 时间差% c; [$ M. S, j
    10.3.1 Timedelta的生成" {/ A. A$ H: h1 ~0 c9 h. p
    pandas.Timedelta(value=<object object>, unit=None, **kwargs)  ^& R, b0 a7 r8 B/ n
      unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
    3 R5 V2 h% P2 o1 g/ B  可能的值有:
    3 c/ ~" |  V. Y- t
    / A: X( V6 |/ e& M‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
    1 y- F3 h. R8 x- ^‘days’ or ‘day’5 a: a, w& \7 r/ g4 G! ~
    ‘hours’, ‘hour’, ‘hr’, or ‘h’( Y0 H: }$ n1 F  V- y( V% u. m$ R
    ‘minutes’, ‘minute’, ‘min’, or ‘m’1 {- b' g/ U! N& @2 J2 Q
    ‘seconds’, ‘second’, or ‘sec’5 D  v8 x5 L$ i  o& q* D" x/ U
    毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
    3 ~/ n- z; j  n: j微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’, {2 M' m. q7 M6 z" f
    纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’." o; _% B. c' e6 ]2 k
    时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:! k3 o6 O5 H' r
    pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')" q6 a7 w; b# A# H8 `  R
    Out[57]: Timedelta('1 days 00:25:00')5 m/ d6 P% r' G8 D$ O( X- `

    6 z) l% {2 u8 }  |pd.Timedelta(days=1, minutes=25) # 需要注意加s
    ' d5 p% j) t' @) }( y; lOut[58]: Timedelta('1 days 00:25:00')* P, s4 e: X( z% h. P, n

    8 E7 o; P5 g2 U& ^0 p, @$ bpd.Timedelta('1 days 25 minutes') # 字符串生成
    7 a! _; e; g1 I+ H9 r# @Out[59]: Timedelta('1 days 00:25:00')9 I8 L) k0 {, k6 Q

    ( {$ z% s8 _  E9 l; kpd.Timedelta(1, "d")
    & g# |; e; E1 s0 [8 O+ X  w$ LOut[58]: Timedelta('1 days 00:00:00')
    9 P4 M- g( M2 i4 _, S6 j1
    # J( e2 ], P9 m; B2$ z/ |, Q( j# h$ k7 k' U
    3
    . c0 v% z& _0 D! W4
    ) u8 y  E* q" d. ?% z5% s# h9 c# S) A
    64 W, ?6 ~: {5 A
    7# z: u3 Z& W1 U. W; H/ K# {
    8
    / H! K1 z8 }: `6 `; V; G+ w9
    & m/ z2 ^* F# I; ^/ C10
    ) u) ?5 c. Y; ^: W11
    : S# Z4 ]* A& [) y0 c生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    4 Q4 q7 P' l8 e; |# hs = pd.to_timedelta(df.Time_Record)
    % V+ O0 f) J  R9 j* m( S  `' ]
    1 e# |7 l1 v. r8 `/ W% p- S1 ns.head()) t+ H+ Y& h& |' m& r/ d
    Out[61]: " R! Q, f& h* v9 A$ y. I2 i
    0   0 days 00:04:34
    7 x; W5 b4 A8 o0 R$ E1 }. \8 i1   0 days 00:04:203 ~) w4 u7 z3 e4 A# S0 D
    2   0 days 00:05:22; a7 }" H2 R. X8 Q
    3   0 days 00:04:08
    . c3 K' I( B8 }+ b7 n( F4   0 days 00:05:22* t. h0 m/ I/ c
    Name: Time_Record, dtype: timedelta64[ns]" s4 ?( N5 N$ F
    1
    4 ^8 D: N3 y& i9 M9 ^2
    " u& _- I" ]( o- u3
    2 D$ ^* N8 h- E+ K7 P46 q0 a0 q- J; o7 n. R2 F( [! `
    59 A8 d9 B; {" i- I0 _
    6% Y6 Y8 M0 \5 y& |6 e8 l9 P5 p
    7
    4 ~. p* |3 I/ S$ \8
    8 H* ]7 G" G* s) I: z92 a9 E9 `* T8 Q+ A, W
    108 R$ P7 S2 o9 u
    与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:* ^4 o9 }5 _% M( v$ Z0 \% N, |
    pd.timedelta_range('0s', '1000s', freq='6min')
    ( x4 p9 _* Z& E1 r% g: QOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
    4 s+ ]) ~0 v0 S4 V
    2 A, Q& k( w' R5 R) l" x, m+ Epd.timedelta_range('0s', '1000s', periods=3): v6 @- m  p. Y- ~. q! I
    Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    " X0 o, O' Y) \" ~7 k1
    7 L2 H& e0 i4 c, r+ ]% n* U6 Q2
    ( U0 J4 g, E* M( l( [( a3+ b* h6 p4 M, D4 W+ m" L9 ]
    4
      G/ O1 @' ?$ `5 l6 L. ]5. q1 Q4 A) c' u
    对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    5 L9 r, U* q" k% ls.dt.seconds.head()
    2 Z& [7 c+ P  W1 P+ g, B* {. KOut[64]:
    3 j8 m" `: P6 E5 [0    274  s( w/ t' ]# @* Y/ e: r9 b
    1    260% E. @4 a0 @# @+ }) [6 u; l
    2    322
    $ B8 M0 w) \! G( _( i9 J3    248. |; R( H, c" r7 r# ^
    4    322
    % p3 v2 m% l# b8 b' n1 \3 M4 [Name: Time_Record, dtype: int64( X0 |0 v( O) H" ?6 G( l" D! z
    1
    ' Q/ Z" x8 p6 T. T2
    ' L2 E  f" J4 I6 o2 ]5 M& e3: ]% _* G: E5 y- Y8 v5 @
    4$ j$ l/ x% E. z) [: u+ f: G
    5& B% l, _2 Z3 r, v' Q
    69 b* j6 N- v" M
    79 ]  }- C8 y$ y
    80 `) n* A3 K# R& i
    如果不想对天数取余而直接对应秒数,可以使用total_seconds
    : F1 i9 F; b! p+ V; k
    * ]1 K6 C5 F  m3 e  ]3 U! k" us.dt.total_seconds().head()/ L* N' c2 R0 j4 r# R  V6 d1 X
    Out[65]:
    0 `0 t  @, S2 [& z0    274.0
    5 ^& J# Q! G1 Y% [" B1    260.0  A  j' G/ V) s% H
    2    322.0. ]4 W8 e" a8 H+ }# w+ s
    3    248.0
    : U4 v! U2 y+ \. s# Q: A8 g4    322.0' S$ U) V$ {6 Y" }4 g1 C
    Name: Time_Record, dtype: float64
    ; r  s6 ~8 [0 d  r# {# f: @4 C1$ Q+ r# s. T; N, Q* g: m  \
    2
    6 T/ d0 i8 W9 s8 h3
    8 x1 [0 a% X; y4+ j/ o* Q  W7 @1 m7 c, O2 N
    55 X7 t; c! y7 O; K9 y# [
    6: _: D# o( @5 p0 S& o5 N) l% t
    7" |7 K6 K$ a$ D) g6 n& z% y5 N! a, C0 P
    8  K% Z' Z7 h3 u
    与时间戳序列类似,取整函数也是可以在dt对象上使用的:
    ' V+ b. C, J+ r  k* t  A
    0 w3 I- \- j, u; Hpd.to_timedelta(df.Time_Record).dt.round('min').head()
    ( ~1 v2 _5 T% r! S, \0 iOut[66]: . h, e+ D. u' z6 E# L6 k, b9 c) S
    0   0 days 00:05:00
    ! M8 g( S/ E/ x8 p9 q1   0 days 00:04:00
    : u3 X# j8 B1 O; H0 x, i" W" U% E2 ?2   0 days 00:05:00  n/ ^( `8 t; s0 n3 B0 p! A9 F7 F
    3   0 days 00:04:000 n! z4 g/ _+ J/ Z, i, t/ ?# u
    4   0 days 00:05:00
    ( _% s# s3 l1 W) XName: Time_Record, dtype: timedelta64[ns]) N& _6 G; A- R5 }6 \
    1
    3 `( Y1 [+ `# M6 w) L6 [# P2+ c) Q. V! P# B4 i8 N) m
    3( x2 Y: W: |9 |$ V7 J/ E$ ^# P' r
    4
    ! j6 x/ U7 u! w% |: V5
    / R8 f8 `' J+ R/ P/ J. P3 Z" y6* v/ q" Q! h. _7 _
    7
    8 w. G9 g# F& I: i* H8
    % f0 e5 }. a* V& y) Q2 U10.2.2 Timedelta的运算$ T( P' V+ B2 h: I. k
    单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
    1 u$ E  {& w: O- Q' ntd1 = pd.Timedelta(days=1)
    ; e+ ]/ E0 h! dtd2 = pd.Timedelta(days=3)+ g8 a. t, ^0 q7 W. M8 }
    ts = pd.Timestamp('20200101')
    % _6 l1 z1 Z6 q' q. z/ s
    3 i% t! {& [8 N! A& v  J* Ctd1 * 2
    ( p8 H& p" O1 Y+ v; kOut[70]: Timedelta('2 days 00:00:00')
    0 V$ E/ I7 m) _0 ^2 ?7 X0 s
      [% U7 n% J3 o- ]1 L. h5 ytd2 - td1
    6 `$ q) o8 H8 c  u2 TOut[71]: Timedelta('2 days 00:00:00')
    7 e1 s9 p3 k1 Z( _0 P0 m( l  C# G+ r
    2 \+ W* i6 q. N" H9 Wts + td1( `* A3 P4 @! o( s7 a! g
    Out[72]: Timestamp('2020-01-02 00:00:00')
      U3 W$ F3 D3 @/ @6 {8 f# D5 I+ i, N1 E, N8 \
    ts - td1
    % O# @* {  ?$ [( \7 U# _6 qOut[73]: Timestamp('2019-12-31 00:00:00')
    + p" T9 a" Z' ?9 Q% x* V" h' B1! @6 \2 X3 i, H- H4 x% _, u8 Z* R
    2
    1 ~* s/ v9 q8 Y- s3
    4 l1 D, X. P' x1 W2 P6 t40 H  y% T% h' w% P* {" Z+ P
    5' Q4 ]/ b* J  r4 l; f2 a) Z; @
    6; |8 b, n7 O) s: l- ?
    7
    5 S" K' I) K  F# V5 ^& {81 Q9 n3 L8 h0 C8 |
    9  k1 t0 A0 Z; m* n+ [
    10
    7 G) e& ?1 ]6 ~" E9 N! [11& ]3 O) ~) A0 V0 `: D
    124 u2 L% v% @! l# Y
    13
      |+ X/ \+ m6 v145 e4 k& r& \2 ?. T9 z0 K+ U, b
    15
    : t1 }: |2 W. m* ], S; _时间差的序列的运算,和上面方法相同:9 v' x- d7 ]# m0 L8 P
    td1 = pd.timedelta_range(start='1 days', periods=5)2 E: Y3 F( ^2 i8 r4 K) t9 S
    td2 = pd.timedelta_range(start='12 hours',2 Q5 j. S, l  l
                             freq='2H',
      W+ _2 F  G* q( e. i                         periods=5)
    2 y/ r6 n3 q9 L. N/ Mts = pd.date_range('20200101', '20200105')
    ( A) |; s" |$ T; y2 N. J  E4 O) z  utd1,td2,ts
    - ^' ?; a/ l- w. w9 s' m/ x# p" ~5 L6 j. A$ N" j' y
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')4 X) F& {! v' o7 R
    TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
    % i$ G6 s2 r5 V1 H8 d' Q( T                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
    " }" g3 }# }* ^3 ^2 cDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
    ; z3 W# l; z3 F. b! C' s% ^               '2020-01-05'],- @5 Y" s  I6 @8 A. L
                  dtype='datetime64[ns]', freq='D')
    / m5 V, J, }0 K  H1
    ! |  J# J& r' r0 q2: t' X, u% |) V0 E) n+ {' s. U$ t
    3
    % y4 _( m: j4 s0 X4; u. y( g) l! {/ x  S( b% J
    5' M7 t  f! S! Z$ q3 F  a) l/ o
    6
    ' x5 U( G7 u2 L& W: E+ j' w70 r4 d+ t5 L  X8 ?- m$ L
    8
    . H! ], i$ u* c6 s! g# ]6 @  ^" t9
    / k. L7 K4 l0 E4 \( Y. H10! `2 b( L( r( ]1 r8 D0 i
    11
    # a$ i3 @/ q" i# S# C" t( t122 r7 r) ~- m/ H* f+ H) N
    13; t2 Z+ y) t5 x( R
    td1 * 5
    % {  N3 T$ P7 {5 q: j. FOut[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
    # v6 R; \5 G  P" M4 ~# d/ h+ m; N4 K' D5 g# U+ Z8 u
    td1 * pd.Series(list(range(5))) # 逐个相乘
    8 M; P6 |0 N* b% s/ d- [Out[78]: - ~+ Y+ r+ Q8 H4 z5 j
    0    0 days  ]2 }& q1 c5 W) D' I
    1    2 days0 D+ Y; j) t: v; C! j
    2    6 days2 P4 N( G, K3 u) t& C
    3   12 days
    $ }, _+ b& F% F6 b3 D& @: X4   20 days
    % r' l) b( Q) M2 p8 q2 Idtype: timedelta64[ns]
    # h7 g9 H, k1 F9 f/ w. C0 [/ k) t3 }2 d, z% A6 r) O* x
    td1 - td2
    3 m8 Z4 {2 z7 F3 N2 j: jOut[79]:
    % Q% _, |9 z6 F8 E  oTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',/ B+ ^* F% K8 S( i
                    '3 days 06:00:00', '4 days 04:00:00'],7 H0 H7 j0 b8 y
                   dtype='timedelta64[ns]', freq=None)9 |/ B% E7 Y. w

    & j3 r1 `2 N) M6 }. a0 ktd1 + pd.Timestamp('20200101')
    - P2 j" ]" r+ ^  Q- Y% Z* b& z& v$ g; jOut[80]: : p4 B2 j2 r- a6 i5 d2 n6 n$ p7 ~$ u
    DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    , @# [3 z! \' \  j. x7 r               '2020-01-06'],dtype='datetime64[ns]', freq='D')
    5 g' y2 O0 j, y) ^' n2 J2 t. S+ e- w7 c% Y! S
    td1 + ts # 逐个相加/ _6 M2 z3 |8 ~" L
    Out[81]:
    ; M1 L4 _0 q; U! U* MDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
    " z& l; ?6 [5 y* {4 d) g1 J+ o               '2020-01-10'],& r+ t5 x7 x; ]
                  dtype='datetime64[ns]', freq=None)  n; z" r' X0 }  i
    * O. s4 `6 |/ E  M1 V( V
    1
    ( p' q8 p$ y2 M7 ?4 S2: C3 G& ?' ]; g; z6 a6 t$ n$ y
    3
    2 L& b) n' ]$ x3 ]1 a4- V1 f% {, P2 a) S
    5# i% y) w1 C; N. H: N* k
    60 H8 H2 T) W. f, I
    7
    ' Z  b* `! X% v; A4 C8/ F: r/ v8 E8 L
    9! J! s$ }2 M: v/ K4 C& T
    10
    ) r: y9 d% v6 w0 n- D" r11
    3 Y* u8 X4 x. A$ {: |8 j- u12
    # b( `& u( @" U- }9 v+ k13
    4 W6 a7 T( R$ n: N4 K& V' \14
    2 y& a0 V8 d( h; l$ d15( X2 i- W  E! ^( }9 Q+ m
    16
      t3 [4 b' G: r3 p9 b% ?17
    9 j- j! [$ t) N18
      [5 F/ A! [6 l: J& x2 f19+ u$ W7 x) A& |2 C
    20  o3 T% e$ g6 b8 w% r3 t* ^9 ?
    21! a7 _% A/ N8 C6 U
    226 W6 P' L3 V! @3 v/ n
    238 _" b" A* R- R* @) T' a' Y/ ]' f
    243 L: k; {& S+ o) c! h1 d: ^. q, g9 G
    25( J5 P! F# p  T  F( u
    26* R( J. W0 t  C; n) D# m% A! A
    27
    3 s$ O0 M  E3 u1 i( B; ?28
    ! c! n3 v9 {; p  y, p6 H0 g8 m10.4 日期偏置, z% W% Y9 N& w4 b
    10.4.1 Offset对象7 {/ [( u4 Z# y+ @. D
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。, j4 d* s# B0 T: G% M( L5 o

    5 b9 ^9 F- v& Y) C3 G: _. bDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:9 a6 E& D* \3 k# q( l" \
    $ l) Z. ^# p$ a  s) o/ V5 m( f' b
    s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本4 a' ^3 G, X2 H0 I( F0 Q3 F' R
    s.kwds:{‘week’: 0, ‘weekday’: 0}: f# B, w+ n: l% r
    s.wek/s.weekday:顾名思义
    , u6 D' `, y( E6 I$ P有14个方法,包括:( L' R1 |! v& G1 L) ~" a

    . Y* G$ e7 D9 a# oDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    9 a7 d9 {1 z) ^4 U' Spandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。3 g: B$ G& g& X. y+ L
    8 o6 c3 o, P; L% h6 \; b
    有两个参数:2 X0 n  |0 M4 R6 W) I# V/ c
    week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
    . E) j8 |- q+ w) B7 Lweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一): W& T( t/ {* ?( s  t
    pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    2 J; C: z1 I4 q3 a& j1 x8 I* g& K' F3 u, J  s; c* k" x
    pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
    / ]" A- p" T& V2 h* s  BOut[82]: Timestamp('2020-09-07 00:00:00'). U4 s4 F) x& B+ l1 B# e+ o4 P

    3 e2 I( C/ R( k' [; Q% W3 {pd.Timestamp('20200907') + pd.offsets.BDay(30)
    " w1 j, B" |8 I! D4 c6 r2 z8 G1 N& jOut[83]: Timestamp('2020-10-19 00:00:00')
    # V9 n1 P* b( q: v0 S1; f2 B# s' b5 C! Q0 z
    2
    . {3 G0 ^' W, Y9 E% S* z. _3( e8 N& s  K8 m/ p+ n- _, E
    4$ S/ ~% ~* z/ y3 D/ T7 j) w+ Q) Z
    5
    1 r0 D  W4 J. _4 R, C9 i% z1 @  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:, i, n, j* R+ c+ x& y# m! S  I

    " b/ a: F& J9 S1 D9 p- Hpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)1 P3 x/ z' M9 p, Z& b, Q: n
    Out[84]: Timestamp('2020-08-03 00:00:00')
    9 D* V9 q8 S, r6 ^& G
    ( y, h! I5 [/ `6 G1 y1 ?pd.Timestamp('20200907') - pd.offsets.BDay(30)
    % c' B# @6 `' QOut[85]: Timestamp('2020-07-27 00:00:00')' ?& y% c3 A( u" C

    * L& x! n& d) p9 [& C& ipd.Timestamp('20200907') + pd.offsets.MonthEnd(): G) p) b+ j1 n+ h9 z2 t6 U4 Q( T) o
    Out[86]: Timestamp('2020-09-30 00:00:00')1 W! _& g8 ]7 W9 `' ^6 S* c" ^% o
    1+ ^% l" U: s+ F/ e3 }8 D! Z
    2) w( [+ w2 `. Y  e- {# {, a9 n
    38 }, k0 B1 k& c" p2 v6 n
    4
    8 @8 ?+ y' }0 i4 w, ^5! }+ c4 G- k) a/ ^& S6 S# w
    6
    ; o8 N- v9 x, {7
    7 q. U6 l  l. K" y# h3 k8# o( Q* U8 w, S& w) V! d* ^- A# L
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。8 k; B% z6 x6 P' A
      其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
    : Z* ^7 r2 Y  l; i* r" w7 g& A
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109']), ?' h# ^4 e9 D; D9 o/ o: N
    dr = pd.date_range('20200108', '20200111')
    0 k: x, {1 C' M  _* q* E/ s, m: {2 I; ^/ h
    dr.to_series().dt.dayofweek
    : b, X+ g9 e! m7 R) kOut[89]: / a9 c- Z1 F" r' \
    2020-01-08    2) }7 [6 Y: [3 @1 T/ h4 \3 v  |: x
    2020-01-09    3
    0 o8 O7 g: u+ o2 d2 f8 C) [+ J1 a2020-01-10    48 y4 K: `9 |! H  E( c- y9 d
    2020-01-11    5
    - y( i! Q7 P9 K( _. Y! b; ^% VFreq: D, dtype: int644 S0 q  Q. U+ z- R8 }. `7 {
    1 @, g1 X3 W! {3 s6 u8 [6 d6 k+ J6 b
    [i + my_filter for i in dr]
    " r4 ]5 B: G, \$ k2 i: sOut[90]:
      w2 i& P: G: i) B[Timestamp('2020-01-10 00:00:00'),
    : S, U5 g1 i  j, x1 b  S  ? Timestamp('2020-01-10 00:00:00'),
    ; m3 m4 S# t9 E% y6 k Timestamp('2020-01-15 00:00:00'),
    . v/ P* v' p- q+ I$ j Timestamp('2020-01-15 00:00:00')]
    $ f% U9 d* x) F* w+ {5 r8 Z5 @$ @2 {& l; U; j1 r8 ?. l  C* ~
    11 T7 a+ I4 K% B3 M/ N) a' m
    2$ S# c6 p! l2 S7 Q: e
    3
    3 V3 `$ }9 |' `$ |( w$ i4
    2 ^. ^' q% A( S5 r3 d' o50 k5 S3 v/ S9 `
    6
    3 S' b) q/ K; k" s9 ?7
    ' X# n1 a5 M2 ]1 D3 g' K+ `: K8
    0 ]* ^4 k: K" \+ u9
    4 A. T8 V8 y% I5 b, j2 h% P" Z10
    2 \: m, r6 z% e4 Y9 p4 D9 T11* R' o, V: G7 u+ g. u7 s
    122 @; {4 f, r' J
    13
    1 M. R& N! r) u  {14. Z0 Z+ f8 Z6 S( P7 ]
    15
    ' m* R) [# H; ]- U/ |16& `) w7 K3 U0 _
    17
    ) a% v5 U2 j. ?0 {5 P* `  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。4 p7 J7 ^0 l& ^  S- g$ Z$ z8 ~8 c
    8 `! |  D) g9 e" j7 B: x1 b+ }. n
    【CAUTION】不要使用部分Offset
    1 z. a. L4 t* x- C; v' |在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。+ @- g/ ^) Q; g8 G$ I8 I

    . Y5 Y: H7 a/ f  h8 D10.4.2 偏置字符串( @& U; h! a9 e. _* x) h) y) G1 T$ a
      前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。" @' h( m0 Z$ t6 I

    ) @  `$ W9 H3 U  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
    / Y9 h  g! i# Z  U! S& [# i. y0 @4 Q% f( J' {
    pd.date_range('20200101','20200331', freq='MS') # 月初. C# S1 J' F: a% y% h% V) V' B
    Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    4 W; }. y' l9 j- A1 n! c$ N6 I: ?3 h# \. n% W! V
    pd.date_range('20200101','20200331', freq='M') # 月末
    ! k: P4 _+ `3 UOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')+ S8 O: ^8 y& `# S. W# D
    ; U& a/ M6 d. P' N
    pd.date_range('20200101','20200110', freq='B') # 工作日
    + M8 T% {0 y+ L- fOut[93]: , h4 W- ~$ m9 X7 w0 y
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    , u, g) P! ^5 H; ?. h- R6 g: U% s               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],: y  U' s' z, `7 |5 v
                  dtype='datetime64[ns]', freq='B')5 m. R5 H6 |7 y! D" U6 ^2 I

    % P' P5 o: m0 A1 S6 X' ~pd.date_range('20200101','20200201', freq='W-MON') # 周一
    9 k- c: }2 m7 F% z. DOut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    4 z- s1 @9 ~% J' K
    8 ]  k( R. b$ c6 W; X: b( W' ]pd.date_range('20200101','20200201',; ~2 r9 c2 j( a; x" b5 B
                  freq='WOM-1MON') # 每月第一个周一- C8 ]; t% @# W

    9 p& m% b+ r6 T; ?2 r& `Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    # F& c  A( f2 t
      G; L  l! B4 Z18 A) O  ]' D; S+ B
    2
    & j6 D# o. b9 P2 a3  i: S5 r( p  [4 W! ?
    4; j3 K3 P# b% y% }: R" A+ M% b
    58 J/ j. Y. ]5 {7 Q. m( U5 P
    62 V4 i2 S1 R% g
    7# z1 ^) C9 n! b
    84 [* t% b5 [$ a# I
    9
    ' H6 d" Q9 W  \3 A+ H( `/ ~10) |# Z& p6 u9 c" K+ j; S
    11
    2 \$ B4 ^* {4 j2 b. z! {12
    * z. k1 n, p7 d0 M+ }( ^. g! ?13
    9 E4 V, d& M7 K1 D  \+ H14( U- t3 Y$ Y3 _9 B5 O
    151 |" X+ m% }3 N  ?( K) ]
    16$ W& y0 T" e& k: n# z5 |$ A
    177 A! w2 k; n( N/ r% y, r
    18  E$ a+ X& r* z3 K+ Y
    19& ~9 p! p# b/ ?6 i% `. ^
    上面的这些字符串,等价于使用如下的 Offset 对象:/ i0 S4 c$ h- u, U
    / P! J4 \% f$ f, k: b
    pd.date_range('20200101','20200331',7 P$ b3 M; Z" `  w' I# l
                  freq=pd.offsets.MonthBegin())
    : X. X8 P; |; l5 P% A: P; }
    / h& _% Q6 w( d  C8 u7 n1 nOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')- ~* k- B. @; Y# z& G/ P+ n
    * t1 Q# M8 c  `: A' ^" x  a
    pd.date_range('20200101','20200331',
    : U/ g1 \) B' U% z              freq=pd.offsets.MonthEnd())
    0 q$ h9 z1 z, e, D7 H0 u, j5 L8 ?3 G* j" ~+ J
    Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')/ O- w, z9 W( d; q) V

    1 v8 q# j) c2 n; y# n/ C/ V4 bpd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    , U, Y1 f% F- ROut[98]:
    4 F' E) k1 l8 TDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    . e2 g( l6 I. ?8 x$ r2 k% O/ M, n               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],) L' n& D3 l; n0 e7 Q3 u6 U
                  dtype='datetime64[ns]', freq='B')
    " p1 q) \' @$ e! f; D2 a: z' b1 u5 a# Y1 l* r$ Z- w
    pd.date_range('20200101','20200201',+ [" O: _# t, [7 O5 _5 \
                  freq=pd.offsets.CDay(weekmask='Mon'))
    % \5 x7 }  b; F4 T
    4 i3 g, v$ A! q8 `  bOut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')3 k$ U' ^' n5 @! g; |/ @$ o
    9 g: t2 _  w' A4 ^4 ~! W
    pd.date_range('20200101','20200201',9 G. t. z* T: |3 w8 Y
                  freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
    9 q; l! j: |& o% `" k; g
    ; @3 \0 e6 K7 k# r2 g9 l, M5 b, h4 jOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
      t" ]& I  |0 O2 j8 p* l
    # R! n: S, A- I8 j. G( w1! G9 B! D& Q- U! I4 O* i
    2
    " e* R7 q, n% L3/ o" t! ^7 P8 k  [
    4
      @" A! l- I5 k  g; ~7 p5
    ' f& x& k% f  K* Q6% l( r4 V8 R; T9 M8 Q7 `
    7
    - y, c' J3 i7 F8 c  l88 E" s* P# _4 M) r9 e9 E# i: F" F8 Y
    97 L- a: |' X; u9 e" v( L  Q
    10
    , @5 t* c8 T$ O. w11
    # [+ _" C* ~4 @2 a, [5 ?12
    1 F2 C8 e5 H( T( H' o: ~  J13
    : O7 I! U! i4 y8 \14
    ) ~% a& R! z9 Z- }3 j0 x: J: W# W15) X1 a* p' G9 P. G
    16/ [; t! p4 ~7 F$ t$ ~) d. ~  N
    17
    ! p% Z/ ?7 y# T" B18
    : h$ {; y7 d  e$ o4 B0 H19; q# G+ r+ f# z6 j
    202 ]' `) k  ~: e, H, q: D
    21! Y- y' [. O7 L) e& a
    22
    2 M4 t, U1 E0 G+ v. V# `23# P3 L7 |3 X( k7 b: a/ |# b
    24
    + i5 K# I! c, n259 l5 j7 y( _9 l. k* g2 L& ^
    【CAUTION】关于时区问题的说明9 f9 C& v7 d3 L; h1 ~/ x
      各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
    # V5 U; W8 P+ B3 f# }5 ~3 r( D" A) q
    10.5、时序中的滑窗与分组/ s1 c2 U3 X( a- {8 @
    10.5.1 滑动窗口5 S" }+ r7 k% O6 z' R  v3 t/ U
      所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
    . X4 z+ }7 s9 X& t& A& `8 z3 B& g" f! p1 r
    import matplotlib.pyplot as plt
    " d# b9 Y! _% h# z" s; widx = pd.date_range('20200101', '20201231', freq='B')8 v* w( T/ z/ l* C
    np.random.seed(2020)& S' ?4 ]  r8 N, g5 a

    # m' _( n4 s! L2 O) _data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
    2 o# M1 y7 \. |( Ws = pd.Series(data,index=idx)& S, m3 S7 \2 j5 b- f6 \& o
    s.head()6 V, ^$ b' D9 i3 a  [$ _! B6 k
    Out[106]:
    3 M+ m9 X) }! [" }8 p2 q2020-01-01   -1
    4 f) t8 i1 U! Z+ l4 M$ J2 j. `2020-01-02   -2: V! C/ q$ b9 J, j0 |9 h) S+ k/ d
    2020-01-03   -1
    4 g' n' s5 j1 S: `" y2020-01-06   -1
    & M& ?( m5 g  w# L$ Z2020-01-07   -2, p& x7 T; J; d4 }7 ], u
    Freq: B, dtype: int32: f1 e7 f5 c- c7 i/ N( e
    r = s.rolling('30D')# rolling可以指定freq或者offset对象& m. C- c- F! Y8 O! X1 I
    ' g7 }' C# S/ M9 k
    plt.plot(s) # 蓝色线9 m+ s0 q5 z( x3 n3 h; R' @
    Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]! Q& X& r, Z2 r0 q$ J) N7 Q
    plt.title('BOLL LINES')/ b. T: W; C3 P9 \% }9 \6 T
    Out[109]: Text(0.5, 1.0, 'BOLL LINES')
    ) n6 L, b$ H* y4 `. J) i4 W1 F0 \* j" d8 t
    plt.plot(r.mean()) #橙色线# l3 n- f: B+ q, r5 X+ p
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]) U* v  y4 u- ^/ h0 B. i! {6 Q
    ; A( _& W1 @( e. H
    plt.plot(r.mean()+r.std()*2) # 绿色线; {6 m( F9 |& Z3 o5 W
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    1 @2 o/ ]" Z  R" H$ T
    3 Z" \. K; K3 s9 J. {plt.plot(r.mean()-r.std()*2) # 红色线. O, A& b/ A8 R8 B: l) H1 T
    Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
    # h9 @! [) |2 k1 Z. R( L, ^5 n0 W3 T" K7 p+ t" B
    1) B* W+ V0 E/ n0 K& S7 r7 r/ T! C' z
    2
    ! B+ H3 q0 v4 T" ^" F. R* p; e3
    0 q; f; F& a; |) d+ e42 @. u# W; b' B% Q: j7 P* h* z
    5
    ( u. V. A$ O' ?& G6/ `% n5 |+ @1 m. ~) v! U0 ]
    7
    8 X+ U# ?( Q6 P# Z89 s. F5 w6 j& ?: m
    9
      \* A4 x1 n0 H2 i/ O10* [( o0 n  P# F2 C' J
    11
    6 h+ d0 t5 A) f# E) @. Y% _  r12# m/ F: X1 i9 w. O+ Z
    13' c6 Y  @+ {4 @  H
    14: g2 G, M5 k/ L5 g$ v
    15# a; o9 w: u+ {, ^7 b# R6 Y
    16
    1 `) j) `) G8 Q9 Y) L17& y  V. F5 A# W* k5 J, W4 {" [
    18
    * e+ X- U6 s. e$ v4 p19' v. i  n- E; g1 A, l  a
    201 S: t2 J8 Y" R  I9 G& _$ F  V9 \$ I
    21
    1 n- H6 C0 H5 x  r# D227 y! S3 j# f3 l. _* c( H/ X% ]
    23
    ! h4 M; g+ I7 F$ L+ s! x% Y# G3 X# f248 O+ h# B7 x" `7 \
    25
    ( @' D( ~3 N% b+ g26
    6 ?+ S# _# U$ O* z5 `6 i27
    ' {9 P: M* Z) g28
    ' F1 l6 f- {) p29( p( Y9 p% \% o9 o: w. B. A; z
    3 a' |% u4 a+ L% O4 X
       这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
    : @+ y4 c* y9 B+ }   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
    ( v: S/ x! Q7 l' x9 S
    + [, c7 c9 q& \: L: J: q. g. Zselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]9 M8 p$ |  J5 A! \$ [
    bday_sum=select_bday.rolling(7,min_periods=1).sum()1 _  s1 F7 M: H$ `$ N2 D" ]1 I! X
    result=bday_sum.reindex().ffill()
    * e( f: ~; x! B( bresult
    : }1 E2 @( [7 _  a% U: |- _7 f' c3 ?, U; E
    2020-01-01     -1.0
    5 z3 o' r' x9 C% _- d( S, ?2020-01-02     -3.0
    0 V8 j2 g' d8 ^2 D2020-01-03     -4.0
    6 x# h0 c2 n7 u6 i- c2020-01-06     -5.0
    , ~( M5 w$ [# @  Z9 q+ U2020-01-07     -7.0
    8 p- _8 e9 k( i1 H1 J/ r6 @$ O# W              ...  
    ) J- H, p, X9 Y0 x2020-12-25    136.0
    : p5 g) ?) K3 O/ u2020-12-28    133.0
    4 n& d7 l* H+ H+ x7 H7 L2020-12-29    131.0: e, e( p! F$ F7 @* N
    2020-12-30    130.0- s9 @3 z4 Q& r0 f
    2020-12-31    128.0. C7 j/ @  @: |$ m
    Freq: B, Length: 262, dtype: float645 k1 p& X! s9 E# o* v7 D6 ^

    & u) T& f6 L1 _5 t1' z& R5 {  X/ o' d7 [
    2
    6 G: u1 b: M# W# z3
    . I# \( o  K0 k  U! w4. t( U" [' V- [5 }+ B$ Q( D' P: v
    5- ?, r  Q  ?2 v% q+ R
    6
    9 G! ?; q9 F+ g, a3 x1 {3 E" `; z: g7% N5 Y' b  Z. u0 n/ R
    8: Q, p1 m9 h- m  q* J; t
    91 J/ p% m, V3 p7 o& X% r" M3 K
    10+ N3 E, u. f0 G1 R$ [9 w
    11
    ( B7 b2 W) ]4 I5 o: f+ e' g129 p1 q3 D7 x1 A0 w* o- v' a
    13
    , e# {! @' @' X( x5 L& Z. l0 G14
    : h% }& C8 j% C7 F/ [# s15
    ( o. t$ M* P4 Q16
      A$ c  I( a* q( W& M* E8 ^% c17. [% v" w$ L8 U" t. g
      shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。) N$ V3 j; W( a$ C: o- I- [
    . m* {8 Q: a; t- o' V2 {' V: x
      对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:* W: e/ g8 ]( D% K% x0 I8 O8 ]

    % v" R6 u& V& j  L) U7 l% ~s.shift(freq='50D').head()
    * M" `" ]% r7 z. g  g: ?' _  ROut[113]:
    6 T. m7 j# A$ ?# F4 {  u( w) r2020-02-20   -1
    + a! V% @- F& |; U, F8 i) [' L2020-02-21   -2
    . V5 J6 P" d$ {2 W0 ^, U2020-02-22   -1
    ( Y; @; t6 ]( S. H' Y9 s5 k! l1 q2020-02-25   -1% c3 k1 y1 @% s( e# H5 X7 p
    2020-02-26   -2
    ! o, r% F1 @: Fdtype: int32+ x+ Z! H4 ^2 C# N3 m7 C3 i
    1& o$ B( N' e( y' o1 p5 C
    2; b" \# ~3 U  y9 X, _" Q+ {4 U0 s
    3
      f" g7 V4 e+ j& [/ _, G4
    & Q# A- u' S3 F# e& J% k5' C8 d0 x; G1 Y/ ]
    62 x- l% H. ~( S4 \- y
    76 K% l7 V. ^3 x+ |; w0 Y
    89 @" h* W2 D; e" O& n
      另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:8 u' y/ A- V& h  {
    9 a+ [2 o! ~, K1 U( T3 n
    my_series = pd.Series(s.index)
    ' Q1 a" ]# N  D. f- rmy_series.head()! _+ t: b/ v. `/ Q$ K
    Out[115]: 8 Z' M/ c7 }; L: O
    0   2020-01-01
    " G: c9 v: `2 B% T2 y; n  @. d) \1   2020-01-027 l) K: \9 n9 @" u# b' P& G
    2   2020-01-039 y# C$ x" g- |9 H' w: y: m
    3   2020-01-06+ b' ~2 M& ]" \% o& F: ?
    4   2020-01-07" A# T& e5 N2 @7 l' J# m7 U
    dtype: datetime64[ns]/ @/ b" P1 c! @, V; `. l

    ! U- U& q4 ^# b  k, \my_series.diff(1).head(). s( n8 P" |6 w4 L8 c0 O4 ~# Q
    Out[116]:
    : L+ o) H& d) W0      NaT( I. T3 i( P$ i3 }0 L3 m2 d5 o: W
    1   1 days
    7 F$ r- c2 {% ^6 ]4 N  i2   1 days
    ; I4 Z0 t% s7 D8 t: I5 r3   3 days+ w4 l# B  ~5 u6 h( o
    4   1 days2 K' I7 x5 Z8 X5 B$ y
    dtype: timedelta64[ns]1 V& R/ b# y- K) u& a" |1 Q

    + R/ S# B- F3 \6 G$ ?) u1, l5 {  C2 i/ J/ v
    2
    , Q: J0 W/ p! g3
    # {9 n$ F# H  C* h9 n4
    $ Z, S# ^; E' ~( G1 [; U0 N5! r$ z2 v# ~4 |" _/ z0 i
    66 W& R! P6 W6 D
    7
    , s1 l0 E( V7 p+ P3 w8
    $ v) P+ O' u3 J. O9- _9 R* o5 I  ?, L0 Q* |2 Z
    10' U, k+ p# d- J- ^4 B6 S
    113 g0 Z3 J( V* k
    12
    $ J4 Y9 e7 Q6 r+ H3 m+ o13
      i& j: L6 L' E$ }14
    ) t0 t/ v" U% {) ~+ t5 U7 [) H0 A, u" J15- g  p5 J3 n. D6 l
    16
    5 o$ G8 f) n3 Q8 K5 c1 q& D17) C7 r( d& a" Z' e5 a
    18. E( p6 e- S. B/ W4 N4 `0 X
    10.5.2 重采样
    # z( V5 T! N1 G) O  DataFrame.resample(rule, axis=0, closed=None, label=None, convention=‘start’, kind=None, loffset=None, base=None, on=None, level=None, origin=‘start_day’, offset=None)/ a! v1 w9 |4 V& ~  _
    常用参数有:* b2 _; g4 v" k( _

    ' b6 c% H3 }8 j2 s3 E& F" srule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    7 N, N( ?3 q  g+ V3 Haxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
    ; j: }3 ?0 s$ S  g/ `closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    8 D, |+ w9 I* k# E& H, Wlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。/ C6 z' E& H$ G/ _
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。% u/ V$ m3 j. a' f/ e7 D) O/ k
    on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
    + D* s1 _& ^2 ?/ slevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。# u& z9 @+ C+ g
    origin参数有5种取值:
    $ k7 @" \* }+ Z+ w) P) E‘epoch’:从 1970-01-01开始算起3 X) v* u* {) `9 {6 l3 M
    ‘start’:原点是时间序列的第一个值
      t  w# [, @( i! F‘start_day’:默认值,表示原点是时间序列第一天的午夜。
    # T7 W/ s# k6 P2 H% S'end':原点是时间序列的最后一个值(1.3.0版本才有)
    3 c( d& v( e2 k* @" Y‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
    . `) D0 w# T/ y: |6 v& ]: toffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
    , C0 x9 c8 X0 W# d: z  closed和计算有关,label和显示有关,closed才有开闭。; r' f8 H7 Z6 n' g  \
      label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
    ) d3 X  c; w! @" X9 F1 ^1 h" L$ p- X, q5 W1 ?# C" B: M$ T* u7 f
    重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:  o: r6 p, r& j& q* w) j; ?& r' _
    s.resample('10D').mean().head()" S5 [' M0 K1 ?7 a$ }# j
    Out[117]: : F9 S4 Q/ e: @- V* [( w8 S
    2020-01-01   -2.000000) ?* G1 O, _% `3 U: ]8 I
    2020-01-11   -3.166667
    ) H7 i( G! a  x% K6 F2020-01-21   -3.625000: ~( {6 }( L4 [% _! [$ Y
    2020-01-31   -4.000000
    ) `0 p+ }8 }+ T* |- J  ]8 B7 z2020-02-10   -0.3750000 h+ q. c: s/ S7 l- d3 m4 P
    Freq: 10D, dtype: float64
    ! r( M) q: Q! W9 F( D0 U# C! U% l1! X. ~$ r6 M5 @% a* W# B, \* }
    26 `. K0 E/ z3 v, l; n
    3- z, C: m" G6 x3 p8 g/ v6 [2 z
    4
    3 R! H- R7 R: s$ n57 y6 j5 Z8 o8 w) p# n) F# A- S2 l
    6
    ) r4 N+ v. C( \( d# N9 D- Y7( S0 d* B& p1 \2 f& @! d! G
    8
    % ?. K( X0 O- \( I" @可以通过apply方法自定义处理函数:
    ' W# C# L; a- l. is.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
    7 m# h" _9 v6 E! q( i: e4 y- y/ Q; s  g8 b7 s' P
    Out[118]: % _4 h8 ]5 R6 C9 ^- F
    2020-01-01    3
    2 j6 q: I% W$ e' D* m6 Y2020-01-11    4
    0 |+ J; h0 |: c! J$ o2020-01-21    4
    7 S  n3 p5 s2 R( |/ y2 w- E2020-01-31    2
    " q+ M: C( [. Q2020-02-10    4
    ; _3 |) _: m1 ~' b4 mFreq: 10D, dtype: int321 k/ F2 G0 P5 G5 q- ^  O! s3 y1 v
    1
    . P5 V2 Y% Y8 x8 c& u; \  B2
    " z% _$ Z- K. j. s, V! \' a$ G& ^3
    $ y5 d1 K8 E7 w) l9 N* h4* J5 G/ l- k+ g2 K" |) u. X
    5
    5 ]/ }  X, D2 L& `+ G6
    ! K' _% e. p) k0 J8 U  [! l7; d' h1 M' k9 f
    82 b5 J# D% U5 x8 M/ A& u6 \& i
    9% ~7 X, k* {& ~5 P% ^5 h
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
    % U, f. F& E2 l. C& o) J
    ! a- @6 {. {  `2 j" I% S8 Bidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
    / f' ~0 L! g7 i( G: g; }data = np.random.randint(-1,2,len(idx)).cumsum()- M. `% L8 V' `
    s = pd.Series(data,index=idx)! u  q% \$ Y: ^$ q- Z3 J# L
    s.head()
    5 ^; S5 b5 V$ T$ s, A3 Q) M- @2 \: J/ I" }, U
    Out[122]:
    ) N% Y% v8 w/ W  `0 e; D+ \- {! a2020-01-01 08:26:35   -11 Q+ V4 v4 _/ l7 j
    2020-01-01 08:27:52   -19 M( _0 I' O( R
    2020-01-01 08:29:09   -2
    / I  Y! O2 I! J: D0 P* v2020-01-01 08:30:26   -3
      f& k% L/ o$ }& H0 U' E  d3 p2020-01-01 08:31:43   -4* X7 K, n* r% g) }# v2 u
    Freq: 77S, dtype: int327 v- f4 W5 O( Q5 r) c
    1, k' g$ _1 L% _  H: h
    24 C5 d8 A' j& C8 U1 |6 a! x
    37 }1 b7 D- v$ M& N* R# `. `7 X2 s
    4
    ' z$ U5 |6 f; `" G: n/ c) S% p5/ ]% a  ]0 S6 a9 _, a2 u. r
    6
    + [7 j4 @; w$ A' ^1 G7: M$ X$ i5 @$ r1 P9 W0 |! w
    8
    4 O7 R# x# L" g+ t9
    : T' z2 k5 W( n  r9 C101 L) S& E6 p8 q" U! V
    11+ g; s' q0 W) M" d1 W
    12. U, }. l3 p0 V
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:" h7 K% y3 e. F, \0 t

    3 d4 a0 e% V% x, @s.resample('7min').mean().head()) p, D2 v8 A4 c, L7 i1 W
    Out[123]:
    + @' Z% S' M* }. E  X2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值8 i- S) V+ b, j/ C+ y
    2020-01-01 08:31:00   -2.600000. S8 \) t5 E9 a; }+ ^
    2020-01-01 08:38:00   -2.166667
    , a4 y. l2 [( H& D7 F2020-01-01 08:45:00    0.200000
    6 D/ I- U2 w0 b) ~2020-01-01 08:52:00    2.8333338 _# f5 y3 _4 Z. X
    Freq: 7T, dtype: float64
    ( g0 v* B. `2 s1) G/ ~+ w) q; E9 T9 a6 ^
    2  ]4 |- A6 Q+ J& t
    3! C) u* b/ _6 \% B
    46 U5 E+ R" `4 X9 d/ p7 H! `
    55 w( A) Y9 H3 ~. g! c- }# o
    6( Z! Q* `) z1 J, s
    7
    # C9 q5 K; W" N! X6 Y2 S1 T85 z) u  A5 z  J& `0 [; a  [
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:% ?7 B: X7 ^$ X
    # P6 P) i; a( D8 D" k' V6 E
    s.resample('7min', origin='start').mean().head()
    8 Y) w  M  }8 v1 a; n2 GOut[124]:
    % l4 ~7 y( J1 L* q" i- ?/ J+ j2020-01-01 08:26:35   -2.333333
    / B% T8 P+ U9 {2 I2020-01-01 08:33:35   -2.4000000 s$ E0 T1 U  z9 Q  \, Q* T
    2020-01-01 08:40:35   -1.333333! D& j: ?2 T1 s2 F
    2020-01-01 08:47:35    1.200000
    5 `$ B# b" |/ V1 J6 V7 r+ L2020-01-01 08:54:35    3.166667& E* U/ n% w- g: I. ?
    Freq: 7T, dtype: float64  T3 F! Z6 v. |  F0 k
    1
    $ i/ @( E& ~) c* K, l8 \$ r# X6 a2
    ) j! p$ J7 G% u/ N# \* W3
    ; Y. t% B" C; W) |4
    " C& I9 u( \) T  U/ m5
    - \: r) o  O: g/ @6
    5 s+ i. B6 x( V% j+ |: y( u77 H& w3 o& X4 U! A
    8
    + m7 c, D3 u/ {5 M+ B  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
    ' I( t4 s: |7 h. w" r+ Q" o6 w1 b
    s = pd.Series(np.random.randint(2,size=366),9 D! V8 h2 {4 n* a
                  index=pd.date_range('2020-01-01',2 n( G: u% u4 t3 ?! S
                                      '2020-12-31'))
    4 X$ b9 Y- r4 V3 K
    ! l9 \0 C& O7 ]8 E- q( j
    + w* s% n' c: n+ Ns.resample('M').mean().head()  J) j3 u6 |9 j$ H( v8 y
    Out[126]: ; L7 C$ P7 ?/ [
    2020-01-31    0.451613
    : h' e9 j6 ]8 Y% @' u* r9 v! F2020-02-29    0.448276; a& `: J! h# H) v" E6 {
    2020-03-31    0.516129+ s$ I6 i! n& k3 i' W
    2020-04-30    0.566667, r$ m# d: i& C" w0 R$ ^# T4 [
    2020-05-31    0.451613( N+ H! V3 `6 Y, B9 V3 c
    Freq: M, dtype: float64
    9 f5 ]) a& ^( Z+ A5 ^+ R. y2 U# }4 E" |! n: j
    s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样6 i3 Z! p1 F) P0 I" m, ]5 ]
    Out[127]: ( L. |$ F$ G+ |1 ~, J2 j. F
    2020-01-01    0.451613
    ! j0 x) {: ^9 r+ o, E7 Y: n2020-02-01    0.448276' e! ~, ^! I" @/ V/ v9 |4 b
    2020-03-01    0.516129. m, g0 v1 ]$ g# m  U( G1 W
    2020-04-01    0.5666674 r- k8 a+ N$ U% H; t9 h$ {* w. U
    2020-05-01    0.451613. P% N: Y& C! `4 s# H
    Freq: MS, dtype: float641 o4 k; E6 ?- o: j. s6 N
    + \! U; X+ O5 e5 W- W1 D5 L
    1
    & E, ]1 B- N3 j0 s2
    " t( D) u- D1 S( P* q3  j/ H( d# W, e1 d+ Y" i
    4
    * Z8 B+ @& Z/ M5
    0 o0 ]4 L# w, G3 P- _6- P- Z/ ^+ Z9 o
    7
    $ l' d/ v- y3 K: x) E% E& N5 ?8
    5 T& F3 H, ?7 v% G# V) ?* R9
    0 H: M3 a9 G. D10% x5 S$ X' `. h
    11
    # ^" }9 e$ p1 d+ O121 {, |/ Z. ~1 k' v3 N! J) z
    13
    4 R) k8 b& G9 `( j2 B& k142 f$ g& K! ~( w2 ~7 E
    15! k' m# C3 c. v  |4 \) t7 D- f
    16% d2 h( H( S% D3 X- W) R
    17! T" M* m, \& G9 S" D/ _* i
    18& `0 Z, V7 z; o( B" \3 k0 \
    19
    , R, P$ X* ]: J. s3 Y20/ e" Z2 k3 q: K5 m4 ]* y
    214 F# k. @8 E- R
    22: g' h% M) B4 N( j' H
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
    , `! d2 ^+ a% Q  c; H  xd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    3 m6 X8 B' A& w; x     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}5 O1 K7 i1 H2 A4 j
    df = pd.DataFrame(d)
      @9 ]1 Q3 f( D- c* R+ \, rdf['week_starting'] = pd.date_range('01/01/2018',; Q& E  Y# e3 c) Z
                                        periods=8,
    " o2 E$ n- @7 Z- |                                    freq='W')4 R7 ^  L: N, j+ O4 m" p
    df
    9 ~; _3 _+ Y: `  T0 `& M' t   price  volume week_starting
    3 l' N2 m  V4 F! N) ~0     10      50    2018-01-07
    ! R) r& F- t2 e5 P1     11      60    2018-01-14
    , I# i1 r- j; o- k: i! [- P' _( l2      9      40    2018-01-21/ R. E3 W/ k" d+ ]$ W- P
    3     13     100    2018-01-28
    1 n" P; [! c5 [9 [4 A4     14      50    2018-02-04
    ) r8 V$ G" X' R# `/ }5     18     100    2018-02-11( {5 e$ f$ P0 r0 b: n% |' i! e. `. g
    6     17      40    2018-02-18
    8 ^5 g7 g1 Q0 j, L' s) c7     19      50    2018-02-25
    # C( Z: G7 b; C; t& c8 t5 X0 E1 udf.resample('M', on='week_starting').mean()* w  b0 W: F5 X2 y+ w
                   price  volume
    , S$ `8 L, E; p) Q2 u, Vweek_starting
    ( m, r: }6 q- M1 O2018-01-31     10.75    62.5
    ; G7 }: r) [  ^/ r2018-02-28     17.00    60.0
    " N: t' W& Y) d" n. L. z/ y3 ~  g2 ^, y' T1 w6 I9 u
    1
    6 U$ [* z* a' o2 N5 x2
    ! ]% s0 N2 \* j0 h" v3 Y9 t: \3
    ' s0 v2 Y7 F. z9 p+ P/ p& |2 d8 _4
    + q+ T: C+ U9 k! r& p& a. T5
    # Y3 N/ E2 ^2 R2 i$ W! c6
    5 u7 c5 a* B7 u7 K3 ?70 m& i6 t! B% W: e$ X& h# G0 U
    8
    1 r% [8 s. A, D% w2 d( C9( m$ ^9 p  J: `
    10
    1 X1 v3 A" a) b/ P$ K11
    / E+ I" g/ }0 |! W12
    4 |9 t8 N" U$ n0 I+ {13% U4 D" ^, H5 u0 G
    14" e& i$ Z8 l9 g* D6 u
    15* V6 V$ l( E' \! P% E; i
    16
    ) o" V% L6 i( _% q# Y4 C% s4 [17/ J1 y9 h) p" y) r: K; ?" ?
    18
    / W1 ?1 _: j. o. n" R4 ~. {19  O. l3 D# N: R' @: M! d
    20
    8 L/ @' y9 v- K9 {% j  \! _4 ^' H21
    / M5 u8 ~( [4 f& P7 x对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。4 G( n5 S. J: a6 O% ~4 Q
    days = pd.date_range('1/1/2000', periods=4, freq='D')
    , {* `2 g* L. od2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],# [0 t: t, d4 d1 l
          'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    / R* P  `9 H# ~. w! v  G* Jdf2 = pd.DataFrame(4 ]) y" A2 q# K: y
        d2,: o! f* t) v9 P7 J: S$ Y- b
        index=pd.MultiIndex.from_product(6 e, w3 }7 p- B1 g+ N6 ?
            [days, ['morning', 'afternoon']]
    . A( G7 D- s/ l/ i6 _    )
    9 O# j$ v/ g# P5 K) M)3 d* J- G) n* Q0 x% E+ S
    df25 B! g( f% T9 }+ o9 l
                          price  volume8 a* {. l' ^, r+ v- C$ ]6 _
    2000-01-01 morning       10      50
    + r+ r) b! I. q5 a& g0 G           afternoon     11      603 b- T& p. i4 [# N7 T4 ^
    2000-01-02 morning        9      40
    2 K- [( m4 t3 |& L5 a8 X           afternoon     13     1002 \" C0 B; ^( t/ }: y% \+ m4 o
    2000-01-03 morning       14      50
    : l) C( I8 E7 N; N$ F           afternoon     18     100
    * K  u1 J2 }2 P( G2000-01-04 morning       17      409 }3 o0 z/ r3 B4 o, P! X
               afternoon     19      50
    , d3 \8 _; I3 K$ Wdf2.resample('D', level=0).sum()4 H& ~/ i7 X, F, S
                price  volume% C2 Q! W% O, _* d' d
    2000-01-01     21     110
    ' ^0 m. _8 ?/ X( @+ n2000-01-02     22     140
    ! \. q3 Z. g2 ^. d2000-01-03     32     150/ b; {' \# b. O7 k
    2000-01-04     36      90
    * j+ q7 Q5 }% q1 Z& f4 }/ X8 e4 x) H
    18 Q/ E! D  r. J" w* I
    25 h. _& q8 b0 \0 Z; W8 q2 ^
    3% p: k  e1 X( B3 `
    4; o$ [" ~' F3 u4 A9 S% _
    50 v% ~4 q& }7 Q* Q
    60 s$ ~+ T6 j. f- }' F/ ]! M
    7
    + u( k& a& C. O/ S( l  I8# v" P- V" V5 t& ?
    9$ [, _/ [& H* l% R- s5 _! z8 \/ S" P
    10
    ( a2 B- N( [3 @( S2 {  C11
    7 j$ E( K, v( ^/ ?) q3 V12( z! ^# I4 N5 @+ m9 K
    13
    & K1 m! o" {: Y14
    7 _5 D0 q% Z0 q0 |9 f* ?6 j15+ X# v) E0 q3 s8 q* D8 m
    16/ a, P" H: b  E6 F; [$ G+ F0 }
    17
    ! r+ L4 A* \5 U180 S6 d9 m/ X2 S" ^" r
    19
    / h" p6 p/ T  r2 b7 k20! m5 N" q+ k$ n7 S$ I
    21
    ; A  q# b* y5 x$ X$ }! Y; O22, ]+ O) H7 n6 w: l+ q- j& p. A
    23
    * B( d) Z+ k9 D" A, D+ F& V24
    / f0 ]. U! X# c3 M2 G254 [1 P8 W, R* z, g1 {2 h
    根据固定时间戳调整 bin 的开始:& _8 {) x1 j0 L) E9 j& i0 U
    start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
    ; }/ A9 y, v! {7 f* v5 vrng = pd.date_range(start, end, freq='7min')
    9 ]$ d5 k* i7 p) e' U) \ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
    . R* h$ Q* C+ G! p0 Ots- O5 ~1 z3 r* V+ j) a. j
    2000-10-01 23:30:00     0
    5 F+ }5 \+ g* v, A, D2000-10-01 23:37:00     3# N( Z! j, a$ F0 C' I) J, y
    2000-10-01 23:44:00     6
    . F0 _* @+ z% R' E) |8 I5 Q2000-10-01 23:51:00     9
    ; V  Z6 n7 T) H# i! [3 B; [2000-10-01 23:58:00    12
    : v( s+ h& ^' V2000-10-02 00:05:00    15
    9 u: w+ u! r6 M5 M2000-10-02 00:12:00    18" L' C* s) e# \7 ~9 E6 R
    2000-10-02 00:19:00    21
    / e3 D0 {* g+ D! ^2000-10-02 00:26:00    24/ s, m: |1 f* M8 q9 n" J
    Freq: 7T, dtype: int641 F8 }. Y  @( B. V0 r; d/ m% b+ e
    . b* `1 x5 W; I' t5 T$ ~5 ^
    ts.resample('17min').sum()+ E9 m3 d2 J* p
    2000-10-01 23:14:00     0
    6 M" {0 Z8 R9 e# f* _2000-10-01 23:31:00     9
    9 c0 r6 X* ]& l. p" W2000-10-01 23:48:00    21
    ( ~' l5 z3 C1 V# f1 I2000-10-02 00:05:00    54
    6 _( P1 }/ f8 j" t4 y2000-10-02 00:22:00    24. h9 ?! I, j5 H
    Freq: 17T, dtype: int646 T% x4 m( A1 L" Q. o

    4 Q" z7 T9 j! L& @' _0 gts.resample('17min', origin='epoch').sum()9 S7 c, }+ T5 `% t2 P! n( {
    2000-10-01 23:18:00     00 E( D' U+ ?' J& n& R5 ?: `8 o- i% |
    2000-10-01 23:35:00    18! R9 A7 j( P3 b. A% I9 W
    2000-10-01 23:52:00    27
    / v. J) `; f+ @- U- g* H6 R# D# _2000-10-02 00:09:00    397 p/ Y3 m( I0 G( E) \
    2000-10-02 00:26:00    24
    " b3 \2 Y. p+ EFreq: 17T, dtype: int64
    4 ^% a9 Z" v6 `, Z# d1 Y/ ~/ w: G
    ts.resample('17min', origin='2000-01-01').sum()9 @; F; v3 k7 ]. v6 }1 ^7 A. V( g% h( |/ q
    2000-10-01 23:24:00     3
    6 }0 @& ]6 K0 J! p1 R8 _0 E9 s2000-10-01 23:41:00    15* d+ H/ \/ X7 E$ U! E4 `: b
    2000-10-01 23:58:00    45) x; b; E- B: {& o( p
    2000-10-02 00:15:00    45
    * S9 H) o" e8 i; y$ j2 wFreq: 17T, dtype: int64
    + h3 g/ _9 j: X- F) L  U0 u# Y( D% d6 ^% U7 X! Z/ A4 f
    1
    * ^' v8 C, J/ b9 C% N) }$ T+ a29 I6 Y! P5 ^: P5 e! a6 T
    3; E$ v! A, L, I; F$ w& J
    4  f* G9 E% S; `
    5+ A( t3 U# u/ E( |, I
    6" P8 y9 {- ~/ I, ^$ R! \% x
    7
    6 b" Q( {. P0 M! L1 ]2 h) p) p8$ h$ S6 C7 E* `8 O: l5 L
    9
    ' m( f. l# q! _7 ~! z6 q  ?: _10" x  y' N$ _9 }8 c0 l
    11
    ) S+ g9 Y: g- T$ c- s124 z8 J* @8 s% N0 Z3 c
    13% n  ?; b- Y& W
    14
    6 G7 D: d% d% Q: Q* `15
    8 A' q- k3 F9 H* w  s7 i2 s16% K+ A2 F* J' E& C  w# w; R
    17
    4 p' ]* {! ]; V1 }18- o3 t: M: x' J  f; j
    19) u* W9 }* q. L3 B
    201 B+ S: `/ k9 C9 z% E4 ]
    21
    + M& n0 q3 N: t  k! V22
    7 h/ O# h4 v* e9 y9 f" P23
    ' ]' z1 A* l: B$ W0 X( _24
    4 |  `8 i% ~+ Z, K8 A4 y25) s4 d8 m" u2 L5 J5 ~, [* s
    26
    ' c/ i# ?5 M* B, v. \270 p; W! Y" h8 u$ ^* R
    28
    - N, c1 J. V- \. w0 V29# ]5 Q  _9 Y- |
    30
    # Y; S( n/ M/ y3 C- n315 d9 d: }- d' e' t  ^' k
    323 M7 q/ ]% Y! Y, K) W3 F
    33
    + P6 Z% P/ G, C7 [346 j  v9 s$ U) L# |) x
    356 v6 Y" \, \% r( p% t
    36
    3 z# g  g, w' H: z37) m' B7 K' k' V% ]* l& W: P9 v
    如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:) T  k, d3 k8 O" G: w# C/ m% p
    ts.resample('17min', origin='start').sum()) \6 r' B2 g" c9 i8 p9 o$ R! b" f
    ts.resample('17min', offset='23h30min').sum()
    , ~# N* b4 _0 Q& u2000-10-01 23:30:00     9( u, D  A- K( E# _
    2000-10-01 23:47:00    21
    , s9 _" r0 V% X7 I- c& T- g8 o2000-10-02 00:04:00    54
    0 G2 a5 F- S: ?1 i5 P2000-10-02 00:21:00    241 q$ A" _1 w5 R2 `" ]
    Freq: 17T, dtype: int64% b! C  ^1 r; s. R; y4 D4 V1 l" G
    1! _1 _5 W2 b) }4 D" t- a) ~; @
    2
    ; t1 _/ [$ A* P$ ^0 A! W3/ W1 c' w# M  R  u  O
    4
    ) N! m4 }: P) c0 {% `! Q, i5
    ' ?0 o" l6 a& V# X  I, E63 V) ^8 g  g8 w: B
    74 H# n) P- d1 S, y. P: w: m7 e5 b. d
    10.6 练习2 k, s0 l; i/ K" q/ p
    Ex1:太阳辐射数据集0 a. T6 c1 @; L; C( l) U
    现有一份关于太阳辐射的数据集:* o3 G' {5 T; x( x! k4 v

    9 j* x1 @5 p/ W% v' x& qdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
    + X" D; a1 O2 @df.head(3)
    5 _1 x% e7 i. p# c% V2 m* C' b7 _- G- m6 R
    Out[129]: ' h' Q0 P% {/ H( E2 \
                        Data      Time  Radiation  Temperature
    0 S4 r0 n: e+ B$ F2 `3 z  P0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
    2 ?7 E( j! D& D. S) @1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    0 t/ u0 T( s. A8 g' J2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
    ! L9 X+ E; v; J' t4 M8 j1: X7 e  q9 T9 J- m1 j3 I. O0 F
    2
    ) Q- ~: w9 D: E; v6 f& e6 i3, p3 d* Z$ c$ I
    4# b9 i3 e* Z' h; F: m1 {* S
    5
    % {/ t0 s! ?+ o* X# P68 Z6 ]- @" y. K0 p7 [9 k
    7
    $ f( X9 v0 f2 |7 D0 V- [6 t3 Y89 k% }( j7 \6 o5 o8 E
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    * W8 i+ U! H7 W" V每条记录时间的间隔显然并不一致,请解决如下问题:4 N/ }0 D3 }1 S. ]2 |5 A% o
    找出间隔时间的前三个最大值所对应的三组时间戳。1 @, X% B4 _% Z
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    7 p0 Z6 V/ e. P" q0 q1 h- f* u求如下指标对应的Series:
    8 N, K: y8 U4 b+ H9 \; y; r温度与辐射量的6小时滑动相关系数3 T6 m  M+ i! [9 x$ p
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    9 ?; T7 p! a8 C, _每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)+ Y9 J$ k! E) {* w
    import numpy as np: N2 D" j" u- S$ I4 j
    import pandas as pd. L5 V0 A0 {3 T" s6 y- w3 b
    15 o" ^) f. _$ i7 g/ l- E
    2$ }* ?! ~/ b! I7 }. ^$ Z, \
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。1 k6 t% ~5 }8 R" F* D1 z4 `
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列6 y7 I  u9 ?( m5 x
    times=pd.to_timedelta(df.Time)
    5 }8 x5 m; k2 ^8 |! Sdf.Data=data+times1 A! h2 ~) w3 S' g
    del df['Time']7 E3 U9 F; X2 V8 q! a. ~3 W
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留. N( ?6 i: W* t5 [
    df, c8 c! f9 _  H! Y7 W% b
                                            Radiation        Temperature
    ( B8 B; D9 ]3 h/ k$ K5 L- ]4 PData               
    ; [$ L1 k; x* L3 C2016-09-01 00:00:08                2.58                51
    + Q! U$ |" {! G, M( v, Z2016-09-01 00:05:10                2.83                51
    5 x3 E8 M4 y$ {! s7 T2016-09-01 00:20:06                2.16                51& B+ D- R, r. W
    2016-09-01 00:25:05                2.21                51: Z& k5 {; ?: y8 g: D; |. i, H
    2016-09-01 00:30:09                2.25                51
    2 ]6 }6 L! S- e& ^7 \...        ...        ...
    3 A1 y) [% C# q2016-12-31 23:35:02                1.22                41
    % Y: }% e6 X& i, I% b, N. ]3 x2016-12-31 23:40:01                1.21                41) k& p' j- x( e( q+ f# |0 t
    2016-12-31 23:45:04                1.21                42! g# F6 O* C( ]" K5 G
    2016-12-31 23:50:03                1.19                41
    " K% }1 D6 `0 p2 I" J* a7 {* c$ Y2016-12-31 23:55:01                1.21                41
    0 ?- l( V1 c9 n% n+ y5 B! F
    8 @) i- _2 X' L0 c1) X+ `) K2 b% @- c+ y5 f7 e
    2
    / f  y& Y6 L8 `% q( Q5 H3
    : K3 B2 m# U7 i& {4 q4
    - L0 \: Q6 C- {1 b50 L9 y) v; F5 x5 ]- C
    68 P6 S; x4 W( |* }- X( ?' x
    7
    8 e& n& C1 ?3 B9 k8; J2 A6 f/ t7 A/ Q1 U2 p
    9
    2 r5 J5 r& Q( y+ f. t! c10: \# d! F9 h% A5 ~2 }5 {
    110 _7 X5 t  Y5 l& p! s$ x6 x) e
    121 |3 G0 a. G% a5 `* [1 a  G
    13
    ) d8 {4 H% \- `, d8 `7 }147 U7 ?+ D* V4 c: i6 q( z( c
    15
    1 s" Z6 f" l9 M7 X; U16
    , g! w/ P1 [7 y. v( Z# f17
    % T/ C3 v$ u# n) Y3 ]18
    % i$ g+ O& c" G8 z( @2 q19
    % {: ^- S$ Y* j8 d每条记录时间的间隔显然并不一致,请解决如下问题:
    ' F5 ~  D5 X* s% r7 g找出间隔时间的前三个最大值所对应的三组时间戳。
      v$ W. l  Q7 N- P; J# 第一次做错了,不是找三组时间戳
    ; B- w8 K* i; J: n. sidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    8 H6 {6 \2 j6 P, Y4 X. ]* C6 Cdf.reset_index().Data[idxmax3,idxmax3-1]
    + X; \2 v; ^: W4 r
    # K6 Z" h) T" [' Y25923   2016-12-08 11:10:42
    & I; i6 w/ w" K! T: V5 e' \24522   2016-12-01 00:00:027 g4 h; ~" ^6 e
    7417    2016-10-01 00:00:19  P8 r( }/ ~8 X* r' @$ G
    Name: Data, dtype: datetime64[ns]
    : m4 z, S! w. `/ P0 V0 Q1
    - l  d! `3 F5 v! s3 R2' c+ [, I+ }4 |. Z4 M* [
    3" K, k+ A7 [) q) G& T+ ^
    4
    , z/ r* M7 g+ `. \) L5
    . ]+ X8 v' ~8 ^; ^9 h( @65 @* b2 Z3 y8 R: }
    71 l; _1 S! q) }) v) D
    8
    $ B  D  k5 E. Y( E* ]idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    ( U/ o1 _. u; Blist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))7 ]+ u' |( I! \
    ! r/ u$ t* i" t) o! ]9 N8 D9 s
    [(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),/ O; _; L+ U5 ]7 z4 p. ~: S4 U2 i- w
    (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),* H" \$ x" g' O- Q
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
    ) e& P+ G6 P2 x1) Q$ G+ x8 p; S0 T( ^8 e0 E* U- s6 k
    2
    9 Y& j8 ^: D# l, h! y% \, o3
    , M% t& {3 g; p& U+ `& f. `41 z% {) U+ O( R3 v/ T
    56 C1 E! [' O0 K; f) j9 l+ o  ]. E
    6
    * D2 X. H+ C  h5 ^2 l6 S参考答案:8 a6 P* {9 z0 Q% R( y1 x
    0 Y' ^& k1 c2 Q
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
      z6 [: g$ U" A2 f( A6 g: Emax_3 = s.nlargest(3).index
    % i' U! g& D1 M0 u) ?3 l$ pdf.index[max_3.union(max_3-1)]/ W  J6 S- g: H$ g' C5 I3 Z

    ! a3 h3 {3 n) Y4 `Out[215]: 8 L9 t+ ^, d) f
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
    / X+ K* Z' i: H               '2016-11-29 19:05:02', '2016-12-01 00:00:02',
    6 a, z- A0 \" ]. T! n9 |               '2016-12-05 20:45:53', '2016-12-08 11:10:42']," g/ P0 }! N% I
                  dtype='datetime64[ns]', name='Datetime', freq=None)
    5 c1 \9 l9 m' Q' ~* N, X1
    4 d# K9 X& i8 f* k2
    . F& d5 |0 _, U, B+ s- [! a3) D4 H# w7 M+ s) {- L; u( e: c
    4
    + r( R0 t$ Y1 o. j9 V  h% j5* M0 `% ], N- z! i; e. Z# s5 b
    6/ w6 Y' c9 r# W8 P7 N# _- Y, `6 l
    7
    8 U. ]2 g- b9 r! j8
    % o7 E1 M2 M/ [. M6 l3 O" ?9
    : B! {5 e& p+ W' h1 c9 X, `是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。" i' f5 F0 [. T1 i8 S
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间0 f* U* Z/ k& I% H( V
    s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)1 T" X. S; O8 }& e2 u
    s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05), ^! ?/ N1 H8 c! I. I+ S5 `% X7 p
    7 h6 A# f  C9 @' I4 X- q) Z8 p6 L) |2 E
    (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0): q3 ^2 }7 J& f
    1" @* P4 U& ?9 m' F' W, e
    2( B+ G$ f3 i) A1 n' z- s
    3: M9 W; x7 w" o2 r# H8 ]! @- q
    4% Q7 M9 \/ f, R
    56 N) l* p: m& m8 k* k% R
    %pylab inline) R% S# i3 k5 e$ `' A
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    : ~; e! M0 n% m  ?plt.xlabel(' Timedelta')- b2 B& n6 Y9 [  _  i1 M
    plt.title(" Timedelta of solar")
    3 o: d- d5 A* T5 T( g# @4 @/ w9 `$ K( l1
    % z2 f' s) O" P1 [2
    0 k4 M0 S# ]" A7 r. b" o- Q9 b3# H+ i) d2 h5 r1 d/ x
    4
    " w) q, @# g& C8 p! a6 ^& W
    ( h/ J# \1 D( Q/ l" \
    ' K7 |+ o6 Q9 K2 g; f% u求如下指标对应的Series:9 Y5 X9 y' x0 Z* H- ^
    温度与辐射量的6小时滑动相关系数2 N: ]6 S# \8 E3 l2 Y
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列/ O& C" R2 S7 L3 v
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)3 F5 E. E' L% H6 i
    df.Radiation.rolling('6H').corr(df.Temperature).tail(); J) y, r" ~* V2 {
    # z3 a2 L9 P, Q( B3 n( [7 t' @
    Data
    # T$ c4 s" E, a0 w1 I2016-12-31 23:35:02    0.416187
      `; ^- B0 v( l' K; I& z' m! n: E2016-12-31 23:40:01    0.416565
    ' _7 z" a) h7 d2016-12-31 23:45:04    0.328574
    0 v& R9 k" Q2 P( _2016-12-31 23:50:03    0.261883" }" S( Z4 a- B' f
    2016-12-31 23:55:01    0.262406
    " q1 l( V* B1 P% R2 i- Zdtype: float64
    - b5 D: E7 O" v10 ]/ ]& z, s' c9 a$ Y6 Z8 g, C$ r& j
    2; O) a1 h0 G4 s# n( G
    39 J( a! ^  W* R. d
    49 Z6 y! T. v9 L$ ]% `8 ^
    5
    * k8 u% s+ X8 v+ B" d" b( J6- F% q  J' K# K+ g; y8 k9 U
    7* u! ~& U- V0 ^+ d: ?$ e
    8# `/ {8 \0 C' R) W4 `9 ], q
    95 C1 m7 w( c5 n4 Y9 f% b& a8 c% U
    df['Temperature'].resample('6H',offset='3H').mean().head()8 k" O6 n  ^- S) D5 I
    2 F* g' l8 _$ ?" q8 T6 f
    Data, u* s5 S& S# R% H+ w7 w
    2016-08-31 21:00:00    51.218750
    0 W& l2 @$ M( B2 K8 R2016-09-01 03:00:00    50.033333
    0 m# k3 u0 e( S( e% I2 L- L2016-09-01 09:00:00    59.379310
    , G& C6 B9 O8 ?$ `3 X2016-09-01 15:00:00    57.9843754 F  z  D8 X8 n  d8 }! H
    2016-09-01 21:00:00    51.393939
    % B: }7 _4 k- k9 y: O! \+ kFreq: 6H, Name: Temperature, dtype: float64
    0 K* O( Z/ R% g: ~1
    8 m  c( a; R9 r" g$ w2
    ' m4 P2 K. {$ E/ x3 A+ R  _, h3; s9 z" u& k- H$ u6 J
    49 c5 s; G) J0 \- X
    52 {' o9 j2 o5 ~& j
    67 g- O% W! e* G/ B, \- R
    7  q' F( [5 X  v* h, K/ q+ v
    8. I2 |7 U4 Z2 j" w8 n+ B. V
    92 t, H# M/ _. a5 U. Y6 }5 U
    最后一题参考答案:5 H& P' c% J+ P/ V# J
    : z+ a; y, ?2 q; `# y, V7 t( H( I1 d" N
    # 非常慢
    # p1 S9 o7 v, ^* Pmy_dt = df.index.shift(freq='-6H')
    7 p, Y  J5 K$ b" M. Sint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt], B" {1 h' M) e: T
    int_loc = np.array(int_loc).reshape(-1)
      v, |. I+ P& Mres = df.Radiation.iloc[int_loc]! }$ K/ X7 E, u! i4 ]
    res.index = df.index, s  O9 v7 y9 v- l, i3 B1 ^+ `* I" e
    res.tail(3)& H- b7 q* ~% y+ {& c
    1
    6 i0 b/ V! V# N% ?6 l9 t2
      E- k  ?/ C0 X& A9 h: U3
    2 @  O: r9 N2 i) ~( c* m2 i( S/ I4( j& y! G8 F) r6 G% C& y8 }; ^
    5
    : F7 R) J9 c* K# b1 t* X4 {8 @6
    ! `' e7 p  n, s9 E( a3 l9 D4 V4 C7
    ( Q. G1 G2 P- q/ _* x2 d& B1 ?& L1 f# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级0 \. A' }' p; ?# `
    target = pd.DataFrame(
    4 m5 z0 r" K/ D9 ]& k. L) r% J    {
      W: r, F7 r4 d. J' X  ?: L        "Time": df.index.shift(freq='-6H'),- n5 h% P+ ^; c
            "Datetime": df.index," K1 |  _9 i, r% k4 }
        }
    % D# N% L0 ]3 O  C# O$ p)
    8 \  m1 }4 h5 C2 y( h! F1 `5 X
    3 G# x* q  _7 v# v% H6 sres = pd.merge_asof(# s+ ]0 G5 H+ Z+ F; v5 p  H5 G
        target,2 w% Z$ _& A# o2 ]- i: N
        df.reset_index().rename(columns={"Datetime": "Time"}),5 W. f' Z  r+ b
        left_on="Time",
    0 z. R3 G8 {6 B+ F- l6 U    right_on="Time",
    4 l5 h0 g$ E' \3 ^: R5 L& p    direction="nearest"
    4 ], m/ U# U, X2 F  h, \).set_index("Datetime").Radiation. P+ M0 K, x, B$ E; y; @* }

    # G5 Z, f) n3 Dres.tail(3)
    4 B9 ^3 R0 W8 K0 T% mOut[224]: - B2 Y% J1 `# g- @- Z! \" z! z4 ]
    Datetime
    % @5 R! b( l) F4 a/ [2 x; j2016-12-31 23:45:04    9.333 S% t. H3 `9 _/ `7 s; h4 k
    2016-12-31 23:50:03    8.49
    0 l* L( f1 }& h3 z: o0 T. z, R2016-12-31 23:55:01    5.841 y2 S) z- t. w6 Y8 \
    Name: Radiation, dtype: float64
    7 u; P! n) G. o: t9 t" \: @+ l! f( v% S) {
    1; ]0 T/ D  t1 z* Y  T
    2
    % E: I; ], {: ?6 j" _* J3
    & q, }1 H- p' N1 P4
    * Z" ?. J* Q2 T2 |! B5- `& u* C) ]) J. z
    63 u, W- h" S  ~, V0 m  w% c/ Q- X3 C
    7
    - `! F* d- j- p' A; L8% r8 k8 w% @  U% A3 @. K
    96 g3 f- ]# v) I( E' \
    10! M3 u; E+ j4 @9 m8 k
    114 e) F+ y7 r! ?
    12
    + E$ O$ q$ J" \" T13
    " x) V; ?  B4 D14
    ( ?$ v" G# X; v. b# Y4 ]15/ Z6 x/ L9 k4 S7 o9 K
    16
    $ D8 R6 u, U' K/ f0 o# M5 z- t17
    / H3 ]1 }/ B2 e) [5 u- h/ X185 Y/ L* i0 S% a: X# h
    19
    + Z3 }3 N8 ]+ {, G0 S; F20
    6 P2 _% \( S. @8 }7 x; z3 s6 q21
    3 ?. Y+ B* l6 A9 `( `2 O0 n22' B4 |+ f0 H' N
    23/ ^$ V7 Q" S# d* E: \% n
    Ex2:水果销量数据集
    $ Y& s9 B& g3 l# o' _4 l8 G5 |现有一份2019年每日水果销量记录表:% ^' m, j2 g5 c6 G% ?1 R3 l) j
    0 J. {1 s6 _8 D
    df = pd.read_csv('../data/fruit.csv')
    9 O5 m5 |  R) \( A" gdf.head(3)  m5 L0 p% q3 r- J' |2 y2 T; e( Q/ C' ^
    . h  a" d; r, _3 j- J
    Out[131]: 8 D9 b1 x- {+ q& ^; ?
             Date  Fruit  Sale
    5 h; y8 ]6 _5 p4 M0  2019-04-18  Peach    15
    / a1 }$ l. i+ U% u2 F1  2019-12-29  Peach    15
    % ?1 b: {$ `; x5 l+ {' i; P! i; Y2  2019-06-05  Peach    199 w8 S( P; q# q
    1
    : |& D( v1 U( ~( A- }6 W1 Y2# x0 W6 u, X( u6 c
    3
    3 g2 N" a& b$ ]; O4" s) X/ x6 o* J6 X, M3 U
    5, i( u6 [8 Q9 V9 m# N/ r  g
    6! W8 o1 N$ z* i) o3 n
    75 ~# m$ v. |- V) y
    89 n4 C/ B2 r! G- B" Z2 F
    统计如下指标:9 H" ~# [' I/ H7 B) x2 u
    每月上半月(15号及之前)与下半月葡萄销量的比值4 E& D' I; j5 W: H2 x  F+ a( ~
    每月最后一天的生梨销量总和
    2 x- f; u: O, s5 B- `+ q6 c" X/ P. S. {每月最后一天工作日的生梨销量总和
    9 V, C; E8 e% Z' s" B每月最后五天的苹果销量均值8 ^! P$ p1 Q) _2 y8 }) y3 N
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。" L  X. M" H3 O2 Z/ R! ~* O& r, ]. i
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    7 T; @' [3 q6 Mimport numpy as np
    % d2 R7 Y% X. ~" }" l3 dimport pandas as pd. F3 ~4 v' w( Y
    1
    ) d) [( w  p  M( c8 C- T4 ~8 k2- `) p- v3 D$ Z& V8 K3 G9 e
    统计如下指标:) I/ L! [& o: Q6 V5 G- \
    每月上半月(15号及之前)与下半月葡萄销量的比值
    ' m/ D3 {$ P4 L  B每月最后一天的生梨销量总和/ q6 Q, `( g' k# a  X
    每月最后一天工作日的生梨销量总和
    $ U, O. r9 I3 }, R, o8 N每月最后五天的苹果销量均值
    $ o0 N( V* x" L3 b! @# ?, I+ L+ e+ e# q# 每月上半月(15号及之前)与下半月葡萄销量的比值' B- D# a! m# W% |; s
    df.Date=pd.to_datetime(df.Date)* l: e# I+ i! T3 c
    sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()! Y' a+ N" v5 m
    sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    + J7 k& [2 M7 ]6 Msale=pd.DataFrame(sale)
    ) T8 y( c+ [' _% a  j5 v$ n  {sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
    5 {* J# \- v# m) \, [                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
    5 a8 v- e3 [4 ]( tsale.head() # 每个月上下半月的销量
    ( W( S) J5 }5 J* Q
    1 |  f) @9 ?0 v. y6 n/ p. y  Month        15Days        Sale
    6 j, Q: \. V  E# g) a5 S! ^- F$ \0        1        False        10503
    # H0 L& l4 ^$ r& A; |1        1        True        12341
    ) N& v$ }& f* Y% m3 i3 R: i2        2        False        10001$ x! Q- v' P% N# V+ t
    3        2        True        10106" D* M# ?% _& N9 L3 \6 q$ ]
    4        3        False        128141 W" w3 s4 x' k" M3 ]1 X. M
    % _- K" @% k0 D. u
    # 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序7 E! t+ r: p2 ~3 W1 C
    sale.groupby(sale['Month'])['Sale'].agg(
    7 d/ k- A/ w: t4 M9 [; l0 H                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    * x: }# B. w  n8 J
    $ u; b& t/ N+ G6 S  RMonth! B. V8 b8 \' _3 m& ?* d
    1     1.174998/ E+ r$ R; h, K  X
    2     1.010499
    0 i2 }# I+ |; r, G0 u1 ^9 ^$ A3     0.776338# p, V) O* Z! Q% E& W
    4     1.026345
    " c; v$ E% C! S4 `5     0.900534
    # |0 R! F7 J$ Y. M3 ~6     0.980136
    + _# r: F( E. u" O" A- Q/ |7     1.350960
    8 j$ @9 w7 o, o9 h& a5 g0 E8     1.0915844 z7 a. @7 y: U* i& t
    9     1.116508, N. G6 S6 l, o4 I4 n
    10    1.0207849 L2 v) G# M" o
    11    1.275911
    ; W: t9 J2 Y+ P5 G9 L+ i& _" F6 g$ E# U" M12    0.989662* g8 s# J5 `1 q1 B$ ?8 f* n! ?
    Name: Sale, dtype: float64) S1 V8 Y! `- |* n( w; d

    - S; H' w1 h7 j: f1( T  d  o1 t: n2 ~; W* g% o, p
    2! y# t0 V5 [1 r  {3 E4 y
    30 a, r/ L' n" b; S! s* Y+ ~# j1 _
    41 f- B+ A9 ~& ]  v7 j/ A4 |# s
    5
    + E2 s2 O% q$ ~6
    3 F6 Y2 R+ f8 z. o: a, `7
    . I) A1 \& L& @- S2 }8
    / U2 X5 @' M1 h) h/ p9
    8 h9 M/ \7 r! e10+ \( ~9 Y$ R. B, l) r9 [. Q( o
    11+ d& U- z! G& F2 E2 w; U# T4 m7 ~
    12
    ; P; \7 _5 r7 R6 F8 O$ {7 b" i9 Q& f7 S135 t; I* n0 Q  j. ]- e6 p
    14
    - _% S: |, r. M6 Y( |15
    : c) s0 y' a9 F, U& R$ I4 d160 X8 M2 T; c3 U6 \8 ^4 r
    17
    : ~3 O( l4 V  ]) w181 C4 H7 v  Q. R: ^
    19
    8 `- r2 G7 p: T3 w20
    + H. b8 z) `+ o218 v  F" X+ I; {) ?) a# Z7 q1 I1 N0 {
    22
    7 A5 X6 b$ C( Z23, a1 B+ S' a9 M- B
    24% N- f. I7 i5 L% c9 l
    25* h9 G& ]. t% b, W/ I# v- U, B! |
    26' T5 h: @' ?7 @0 k2 c' g- j) G6 B
    27. s, T1 w1 Z$ t( Z3 V3 O8 \
    28- X, c) I  Z5 t+ j0 ~, e
    29$ P" k6 C: e$ I: n- Z
    30  a9 u4 y2 }8 l4 J
    31" W5 C8 ^; e* w4 G1 x4 V. w% Q
    32, E3 ~3 q6 b! r( W
    337 ?/ l2 h8 Q2 `* b2 G% m
    34
    7 L* F/ b! ^) {# 每月最后一天的生梨销量总和
    $ w0 ~# e3 r+ U0 Idf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()$ ?, G7 T0 c  b( {, i! D4 H

    / a/ e0 O0 k. c2 uDate9 F) u! G% G, c  c" }
    2019-01-31    847
    % \) D" F1 g& L0 @# |% l2019-02-28    774! C; i4 g; y. _9 b7 V
    2019-03-31    761
    9 J4 k1 d9 R( ^' a( ~$ l2019-04-30    648# l* P8 u8 |2 I" U/ r4 E+ n3 V6 @' _
    2019-05-31    616
    $ g# T5 P+ Q6 l1
    0 n2 {- x6 s+ K& k1 ~# ]2+ Z4 [# \# G8 l
    3
    ; w  D# V6 t0 d' e4 `41 W/ L" k  D4 M6 {
    5; F+ V9 {! s* j# H8 ]
    6
    : s$ X3 G0 I- N( {3 h' i" S7# W3 S* l) I' _- [2 y+ K  y- p* F
    8# S7 \; I' [" U3 l  |
    9
    : ?. c7 s$ T3 Y" h% m" G/ y8 Y# 每月最后一天工作日的生梨销量总和9 \3 B4 n2 T; w0 e& `+ E
    ls=df.Date+pd.offsets.BMonthEnd()
    3 `0 E8 [4 W5 W, ?9 Smy_filter=pd.to_datetime(ls.unique())
    8 B5 K  e. A  S3 l( \3 v" Jdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    & r) q7 A& v' t5 F( w
    7 z" |% P/ r. a; wDate
    0 Z" W! @+ C; _. g2019-01-31     847
    ( O  H7 o! O5 H+ u- `1 F% [  ?2019-02-28     774
    8 i/ Q. _5 m; v  `0 e+ R" ~2019-03-29     510
    3 E  q( B. S5 [2 w2019-04-30     648
    6 d* P+ _. E: |1 O5 _8 u! I2019-05-31     616  T( \7 ~5 G4 R* J4 Q0 N% s0 R
    1
    3 r; @% y' l$ n  c; `: q2
    4 e$ w. _2 S' ^8 G, B' ]- m0 x3$ U5 t. q& h: J: g5 }: G
    42 S/ r! s+ B7 X' |) t
    5
    * D& Z; Q/ n% ?8 |6 K+ `6
    7 u/ F+ R, c) B; L  }7
    , p2 R1 g& O! W  F* {# o8
    * M) v/ x% I- @; i% t7 G/ O9
    4 J( C% L$ s5 Q  _9 P' u; U( I10
    3 @- a$ k) o# L, W- l1 q( U* ]+ I: @11
    5 A5 Y2 b: I0 Y- E. Y2 x, A( e# 每月最后五天的苹果销量均值
    ) T! u! [9 W8 j' A4 i( ^0 q) j6 Kstart, end = '2019-01-01', '2019-12-31'* A5 Q' A/ m+ e1 c3 U( f$ `2 I) E
    end = pd.date_range(start, end, freq='M')/ n4 T% e) {. p1 ^, s# H# }+ Z
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
    0 a, I3 n- i) A! G6 {: ]
    ; m$ ?( X1 v' t  T3 ~- h9 ^td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    % `, n  P% P  otd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天$ v* E& C0 C9 \; C
    end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    ) g, c* \+ C2 Z3 ^  g4 p* X5 E/ K7 [8 m9 y7 \
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    9 Q  ]+ \* _! U% u* T- r& ^apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()# [- C$ V6 h7 V; P- x) ^

    / V  U: _3 _" P; n, q3 X! ]) [. {# q: ^: kDate
    1 K, K* T& K* y; O1 R/ y1     65.313725
    0 q% [+ w, i% c) Q2     54.061538
    3 t8 A' P# T  v; h! w: f3     59.325581( E$ O9 T# r$ F( g
    4     65.795455+ F5 P6 A. {/ l: A
    5     57.465116
    2 l& F9 Y6 ], V
    + H  K) [: e7 i4 T( ]1
      s2 |/ b3 S6 E9 C$ u* j6 p5 k2: ]2 l7 Z- ]% v6 [
    3
    2 D) P" T6 U4 x/ I4 l$ _# A8 N47 f  z. G/ f* Y# V0 Y2 R
    5
    " h9 X' U  R" F+ z: f6
    7 v0 |) d! J6 H' W7
    " a/ d- E' s1 q. ^9 e/ Z8* W" j. i7 f/ Y! G$ G
    9
    * E: e! D6 a9 d9 w1 ?10
    , g4 B( P9 [1 I% i6 [11
    : F/ W* n( \: A: G, z: K8 |12
    ; N- }9 F) P/ @- }  q7 l2 ~13, _: q& D4 s2 h1 a% A: n
    14
    ( j( T3 R, v0 t$ P# c. H* J15
    5 A9 A: V* h3 L5 v, A16
    5 d. E& i. f9 I/ P17
    3 W# a7 L; Q2 N& }$ j18
    6 {* D1 O  Y  F' ^4 N- \) k2 ?# 参考答案:0 p. m/ x7 _0 e* d
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
    ! U( s; `1 G0 ?. x            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)) e0 i1 V: q( v' g
    + L0 D  H" v% s8 N2 Q
    res = df.set_index('Date').loc[target_dt].reset_index(4 S; h5 C2 G/ y) u
                ).query("Fruit == 'Apple'")
    & p. l. ?$ o/ O7 @9 G1 \$ I3 j' m/ x$ U8 L. U1 Q
    res = res.groupby(res.Date.dt.month)['Sale'].mean(  J. p; T6 R" `: i1 V; c6 L% z. A
                ).rename_axis('Month')
    + n( m0 [) s; d9 B0 h% ^
    1 e9 u1 r" D/ b/ Y
    $ K; a# A0 r2 vres.head()" }: t# M5 w; c& |7 Z9 H" {2 U
    Out[236]: 3 @0 u- K+ p3 h9 @0 b% y$ E
    Month4 [) [3 l4 F0 T* a/ D$ g
    1    65.313725
    2 ^& H7 @4 ^% Y2 i: L2    54.061538
    ) I2 p$ c7 Q/ o  a0 w3    59.325581: U( b/ t3 W) |4 v; g. w, W$ g
    4    65.795455, B% q# Z1 r8 o4 A! B' d% u
    5    57.4651168 ?! O9 Q+ }1 _3 ]4 b
    Name: Sale, dtype: float643 d- y6 g% _" H

    8 [  S+ j0 C; Q14 o( }4 E. q" c
    2
    9 r, C+ @: r+ ^  G) r$ u* h39 U! p% ~2 L! T  Z; }. p: ~6 I
    4
    % H' b' Y6 y! _! x, N. n' Y53 x. F  _* e, b" r
    68 Y3 ?$ P) F3 w4 [2 U) u
    7
    * o7 ]5 `" \/ c8
    ; P' t8 f8 Q, F1 v  ?3 w+ G5 I9) i3 g, ?! a7 C
    10
    1 k' _9 f: o2 t/ C0 j- ~11
    7 O; U- o3 ]) j7 o9 e) {# t12
    4 @# M0 u( J! i' l) `5 v13! J0 P: N6 A. B) d0 _+ s
    14
    - E7 ^, q+ K( Q% {% r3 A# N) L15' H5 @/ Q" F2 ?
    16% i7 j2 `1 }& R  G$ b# Y
    17
    & C2 Z$ m5 Y0 Y6 {) Y18
    2 `6 G# `( ^' M1 e  ]2 I, o& l) Z19
    - A2 C7 a3 ~: @6 }5 Z" ^* \; A6 y20
    4 X+ i2 p$ [$ ^% [6 s5 D* ?6 e按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。  x/ i- o. X' `1 G, n/ q: S
    result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
    " D$ Q& F5 Z  S' T* o                                        dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
    3 w+ _9 T; U+ F' J# Y: Y3 Z                                       
    6 ^% ~. @% j3 e2 J1 |3 Kresult=result.unstack(1).rename_axis(index={'Date':'Month'},( D8 r' B1 Q" t' ^' A7 U
                     columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.
    # r: |. c6 u9 |7 xresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
    1 b/ X& ~1 b, Fresult.head() # 索引名有空再改吧8 J9 c2 \4 Y+ A" O/ N4 w1 G& @
    - V% a- o+ a' Y+ O) _# m
              Week        0        1        2        3        4        5        6& X* p" P: q  @' ^! O! r3 B
    Fruit Month                                                       
    8 {- t' r, e- X( Q1 `Apple        1        46        50        50        45        32        42        236 e$ N! ?- }" W( K7 z7 I
    Banana        1        27        29        24        42        36        24        35
    . c0 o( n9 d  iGrape        1        42        75        53        63        36        57        46
    5 w0 H7 p% ?; h1 W& }* }% ?Peach        1        67        78        73        88        59        49        72
    4 s1 o0 f2 A, L8 i! xPear        1        39        69        51        54        48        36        40
    # c% l+ l, Y$ k- h( L12 J1 H# t5 o2 C% I# b) \5 p. {
    2
    8 X8 K" j6 t7 J- x) `6 q3; N6 z+ Y- J, R5 @4 L
    4: ^2 u+ @% g7 V" X' P% p) ^1 g
    5
    ) k3 h( {- z' ^1 Z$ L0 W6, z8 ?; N$ C; b% y, T
    70 d: r; F7 V& u1 U: O
    8( f2 Y/ q+ j' t9 A+ U2 y
    95 F8 |" z5 D! w& F! e
    101 K8 C1 j( h6 }3 j7 }7 @: _- m
    11& H" ^  I8 `: w3 |' I) O% m
    12+ E$ }/ F  n; c
    13
    0 a+ t& r3 \6 t+ P14
    ) X, |! W( Q4 P15
    6 b9 U2 \0 b+ S  [' @( S按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    * D; _% _) _9 p) c: N# 工作日苹果销量按日期排序, f. C( m7 f  ]0 m. C
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()+ h& t# A4 F' V% R. G
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总+ p9 b' t- i* a9 E$ @# y, D5 M" f
    select_bday.head()2 Q. Z  Z7 `% L' [  ~* o7 S! l
    # y( K+ Z1 |0 _; }
    Date
    ) a; T9 N" C! ^# a2019-01-01    189
    5 q" j9 i2 M- B+ ~+ k. E2019-01-02    482! f+ ^8 t) p# S! O  c
    2019-01-03    890* C+ \8 j. q+ Y" A
    2019-01-04    550! V& f2 \' V- _0 x
    2019-01-07    494
      B8 y3 C  ?1 l  h$ p, g% H
    ' S3 Q9 Z! n- v# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。
    / N1 @% E. J$ P% i$ Qselect_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
    5 `2 H2 X+ X- E. y  A% m$ _
    - C* f/ E, M: }$ b" `8 I) z  P1 NDate
    ! [% Y1 H, |' t  W2019-01-01    189.000000
    * R( a# Z# ~' z; x2 }" ?* c* F2019-01-02    335.500000
    0 m, W  L& h% b  q$ s2019-01-03    520.3333330 P, _# g. k& h
    2019-01-04    527.750000
    1 G# J8 k$ F8 V* k; M& @! H9 d  C8 ]1 F2019-01-05    527.750000
    ' j+ B/ J) M- _# |& }* s8 \1 x9 J! `4 k1 o+ i# C: ]
    ————————————————
    3 [1 x5 k& |& o! j% n2 T4 m版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。* Z+ H0 s0 D6 ^; Y
    原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    * v# ~* T" I1 `$ [/ }
    ( v: y$ N9 _$ w( ~- r8 A
    ' E, C+ H8 p: r+ ?- |2 o
    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-8-24 04:03 , Processed in 1.612557 second(s), 51 queries .

    回顶部