QQ登录

只需要一步,快速开始

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

    4 u! H# A( ^- }) p; G: k2 W! M: w8 M8 R; M( N
    " ?  \6 L/ |' V$ c5 w
    文章目录) Y, M7 V( K! d8 Z- W
    第八章 文本数据% C8 Q  s, N* m# y5 C1 J. ^
    8.1 str对象
    & \, M9 B- w# M6 x% m6 h8.1.1 str对象的设计意图2 T9 S1 h! K  Q
    8.1.3 string类型
    # Z' E% {! E% F9 L- `8.2 正则表达式基础
    & E7 W8 {4 J$ y8.2.1 . 一般字符的匹配2 D. e+ y3 I7 U5 i
    8.2.2 元字符基础
    : m& y1 E0 I! T( V8.2.3 简写字符集& \4 @" I3 F6 P
    8.3 文本处理的五类操作
    : M, S6 g+ O8 _1 F8.3.1 `str.split `拆分
    4 e: I: q0 Y( n, T! ]# O$ g! ~, F8.3.2 `str.join` 或 `str.cat `合并
    $ @* u& l# V0 J/ z. k8.3.3 匹配
    # z2 P" d8 S  a; b8.3.5 提取) i" o2 Y3 O7 [: @, v
    8.4、常用字符串函数( g% I& s0 v9 X
    8.4.1 字母型函数
    % S( x" ]5 o- j. j" v8.4.2 数值型函数! z. L: g, N" q4 m' U; a0 b
    8.4.3 统计型函数
    + d' L0 u2 m1 |7 @8.4.4 格式型函数4 V/ q# M; g% I# ?) W
    8.5 练习% I- |; }! N5 t) R9 X9 f/ L! S
    Ex1:房屋信息数据集
    ) {9 `* S0 P/ ^, yEx2:《权力的游戏》剧本数据集" J+ u' |  `0 }0 P9 l
    第九章 分类数据
    " H$ b5 d3 V8 L& _4 a9.1 cat对象
    7 m) N% x  Z+ M! J9.1.1 cat对象的属性$ D" B* K, O. h7 G8 _
    9.1.2 类别的增加、删除和修改
    4 W! b( M, l/ G5 J; `3 k% ?9.2 有序分类
    ( `' M; a, V) w! ], Q# D6 L3 Q9.2.1 序的建立
    . n5 M& o8 H+ x$ D; l  Q9.2.2 排序和比较
    + u6 m6 P; }" W# S5 e( L' e# T9.3 区间类别3 b) [# U" v5 }7 H+ e& B1 o
    9.3.1 利用cut和qcut进行区间构造1 t% K" W' n; J/ R/ o
    9.3.2 一般区间的构造
    / C: ~' \; i4 {2 V+ I/ j& G9.3.3 区间的属性与方法7 x3 o& s* C6 D- q, k0 m
    9.4 练习
    , \' u* A* `7 AEx1: 统计未出现的类别, {. ]# x' o8 v( C( [
    Ex2: 钻石数据集
    . a$ {6 C$ @' c/ X第十章 时序数据$ I$ j; T5 |8 A5 Z$ O1 W
    10.1 时序中的基本对象
    + z" h& v4 E' X8 ~, a  u8 h! o10.2 时间戳
    ' }3 B4 s- \1 K. u& C6 V10.2.1 Timestamp的构造与属性
    6 P1 c$ ?% e$ N9 v10.2.2 Datetime序列的生成% \; [; Y: m2 G9 \) Z1 ?
    10.2.3 dt对象$ z4 K3 f% S/ J
    10.2.4 时间戳的切片与索引
    7 s9 e! D1 V2 C. n' b$ G& y10.3 时间差1 A1 }' P, O. s  }0 _9 t
    10.3.1 Timedelta的生成
    : k, x0 V+ p3 W. _10.2.2 Timedelta的运算, N( B% U0 Z4 `4 t/ W+ X# l) G
    10.4 日期偏置: }: F+ @* U( M5 B8 I' u
    10.4.1 Offset对象
    9 v  A, j+ r" k) m1 |4 r10.4.2 偏置字符串
    , K4 M3 M! k7 }8 {8 ~# o10.5、时序中的滑窗与分组+ _& L, R9 i4 w
    10.5.1 滑动窗口& F" u: d0 A, c6 m, P
    10.5.2 重采样
    9 t# y) P6 q2 @* D$ B8 b) g10.6 练习7 [& G6 M  ]- Y/ R$ B9 U1 B8 O
    Ex1:太阳辐射数据集
    " c8 r) X& o$ I6 H: H5 uEx2:水果销量数据集' Q; q$ F: S- z2 z
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
    ) C$ Y" E& \; X0 B+ N, f$ P传送门:  F8 v6 P: b" G8 z# {- ]; ~6 ^

    , C( |# ~( X" S# A  K4 x& Ldatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)+ A$ w/ n4 K! G! p2 u
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
    . S5 [$ h' D; h: F& f第八章 文本数据
    ) X* y; w& F' ^' r8.1 str对象
    * }- B8 o. J* G# u' }8.1.1 str对象的设计意图+ F0 Z  d/ o7 ?
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:8 h; F3 I8 R2 b4 T! S4 s

    1 r0 I* o3 m  t% ?& Rvar = 'abcd'
    / X* f( P  U6 Dstr.upper(var) # Python内置str模块$ s/ U% p) t; L4 x( C9 u2 u
    Out[4]: 'ABCD'
    # {4 }- O# E8 Z6 |$ |9 I& O# }3 p
    , Q; `+ B9 Z- u( N& D7 @s = pd.Series(['abcd', 'efg', 'hi'])
    4 q' `' ]. }, v( c
    3 T: o1 o8 B) l- K; [& N# \s.str" w: H+ j, k1 S, @, b- K
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>% I: |4 t/ u- n3 S- }

    2 B+ h8 }2 I! `& M* q5 Bs.str.upper() # pandas中str对象上的upper方法# k1 \* R1 S5 m, [# D
    Out[7]:
    , v. A* Z+ U# C) N  L0    ABCD
    5 p6 f0 k4 T8 ?' R3 _' s: M+ i1     EFG
    ! d8 }) ^6 s9 u3 N) f( S2      HI
    ( |/ ]5 ?/ S4 q) d3 n5 }6 adtype: object
    % [- {" m. v( m4 |. n1' O8 `: O) [; J
    2* {# s; p. X9 q
    3
    ! {8 g5 E; S% H+ b- `7 j4
    * `. ^( @+ Y$ O6 @5
    ; `! u3 @5 {1 u* t3 ~2 f9 b6 A6
    % `2 S1 P- j7 Y, J, `# M7  b% ]. \1 W) L$ N  P
    8  y/ }4 e8 r/ R  z+ m' |
    9
    1 x4 u: U  K3 f, w9 p+ A10
    . M) Q" W  F6 T. y3 F- t) t3 Z11
    . A: A$ S9 |! h' E  x( B8 Z120 K" _; `, }& H) H$ [' ~( r
    132 A! p/ g" p: b: `1 ^+ ^
    14
    % N# ?& ^8 S+ i% t# r$ t15
    : g/ x" I9 H3 M3 f. {- U* Q8.1.2 []索引器
    ! `5 Q2 Q0 f3 n% x/ S8 X$ d  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。" f% `. |" v. k1 J
      pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:9 t$ s4 w5 b: N, |( |' \) a

    ! G" Q, T2 T+ X! B/ Ys.str[0]# \4 n0 x: D! P8 e. w! o) b0 L2 z
    Out[10]: " z1 ~, w- {: \9 f0 M# l. U
    0    a
    ; K$ P5 g! X! x1    e0 g+ @& ?# R% u3 X$ z& S
    2    h
    % d1 K5 D5 X2 w% J+ ~" ~# S$ vdtype: object
    / c# L& O. f6 L  t9 Y& _- X- }( V- J5 t5 v5 p, b
    s.str[-1: 0: -2]
    ( |  J3 H# f+ U- M" h8 `  HOut[11]: # ~+ M5 a+ q2 K$ U/ y
    0    db
      k3 n9 l; J& Y8 |6 k1     g( `* L8 k* K# D9 n( w# ]6 S' C% h7 k
    2     i
    $ Z& K" l1 W( `" Hdtype: object
    ; y+ o4 w3 }7 o! Z. a
    & w; o- M2 J$ L$ U0 G! z+ \* x# zs.str[2]+ Q! M) T+ [) S, q7 Q' c
    Out[12]: * i' H* d! z7 I7 ?6 t, [; D$ z1 k
    0      c
    8 Z) ?& Y7 j% `  a& U; y0 t9 Y1      g
    6 J# d6 Z4 Z* Q  m& I1 v/ z2    NaN( U  h9 s' N4 e: i. H" g! X* m+ q( d
    dtype: object
    3 a% P2 n) ]7 ]4 t
    + `4 R4 ], s# [+ C1 k2 t1
    - \3 ?* w  _& v) |2
    3 W" [& {8 t5 c! l  b5 A, J3
    * [- x" p$ V7 h7 v  E. e42 k$ g  H* w1 b
    5
    & [& P) N' Q5 N8 ]* K$ d& R7 x64 Y, F2 E, M" N
    79 ?8 S  C3 p! k) B/ S1 l2 Y
    8* I; t/ O$ g% e* z5 g
    99 X' H( Y9 x6 R) L
    10+ D, J% [) j+ c, t  v" l  ~0 L! _
    11
    % j* p( d/ l& [5 [% o" i' G$ a12# r6 I" n/ `; E2 y
    13. y8 d5 z) U4 q+ H& }! E
    14: ?# X9 u* i7 [2 O
    159 S: A1 {  |6 T! k- V, L
    16" @+ n9 s! {& ^( J; s) l7 N/ e7 e
    17
    7 n+ U8 j$ v; W/ X+ \5 e, Z' ]18
    1 P) h& Y& ]0 k& y. l) O! {19
    * m) K( O$ O. g9 [20+ |7 \* y1 h$ m6 i! B
    import numpy as np
    4 W# b$ j# t3 g' `import pandas as pd
    . n! o* o/ H$ k7 I% \7 H
    $ Z  j2 k1 s+ d" n/ T$ E9 Ys = pd.Series(['abcd', 'efg', 'hi'])% s7 z$ [0 T8 E- `- Y
    s.str[0]
    7 _8 e6 F* U7 {2 G. i! C1- x. Y8 a! d- X2 w5 l+ X, T2 x
    25 M# P) g$ G* {1 ]# d- Z; C
    39 n7 O, z; p' ~/ r  N
    4. c/ \  Q: f5 I$ t
    5
    3 i* l, R9 U: W" s8 c/ q7 U# M6 P0    a3 @, I, |! ^& N) }% H0 w6 s& v
    1    e9 v  V' I0 u# ~; A/ e# [% {
    2    h
    8 y) o& `! a2 y4 G: w/ x4 Mdtype: object
    3 `& d( f' I' ^% D1
    * H0 u$ K: m0 K7 I( I9 @0 [# L2; _* O9 t2 T5 r3 }* L1 z$ T
    32 S$ \2 g( c8 S; F1 o( |
    4
    0 H% D. Q4 t% P: j; W/ s0 ~+ p8.1.3 string类型
    1 J  ~4 y; H, j  在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
      k; \0 z: L8 ~- c! B5 _; L, D! U  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:) m2 \' ~9 N# L

    1 r. k, O9 f7 S( n' Z二者对于某些对象的 str 序列化方法不同。
    . Q1 ]( e4 h" S) e0 e可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:% @; N8 d0 f5 ~1 L( t) {
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string']); x# y2 E' ?! q8 b+ N
    s6 \8 k! X* ]9 y# Q7 N+ M# l
    11 L6 k. z9 M4 J! }' t  q7 c0 T6 [. {
    23 {( ]8 C1 k& {" O' e
    0    {1: 'temp_1', 2: 'temp_2'}
    8 D' D! Z$ X2 Q, G, v1                        [a, b]9 `5 L3 c; g6 \' K% ]
    2                           0.5
    8 T# s  R9 d' A! d1 g3                     my_string
    6 U( j9 c4 D4 n, y; O2 |dtype: object: A. `% S/ @3 [, V* h% k9 R
    1+ T) R9 g9 J* i; X
    2' Y! k) @% s* a
    3& Z/ j3 h& K/ U* I6 K9 u
    41 ?, v. @. N/ n, l5 y0 ~
    5$ T& ^; a) r2 i/ \0 Q8 P
    s.str[1] # 对每个元素取[1]的操作- M) `0 `' ^! X* ~
    1
    # r  ?* ?# l6 z9 o. O5 ^0    temp_15 S4 A1 P8 h7 P/ e& w! Q# ]& ^
    1         b
    - l* `1 S% I! u* a& G5 H2       NaN0 o& N3 [0 {6 S
    3         y
    - q7 k) F  u' Qdtype: object
    ! p% y$ }: h" R& j" y; z1
    8 l* v+ n+ z  l7 P2* y" w3 \2 ]1 a0 N. m7 N
    3
    2 C3 V9 D+ s2 n. g42 B2 ^9 F4 x# P
    5
    ( {. F4 V$ N* l( a. z9 Js.astype('string').str[1]. ?8 Q7 B3 G$ |% ]" a0 G" I
    1
      F: Z$ b" a# j+ u1 v6 c" i2 M6 ?0    1
    : M& C+ r* r  G* T1 n1    '% h9 S5 B, F$ \/ V! v" M) c' d2 l
    2    .
    4 y9 X/ R3 i; h: j( a0 s# s5 V. p3    y
    + R9 X# J) q# y  N& Sdtype: string0 a' N* e3 b/ a) t
    15 I6 u- A' C+ z& h
    2
    6 ~- U3 b% [# C' m6 Y9 j+ S3# u$ o( B/ c5 S, L1 q' ?+ x( s
    4
    ; ?, o# {1 ~: u$ ~! a; T5
    . ?9 x* @4 B6 X; [- r1 _除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    & C2 o( N' M& n# Y
    : J5 l0 V+ {" V2 a" L当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    9 [8 G# v2 |' ]$ }4 ystring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。, ?6 r( a# h4 R* [" Q1 r
    string 类型是 Nullable 类型,但 object 不是
    9 s9 ]7 @7 T# G9 K" i3 O6 [8 [. U  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。8 P( [4 Q( J9 `+ J0 }
      同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。% _# ]  a* |$ L, u5 [, L$ p
    s = pd.Series(['a'])
      q0 m% z% m+ y$ |5 S8 T
    8 m& M  K) V- U) d3 A4 j; Ls.str.len()* Q2 |2 v0 p4 }. D* H  X
    Out[17]: 3 `; K! y0 r3 R
    0    1
    ' U) I2 }" w6 \8 Ldtype: int64
    ; E! T" k) W! S/ m( J* [
    , Y2 E( U( {% s: Js.astype('string').str.len()
    2 H# R2 n+ W) T1 J9 T+ DOut[18]: 0 ^7 r/ ^# M+ M1 k( h
    0    1
    & z0 x; h# G. n6 C5 I1 [1 X& Odtype: Int64
    8 S$ J: G. j7 z2 ^9 A/ C4 W! _4 ^( [' O! \
    s == 'a'" G" j+ C6 q# h/ e( H( h
    Out[19]:
    * f- S8 n* Q, b4 b+ @# S0    True
    , O! z0 ^; \2 [% g  ~* Ydtype: bool, R9 c" A: J0 N; j" G9 J2 q/ N- a

    % l1 w6 R% h9 a' zs.astype('string') == 'a'
    % u* v$ y$ N. i  r6 }3 tOut[20]: 4 l5 a2 q' E" u7 K2 I# Q
    0    True! D. D9 q0 t' w0 @" r+ |9 |
    dtype: boolean1 f8 z% d. C+ k9 O
    1 Q) B: N' ?5 U! I9 A7 T5 ]
    s = pd.Series(['a', np.nan]) # 带有缺失值
    * f7 c8 M2 h* ?' k) h9 h4 G. a9 ~& |  {: b2 D! C4 I
    s.str.len()* {; e1 Z3 E$ n4 n
    Out[22]:
    ! R' L0 G6 v1 ~# ^  x* A0    1.09 M% s% u3 C' b/ U8 y$ \# ]
    1    NaN
    5 ]& ^: e/ q$ Z, Z9 e, a3 K" Qdtype: float64  K7 ]' s/ b+ r/ L0 K9 ]
    ( W  m" g. k3 @$ M" Q( ?# Y
    s.astype('string').str.len()2 f6 ~; N/ K. ~! }
    Out[23]: , z  |5 i, D9 w$ {% y' |
    0       1
    7 a/ Z* P- y, e0 }5 m1    <NA>
    * |) `$ |. w  ?& ~. idtype: Int64
    + N5 ^& `& @+ X4 |( C
    ) _. w  ?2 Y# K* b7 w1 G/ w2 `s == 'a'
    3 m0 m# H7 \! qOut[24]:
    ; i! ]8 s5 l' n" {& ~2 v0     True0 ~" w+ N' K+ B! Y
    1    False
    ' Q; g( h% N' \+ P$ Z5 Hdtype: bool
    & m) }6 y6 ~: r0 M% H; f0 E
    ) w4 y5 K9 o3 B8 z  I3 ^" V+ F2 Fs.astype('string') == 'a'
    ) e% D+ I9 n4 F, m! Z: Z$ ]) COut[25]: + Q% Q# e* x: I2 h
    0    True7 n) h9 \% [0 B
    1    <NA>
    , E/ x; c- _4 {0 N& p: [dtype: boolean
    1 @; l& T: c7 J, L
    & J7 W! f  }- ]- |) _: Y1
    7 d8 ?9 u' o& R/ C; R, \# F2
    : i9 J6 X# c5 |/ A7 e3
    , c0 m* A. ~% k% z  M0 f, L47 _" `* B$ ^, }: t8 ?7 K
    51 T) b  M6 P" M' T9 t
    6; y; P2 P% c+ a  p
    7) r  }8 [. M$ ~( [2 I7 b+ w- N& r- v
    8/ l& p; D; L3 L4 ~: H
    97 J# D% H) o& P& J
    10
    : g8 f. H% \" h5 N3 }11
    & j) f$ F* T! D7 c1 D12
    - W; `5 P1 A1 ~9 h) u130 Q* j& E+ g0 m& R  x, b
    14$ E, e: f7 r5 E% R2 p8 E
    15, m! N  J2 o; q  c0 g
    16$ Q9 |; i! k$ I; X( n! u7 ^# O
    17
    8 ~& Q2 a! D# f) ?5 Q# ^! [/ h18
    3 ^" p1 z8 A! T* x198 b' P! r% P& {) A
    20+ q' B5 p7 \3 `* \0 z# f
    21! h1 g/ z( f1 B( E
    22# p! w6 u; q3 D
    23- Z3 @6 H  d! L  Q, H
    24. S2 p4 Y2 j7 c2 @
    25) I7 D8 y5 N& ]) d
    26
    5 Y# @. D$ R" E27
    3 C; Z+ T6 B/ U+ i% n7 ~28
    3 U+ I3 G  k0 o29
    # O1 E; {4 f! N30! O& o+ u, r$ i( |: |
    316 _* }2 Y/ A# v! Q$ Z& C$ |; b/ F
    32
    0 K* R$ G7 e: D2 l33
    8 f& p, [/ K* h) e% x341 l) _6 U& M: _
    35; f& n2 J4 L. e5 o+ \" k2 o/ w5 O
    36
    & ~% g/ h+ f3 u& T) y* F37
    $ E' ]3 e: b) E389 x6 z8 L$ m9 `! _& y# y
    39& X* N3 v/ @! T: ?$ W
    40
    ' M, n( V5 I* Q  a# }41
    : ~5 O" |- o1 s: c6 J42
    / y/ q6 C) u& @$ `# n4 I+ w: m432 j1 V2 v  I8 h* m, s1 L- n
    44
    1 P: H7 i' F/ z! Y455 c# [* L5 J) G. V! Z* B
    46
    * l- J. s+ `8 g. N474 s! O* d6 a/ O9 @# e
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :+ u# E% P- z& d% T  t
      ^+ M' D4 x. }1 `( F, g3 }6 ]
    s = pd.Series([12, 345, 6789])
    # u6 x- C, V! G+ p/ z$ p& U" C% \# i6 d7 d4 q% }- p5 B
    s.astype('string').str[1]
    * q. I4 T# D- FOut[27]: 2 ^5 d2 u* j6 U8 M
    0    2+ a: t( `# x5 R/ J  I
    1    4
    ' M$ {  S5 s; g* ]& |. b: R2    71 J6 B0 Y: D# C2 I
    dtype: string" i) c( {% n, R" @2 D( |( n
    1# p0 j1 D- u1 ?7 B
    2( v) R1 R, O+ R! R$ F9 ?2 J
    3- L* y/ n/ m- A( W# Z+ f
    4
    + t- ]7 G( L" S0 v' R, p, S5
    6 O" }. z; R; N- P1 K! m6( t9 k3 \8 [9 ^# F) M
    75 v- \  X. a/ f- i9 Y- m$ i
    8+ g& Q0 Z+ e: k0 o2 X  t: T
    8.2 正则表达式基础) P: A( k, N/ Y# S' `8 H
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书+ K- b& ?' d2 `! o: ^

    7 A( V4 H3 i- O4 p8.2.1 . 一般字符的匹配
    % j0 O+ M/ @& K; F7 h! q2 a正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    4 j. h3 k0 _4 ], m  f
    8 m* j; ^( G  I, U  M- E) Gimport re; \& |; ]" P3 s

    4 s' j0 m' T8 E% e  gre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配# E5 O) {, g9 D+ X* t* D! @( j( _
    Out[29]: ['Apple', 'Apple']
    , J+ ?- B8 [4 W2 c+ E1: Z( e( a' H% Q9 w' V. P
    20 ^# w. Y+ F9 L/ L( |  Z& J7 H- |+ ]# M7 p
    3
    & Y; T. @+ \8 s4- P+ C9 k9 A4 N
    8.2.2 元字符基础5 F" R6 U  v1 ^
    元字符        描述
    ) H6 Y- h# E/ u1 A, `: ?7 c.        匹配除换行符以外的任意字符
    - G3 Q/ x+ S5 e) _5 [" Q[ ]        字符类,匹配方括号中包含的任意字符0 @5 G4 p* ?4 N9 J
    [^ ]        否定字符类,匹配方括号中不包含的任意字符
    2 K9 [; C6 s: ?! d: z  H/ z6 m*        匹配前面的子表达式零次或多次
    + h0 r; x9 K( ]8 F* ]# T! ~0 g+        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
    1 q3 Y) s; [! P, m" p! e?        匹配前面的子表达式零次或一次,非贪婪方式
    * x/ w+ d) H0 Y{n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式0 u% @! N9 U6 p) k
    (xyz)        字符组,按照确切的顺序匹配字符xyz
    3 E. ~- s+ B% v|        分支结构,匹配符号之前的字符或后面的字符
    4 G! n, C4 R  P: r- I% Z5 V/ H9 Y\        转义符,它可以还原元字符原来的含义
    : w# c3 J  W& G$ x6 k8 J^        匹配行的开始
    , R& ^8 R; S2 {8 V1 {/ ~9 L' D$        匹配行的结束( [+ g8 k: X0 g0 E2 c) I- x7 l0 a
    import re6 c2 ^- `: J# I7 s" M% p
    re.findall(r'.', 'abc'): ^9 J8 B( ?9 R( O1 C5 c# H7 |
    Out[30]: ['a', 'b', 'c']
    0 E3 ?; _+ e! J& A/ v7 y% r* }
    ) H2 X) G/ e4 R- r9 @: rre.findall(r'[ac]', 'abc') # []中有的子串都匹配
    8 r4 ~1 z. {  u- k* \. ^* ?Out[31]: ['a', 'c']
    - E4 S9 y* H- t+ ?
    9 A) P8 J. Y# Q8 b- Yre.findall(r'[^ac]', 'abc')
    . _- q, `, J" N1 y+ x5 JOut[32]: ['b']
    4 d, U/ m, ^1 v% T0 \4 n- r7 X; w9 l9 m6 }5 ?3 T" u6 Y1 @9 j- C2 h2 F
    re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次7 u/ Q& \6 U2 m) l9 y% K
    Out[33]: ['aa', 'aa', 'bb', 'bb']
    5 z' C7 d  U! N3 f- I# l* l( |' e3 @
    re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
    5 T" M: ^5 l5 g9 [( ]Out[34]: ['ca', 'bbc', 'bbc']
    ' U! _5 U5 a0 w- _7 U3 H8 i3 p' R: k4 X0 H
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
    % _. b0 V8 b& l5 F"""4 G6 Z3 Q" r$ m
    1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。9 T9 A# ^7 h& a7 N9 O7 H
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边
    ' J& u7 T5 h% |3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,% Y+ d: G6 G5 Z( i4 ^$ V3 c: l
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    9 B' o& d8 G$ W, `5 _  R( `2 P" J"""
    1 s% H8 R) N  W1 Y% ^5 g4 v$ G/ K# U1 r. o8 X8 y" t3 C
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。1 Z; a! t4 K- p* s3 K! v! r! T! C" g
    Out[35]: ['a', 'a', 'a', 'a']
    ( Q+ x% v2 n8 \. v7 R& o% W, Y& y' d& n4 h2 P; Y
    # 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。0 d0 }$ j+ z' g/ o
    # 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。* K4 b) b5 Y& {, G+ [
    re.findall(r'\\', 'aa\a*a') # O: s  r# d; s! ^, W/ g
    []
    ! b# {% n# W& }
    $ x8 v- A1 ]) t- @; \2 T2 V7 a9 ure.findall(r'a?.', 'abaacadaae')4 L  I. U2 m- L7 I: M
    Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']: Z' p3 W) k% b5 V7 J* o7 k7 u5 v

    + Z8 S3 D; U" F# Ere.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
    / a; e. {/ w) e$ Z( w[('width', '20'), ('height', '10')]: u2 j& S+ K5 K; ~; X; l& Y1 l

    ! c% [  M0 @$ j! L7 X5 u1
    6 }& z- H5 C' p# N% P2 p2  \# S3 e  P7 n1 u1 A& q
    3' \3 r& O( s) T$ ^1 M" D4 r% r: O) ~
    4
    , F0 h( l+ a* o& X) f- ]( r8 D" c3 f$ ~5. |9 ]3 M3 o1 F: P0 m
    60 }, a& |' e: o& T
    7
    : j* R( z; D+ M3 `84 O# H% Y( V8 I1 w
    9
    , |+ v  ^" ^  n3 |; k, J+ v104 t; ~( K7 z( u. X6 `
    11
    % u% K: B, j# `+ [12) \4 s9 q9 T6 p6 N9 ^
    132 B* D8 B9 R0 v' q( e) x( h/ k
    14
    # Q' b1 I) ~+ |0 A6 s15
    ' r' Y1 K$ J  }2 ~7 a16& h1 {) v0 y8 O( A- |6 [
    17" }3 [7 }+ y' l% |
    182 ]% }6 r/ l# H6 x+ {, H+ t5 Y8 F- @
    196 {$ e2 w1 [; i( v. s/ U
    206 y6 Y' r  f( s- c
    216 x* H  Q8 G3 h( w* E3 k: Z3 o4 i
    222 f: s) W9 I. R4 Q- |; E
    23* m0 g' B! I, n- z$ e
    242 U9 f' {0 q3 q  i& l6 O; T
    25
    3 _7 t1 a3 n; V0 ~1 u$ t26
    ) w6 R2 j& O/ ]  _% ~* ~27( L1 F8 N; T$ U+ D8 ~# y+ e* t
    28  [5 w/ m; g9 H* S0 ?5 a
    29. \  C. k8 K* x, W# k
    30
    . ]) v! C: `- M" v' ^/ C31
    6 x0 j, X2 _2 c# O32# q7 O/ L% u& q- e( ]. U0 E
    33* b4 J" {% f. @% V
    340 |% t0 P: u( S- g" z+ l& z
    359 c3 b3 O) g/ b8 n7 \  \
    36
    + D4 ?& T" g1 t6 U% D/ V- a, {376 U$ }0 z. N1 s
    8.2.3 简写字符集
    & N% ~3 x( J. d# {, P2 I% Y1 [则表达式中还有一类简写字符集,其等价于一组字符的集合:6 M( v$ A; t: g3 @; E  D

    6 @! D7 X1 x/ m; D' `% H" M* q: F简写        描述
    $ @" \  ]  g- O) |8 f' T\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
    * h' Y. {: f" q\W        匹配非字母和数字的字符: [^\w]
    0 F% ~- g+ w5 a\d        匹配数字: [0-9]- X$ b6 A/ f0 r- E& d
    \D        匹配非数字: [^\d]5 v2 O0 `. E1 X. h" K" K
    \s        匹配空格符: [\t\n\f\r\p{Z}]
    5 W9 g# T# i# g6 B/ ?, b. L\S        匹配非空格符: [^\s]; B, E4 o- j! y/ i
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。/ A7 Z7 i8 g+ `; O8 t6 ~
    re.findall(r'.s', 'Apple! This Is an Apple!')& M2 J# g8 v- h8 M3 Z6 o' f8 ]
    Out[37]: ['is', 'Is']
    / e( N7 g* }" g: b( t, F0 B9 ^. E2 r, ?0 b
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次3 h# K! }: h! s5 m6 o
    Out[38]: ['09', '7w', 'c_', '9q']
    ) A% N$ `; Q( ~0 M1 b8 u, v
    9 i! E6 p/ V- Z$ [7 H# ?; ?% d7 S6 Lre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
    5 j) j' V+ O& x) A/ _7 d! EOut[39]: ['8?', 'p@']9 v) |! H+ [7 X7 z
    % w, C# N. s% X) Q+ i1 M
    re.findall(r'.\s.', 'Constant dropping wears the stone.')& K& l; o$ V( g2 `9 W- O1 o
    Out[40]: ['t d', 'g w', 's t', 'e s']7 b3 n6 @* ?6 M" X) o

    9 v' y. [8 |6 ^) y6 u4 W% Kre.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',3 j+ g. y& u9 s* x9 w
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
    , A$ X8 g( w- G( s) J$ B
    9 L3 K+ m: u- yOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]1 @: _+ U- _# u* Z8 t
    : `0 d1 K7 [4 b: b* y2 l9 C
    1
    $ [. J  J1 S' i  P: z! n, R/ L( y" }2
    ! R0 J! ~% g* L3
    ; ]  p. H& p- W( N8 r& c, J4' b  ~9 d0 D/ @6 S" p7 m
    5- U2 U% R( @  l5 w
    6+ w; ~( V  j" \0 C; m
    70 Y& H3 V. X8 x
    8
    3 T/ ~0 x7 `- _; L6 H9 l5 J9
    ; M0 }2 `: F0 d( K! ?9 l10$ Y* q( v- L. R
    11
    ' \. D4 J/ T; T' s  c! [: L: I126 m# G% g  j* k0 i2 \, P
    13, @* U; j- s; a( `# W
    14- i) [6 J! N( F! m( U  u# i0 P: m
    15; [( x  _7 Y+ Z' f
    16
    ; K. z( Q8 o3 {5 Q9 ?; j0 K8.3 文本处理的五类操作
    8 ~0 e$ j0 w, t3 d; V, F8.3.1 str.split 拆分
    6 ~- y  Z) D* c. d3 C* I5 D  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。" i: Q# x" g2 I8 t& H, L/ {9 @
    ' J, [  D4 n/ A- b' @' i
    s = pd.Series(['上海市黄浦区方浜中路249号',
    * m: F; L& }" u8 G% ]. d            '上海市宝山区密山路5号'])
    9 U1 g  s, f3 D  r, n0 g1 @" b0 s! Y2 D/ y
    & [/ {( Q' \) G+ \' a
    s.str.split('[市区路]') # 每条结果为一行,相当于Series- ^- c6 H% z/ n# T. l; l: w* r
    Out[43]: ; C; i/ \, c1 \# ^, U
    0    [上海, 黄浦, 方浜中, 249号]
    4 @! R5 T* b6 _1       [上海, 宝山, 密山, 5号]& V4 x, Q% j# y3 o+ h! I  q
    dtype: object
    8 _2 X( m2 K; {9 n- E
    $ z9 ], C- H8 a: L4 [) Cs.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame* C6 d+ O; U) }0 x9 h9 m
    Out[44]: , F% d* G$ X. X! }2 y& M$ t
        0   1         2
    ! h3 e( L/ f  j. m: z1 |0  上海  黄浦  方浜中路249号
    1 ]/ i- z4 W. O1  上海  宝山     密山路5号5 Q8 W% e" v  x( \+ A; z" N( u
    10 L3 H  Z6 @! z
    2& o7 ^. M" d- [# P: P
    3- z) \0 c- ^: e
    44 ?5 q3 g  `# I/ M; h1 g1 v
    5- b& B+ ?4 A, H2 m
    6
    . Q) B0 T: I1 A2 s4 ?73 y& [( N+ p- R9 U0 o
    8$ r1 ^7 o/ g4 H5 Z" D
    9
    ; h  ^7 U7 O& l- G2 p10; x5 A, O8 m7 T. f& A  O* c9 l( G
    117 P/ C' @5 c* X6 Y" I8 r9 r7 n' g4 `
    12: U& n/ s, C  K4 G' R
    13
    4 x; j& ]- h1 u+ }14% M0 p0 H/ v6 D; L& Z* L, y
    153 Q- @" U5 r( }- M3 l, e' z2 e6 W
      类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:( ]6 v- G& x8 v  A' u/ j8 M) \5 I/ C
    6 d( y+ \* T; v7 \3 n# j
    s.str.rsplit('[市区路]', n=2, expand=True)
    - p3 P2 m- U! `# f0 r6 ZOut[45]: , c7 \& p! R' ^1 D
                    0  N$ r' [+ ~+ w  l( h, f
    0  上海市黄浦区方浜中路249号* ^% b; x4 f3 |4 L' B6 {
    1     上海市宝山区密山路5号
    . b* z- I6 K0 ^6 R6 q9 n1
    ' e' Q+ z7 W* Q28 [  `% A7 Z( O1 J" G  G
    39 Q7 |' w# W1 O0 I6 _' L7 l- }# j
    4* t" z3 |2 c; S
    56 o" E, `/ j' u* X; X5 j& [
    8.3.2 str.join 或 str.cat 合并
    ! y) l/ s: [4 h: K* Estr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    ' K. C4 w& R2 {, b2 a" H. T% Estr.cat 用于合并两个序列,主要参数为:& }, e( K: v, ?; ]" _
    sep:连接符、8 ]9 V6 W8 K+ w
    join:连接形式默认为以索引为键的左连接
    3 E2 E, B+ f' l0 Ona_rep:缺失值替代符号
    . k6 M; u4 ~, `9 B2 qs = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])# g" f. y; a$ S
    s.str.join('-')  J8 P1 K+ x$ V
    Out[47]: # [0 O% c- b: x( G7 l: ^# y! U
    0    a-b
    7 L8 O% s- j& t% T% c& }( I5 k1    NaN
    - z: ?5 ~+ z& H/ D2    NaN" l* Q0 U' B& D
    dtype: object
    % D) f0 j( {2 w8 |$ `13 }7 O; F2 O3 U: j- h% v/ h
    26 j2 p  D/ D$ P+ `" J6 ~! G% e8 U  [
    3
    # x  l, [$ q* |/ ~* G* ]1 p41 ~4 f# W6 T8 q/ ~! l( }) W
    5( ~0 S& G% B! w$ ~# M6 u
    60 R7 c" R0 N- V1 c; D5 t
    7
    / e1 U% p" D  c8 is1 = pd.Series(['a','b'])+ M3 n3 r3 c, d( w" m0 `
    s2 = pd.Series(['cat','dog'])
    # x( x' B" u+ j4 Cs1.str.cat(s2,sep='-')$ M0 l' _% m- t& z/ ^2 v
    Out[50]: ) S8 u- M* H6 F  @4 q0 z: O! ?# a7 k/ o
    0    a-cat4 ]( C& ^/ V' D3 b: ~+ J- W
    1    b-dog& T, z/ {7 r' d/ O: q
    dtype: object0 v. |" Y, s3 m% O- e; @5 z+ J# W# O

    ! ]0 X  A) c- c- bs2.index = [1, 2]9 t9 {$ k- U6 K  I) y- i1 C; j! l
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')
    ) e; [. z3 L1 o2 cOut[52]:
    4 h, }- e# q' N; I$ p2 P4 J5 @' H' |0      a-?
    5 F' ?/ v: h3 I& q: Q4 @# @, y1    b-cat
    6 `) D, i2 _( |% v# M2 W2    ?-dog0 R7 t2 E9 g- t2 y6 E  z" ~# O
    dtype: object: ^' n3 d! [7 @) x3 Z0 d: `
    15 J9 L, {, Y! m' s  t+ u5 g
    2
    + _  Q, s& `# f1 ^3
    ! N" Z" R' B& |4 H4
    6 D. |' N& y) Z( [4 `0 H( I6 u: x5
    5 L0 v% Y2 [5 C4 T% `+ ]61 J; R% s# J: Y( h
    7
    # h5 ^  I7 F4 G8# U0 {8 C: ?0 B6 o& t
    9
    : L9 S; d: p. ~5 c( E% @10( ]; B- k; e6 b: i& A, U4 Y
    11
    + `2 a1 S8 U) L( ^5 r12
    ; ~4 F. Y# i+ n5 ^! _) m13
    ) y9 g4 t6 o; ^* G148 o6 U# C. [7 f0 j
    15# L- @. L2 N+ C  W/ t$ b
    8.3.3 匹配
    6 x- {3 R/ ?8 b6 l6 xstr.contains返回了每个字符串是否包含正则模式的布尔序列:3 S$ x8 r( }  |* g
    s = pd.Series(['my cat', 'he is fat', 'railway station'])& Z; l) a. Y. O; j/ g' M* X) g: g
    s.str.contains('\s\wat')
    1 x  n4 N5 }% y1 N4 @0 L' X( P( R# W$ _  A
    0     True
    9 l. c  b/ F; T4 F1 k1     True8 H; @8 i! z- c3 [
    2    False2 U- Z8 i, o8 [8 r
    dtype: bool7 n) ?6 G  h! o8 z5 c7 v, F
    1
    : A) E" W0 m1 V2 \- g: z! K/ Y, q2
    ! m4 w! x/ J* n" ?& O5 c) O3, Y% i  Q# a' @, S% Y  }
    4# ]( k7 G4 {/ Z, Q; e: n
    5
    . ?: b5 P9 w! q. L* b, h1 Y+ p; ^6
    ! ]! F) f* a1 E4 n74 |( Y& F& B: ^, W+ [
    str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    + I6 p" [% R9 Y: Ts.str.startswith('my')
    , ]" a* [8 ?! O8 A* j6 E: D4 z
    9 f' Y: L- z' k% I+ r/ z0     True
    9 b& a+ v+ C! X9 G8 t0 P8 f7 w6 Y6 V1    False
    0 A+ Z1 Z# j# q- w, t. f2    False& t& [& `/ q8 Z
    dtype: bool
    : W$ j( C( R' f1
    & k$ u0 T% \, m- p2
    % T3 }6 ~# v- v6 r7 V  s3( D7 F8 k& p8 }
    49 w' a) W2 J- G% I
    59 E/ N/ k0 b1 P1 V* D
    6) h( W  J- \7 N) s: t$ Z- _' i# ]
    s.str.endswith('t')
    ! t# J5 K8 @+ J$ P2 Q* F. _& `7 }8 ?. @( v1 V
    0     True
    6 t# Y6 A( W1 M5 g: D1     True
    + `1 t+ q9 s2 a( O9 L4 y2 p2    False
    ! i. p+ V5 L  m/ V3 ndtype: bool
    / ~3 e, A4 C# A) o1 g" x: s1
      V' s' y7 L( _6 e( S% _% W21 h  B0 W0 s( R2 L
    3
    , h3 ]2 e& C! L  f9 A4( R  m  T; W% F2 d2 |. m
    51 O9 Y  Y$ I( d# Y
    60 A0 @& S! L! ]2 e" m" w0 p
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    & U" w4 H1 c8 {' P# T5 Cs.str.match('m|h')
    * F2 ^3 J  ?) ^: c2 x: Q% E7 gs.str.contains('^[m|h]') # 二者等价/ m8 B# q5 {) |, j
    0 l0 p! f& s% h5 q+ L' n2 B' H
    0     True" L1 r8 w( C" r
    1     True
    0 B* e" c5 J( P" M2    False3 b/ [7 Q* n; b
    dtype: bool' W. N( P4 x3 b+ @6 O/ [) X1 ]7 L
    1
    . E6 C8 [/ _3 S+ b" @2
    9 E5 C2 {3 [6 [; h3
    ) D& C. p; g% F46 b: V# p  O5 o9 _6 I& }
    5
    9 N  z) V0 o4 E! y# @6+ S2 t- c1 `) H5 j
    7
    1 K" d  L; ]) h, @7 ]s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配  D/ A$ \) y* x! J! p
    s.str.contains('[f|g]at|n$')       # 二者等价2 X+ q5 h* Z% Z" h7 o' G" l& w
    0 a! V" ^3 V7 A' s
    0    False
    - S6 J& s" X5 ?4 D% r3 a1     True) D- r( H6 N  i; D/ u6 |9 W
    2     True
    & c- y( O3 G; H+ ldtype: bool8 c  D- F* X: Q# a2 ^
    1
    1 b1 e7 r# l$ Y& \. |' ]: {$ e& S; ]( o2! r0 a5 }3 V- p9 K5 z
    3
    $ B( \: O1 B/ f4
    & e( l" U! r7 B5 n  ~5
    ' P) O& L! N: e! ^* ~* ~69 I- a+ K) s( J9 v& _9 b
    74 G; r8 f* v3 p  N
    str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
    " m$ [/ M9 q) v3 M. L5 ss = pd.Series(['This is an apple. That is not an apple.'])
    " Z$ Z- A- J* K. W) k5 u/ F
    / i) B* \6 k" }; d. w6 ps.str.find('apple')
    0 X; W% d* U" y$ h% x9 O4 Q$ JOut[62]: : e9 a9 b+ g( a& e
    0    11
    9 S: i2 @0 d* d* x3 ^8 m. `. x! ]dtype: int64
    . P: l( o4 `. w; [6 N/ n0 M4 E! @/ U: M( d) d! j2 M
    s.str.rfind('apple')
    , C* g" {" [' U- A; mOut[63]: . L8 k2 M, Y7 r- `2 n4 N2 I
    0    33
    4 C1 [) z- }; Cdtype: int64$ i# E, |+ F% n( Q# S* C" A; _% h. Q
    1: A- |7 n# G, o0 X% p* D
    2- g  `' `) X, |+ @( N- [7 c& t- B
    3
    ( A7 o$ X/ K9 \. R& i. k4. X9 x7 [! p) G  F6 o% ~* e
    5
    5 V0 p, R0 v; C/ t7 {6+ d: \0 N/ O" o8 b
    76 M3 b+ \& B  ?. A* J7 p" c
    8
    1 k4 l& u& ^( w  a& G92 f) u2 N" L+ k9 ]* z5 w. N% C
    10
    6 {. H- L2 u' X+ C7 X11
    7 q8 s' d$ G2 \5 \, _1 M; [替换! Q( _6 ]; o! k8 W1 [5 I$ P6 j" m
    str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    2 ]) ~7 f! Z9 P; ]8 n: Zs = pd.Series(['a_1_b','c_?'])
    ! [  O$ m# X8 G0 X& C1 x" Z7 Q# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
    4 @8 H. N0 d- @5 y* [  As.str.replace('\d|\?', 'new', regex=True) ' M- d' S5 R' P5 {& e7 ?

    # {7 L2 T7 o7 a. D' P: b! H0    a_new_b
    & Y, a" i! c" [1      c_new2 I/ ^0 _( g# v
    dtype: object4 P2 p$ z: s1 y
    1: i! d( A  H/ r6 `* A" I
    22 b: d& L/ {6 S
    36 d6 p# C: x& ~* |$ o- R
    4  k! D, s; D4 M, ^0 y5 S' X  `( y; A
    5
    & N: D5 R* O0 h7 R8 F) y6
    8 x1 r) b. k/ n1 T' i77 |$ f4 x% O' C& D
      当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    + l- i1 A; G+ C0 G
    - R; ^- ]" C0 Rs = pd.Series(['上海市黄浦区方浜中路249号',
    % D5 m5 C, w5 w) `) j; I                '上海市宝山区密山路5号',2 H5 C* |) g. i8 o
                    '北京市昌平区北农路2号'])7 P' z) ~+ ~* [; h& f- W
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    - e9 g9 u% `/ L, s$ B; Vcity = {'上海市': 'Shanghai', '北京市': 'Beijing'}  b5 w4 o+ w: D5 b' e
    district = {'昌平区': 'CP District',- _" }  f8 |$ {9 d3 L& W1 n
                '黄浦区': 'HP District',
    ' A$ @1 q3 O( _            '宝山区': 'BS District'}
    % H3 b/ O/ V  R% E% Froad = {'方浜中路': 'Mid Fangbin Road',4 Y7 y0 E5 h' |8 O) E3 X
            '密山路': 'Mishan Road',
    : p7 x! Z, z9 i        '北农路': 'Beinong Road'}
    8 ^' W* u+ p' L$ ?  C% ?def my_func(m):+ \' N; [7 ]- `; p
        str_city = city[m.group(1)]
    - r# K4 I% X* F% s, L9 P    str_district = district[m.group(2)], Q: ~; U! P( b6 |4 m1 d
        str_road = road[m.group(3)]
    ( e; S+ w% ^! i- e- q% s    str_no = 'No. ' + m.group(4)[:-1]
    0 U/ k6 D3 C' @  ~, n    return ' '.join([str_city,
    0 j" D5 W: A0 h1 b2 q0 K                     str_district," Z& ~, v* z6 u  Q# Z+ N( K
                         str_road,
    4 \7 y) q" F* _3 t8 @                     str_no])
    ' I8 f/ F; m% a% A: ]9 m8 Gs.str.replace(pat, my_func, regex=True)
    ( _! C% `2 K% S7 T) Q: C9 p8 ~8 U& Q8 P' t4 I: Z' X2 D
    1
    % k4 t  c5 L. M, K3 k4 O2 E2 q: ?# t9 y2
    ! M" t8 f2 F3 n- H/ @) j3
    6 h% J& P9 {8 m4
    / a& G; o9 M1 Z- i, I$ C& H5" F& W* y6 m" \, r* |: j
    69 z+ d( s9 ], K+ B) H! |
    73 [: R' I0 R6 e
    8
    1 n# @# e% F( N  I( v* J9
    $ H4 T. |6 L6 L10
    5 s, V. C2 p, {+ E2 D11# U% s* Z* ^  G
    12
    1 ~; ]1 [3 E9 `- s, T13& H- `( f2 i% t5 D1 |( q  E
    14
    4 E; e* x( y; Q154 D+ ~. V7 q6 o$ w$ G$ a
    16+ C% ]/ X7 k- r/ h
    17
    # I) U4 Y0 s% T; E1 _18
    2 w: F# [0 O/ N3 o" ]- i' U) }19
      w6 Z3 C5 [9 Q+ {6 |20
    - o; k' j. i7 i! i6 P21+ U  g2 q& S; T& A2 d! g3 U
    0    Shanghai HP District Mid Fangbin Road No. 2499 r7 W1 Z* u6 a. u( y
    1           Shanghai BS District Mishan Road No. 5
    6 r* Z6 J: R4 T4 v: m) h2           Beijing CP District Beinong Road No. 2$ Q3 m/ ~( F6 F( |6 h) S8 o
    dtype: object
    ( [2 `! ^7 T7 F5 p* c0 }1( I  n$ B9 s5 B
    24 s; T* m: o* j. A
    3$ m* V' E% Y* L
    41 m, A5 |7 S2 S% g' K$ N& V
    这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:- O7 ^: w$ i: w1 v

    ' m$ [. R0 V! |; R( y# 将各个子组进行命名
    8 q9 _5 ^4 C* i# O. W! |pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'2 E5 [% j6 l  @: q
    def my_func(m):8 `0 w; \- g" _! w1 g. W
        str_city = city[m.group('市名')]* c6 x! H0 k! V. |/ k
        str_district = district[m.group('区名')], O% o0 b& D7 ~: X/ v
        str_road = road[m.group('路名')]
    ' i" s- D5 c" g  M% H    str_no = 'No. ' + m.group('编号')[:-1]1 z) y7 T1 n* d* s2 j1 j+ j
        return ' '.join([str_city,
    4 w, Y: r: q  K6 _' D* N' Q                     str_district,
    $ J+ s) D( g  V- W7 z                     str_road," |) p# }3 u( g- Q; d; `1 Z
                         str_no]), }& |2 a/ Z' ?
    s.str.replace(pat, my_func, regex=True)" o3 M* h$ @* P3 b% g
    1) A7 Z/ P8 F- |, @
    2$ g! e0 K; h* I" s4 G  U+ y4 S
    3
    ) X( e8 V( _- \6 I* Y4/ {+ V# ?$ p: }" U$ A" @+ }
    5
    & z! {/ ~+ h: Y6
    & U/ O* M$ M3 Q' l" V, n7
    3 F1 d" r! l( k  |4 Z& l$ Y: i( o6 x6 v8
    - Y* Q$ E; k! r9# q$ w" p& u& ]  m
    10
    2 [; z8 I1 ]3 j& F: L  Z115 N/ F) @0 E7 m; Z
    12
    * z4 s* |% j" n6 B% O! p& W0    Shanghai HP District Mid Fangbin Road No. 249
    0 n! p6 g6 n0 d3 Z3 k1           Shanghai BS District Mishan Road No. 5
    6 a( n0 |, r' Y* [% ~2           Beijing CP District Beinong Road No. 21 |% }' @; |* T+ n6 _
    dtype: object
    ! F, Q' l+ |$ }5 N$ D/ ], K0 |1
    + |/ i/ |% w3 K2- ]1 j0 {; v4 ]! p
    3
    9 U* w( ]! e/ a! u- n+ `/ q4; s3 E9 m' p3 S  C1 l, g* a8 L
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。5 Y7 ]) D( t+ l% _+ `( N7 w, u, r1 M

    1 n3 ~) c- b7 R8.3.5 提取0 R# J+ H) O4 y
    str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:  n2 j; x  \' R4 g! Z8 Y* X9 _% G" E9 D8 ?
    s.str.split('[市区路]')
    8 F$ p+ K7 V" N2 E+ ~Out[43]:
    * u+ h- `' u3 k3 P0    [上海, 黄浦, 方浜中, 249号]8 \0 V8 E9 x9 o" }
    1       [上海, 宝山, 密山, 5号]
    4 P6 ^% W: h4 G6 w( Q! @1 ?dtype: object) u' f! M, U- ~
    4 J6 R2 y2 S7 f4 o
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'! K! O' ]" |4 S0 M. E  F
    s.str.extract(pat)
    & J* {/ R) B' y" y" l2 e3 YOut[78]:1 u! a' }. z& l3 ~) T' X
        0    1     2     3
    3 F: b3 n: j+ c0  上海市  黄浦区  方浜中路  249号
    & p& G! s; s! l3 O1  上海市  宝山区   密山路    5号
    4 q. A0 N* b! R3 h6 i2  北京市  昌平区   北农路    2号
    + ^3 `, ~2 C$ ^3 D- t) l: t1
    / e. D) l6 U  G# v6 Z2 |8 m/ b27 ^4 p) x. w2 z$ ^
    3
    " e# h$ @: U# @! m8 V4! K$ v8 R( {9 \" [
    5
    " f% R; A/ x. ^69 h$ v- J* C: N" y
    7
    9 k% D+ F" P5 r% e: W; ?5 X3 ?) T8$ S5 D6 F/ I4 A* z+ K: D& j. S
    9
    6 ^, s' s! j! B; R' o3 Z4 `10
    , ]$ h, F/ W* f11
    4 c8 E$ r9 F' m12" [% y, G' ^) S) O: H
    13
    + x5 a1 l$ u/ j( V/ C. x通过子组的命名,可以直接对新生成DataFrame的列命名:9 Q: \" n) \9 R8 B6 M
    4 l! Y- G* X6 g  t$ E) ?
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    0 R; R( d% K! l, Ns.str.extract(pat); O6 T8 z( J5 p+ b. x9 ~6 v* j9 z
    Out[79]:
    * W4 m! k$ N" M. c  [6 s4 b    市名   区名    路名    编号/ w. }5 j0 N" L* _, X
    0  上海市  黄浦区  方浜中路  249号9 F' a7 f" X$ ^" P$ c1 L* C
    1  上海市  宝山区   密山路    5号
    4 b* e- }/ D4 @4 M& g* @2  北京市  昌平区   北农路    2号
    6 n: v# R5 a: b8 M6 I& P1
    5 ]) ?4 M- s0 R5 ?$ R2
    ; G! ]9 P4 {( x# N) m  P; |39 S% [9 d0 v/ m* J5 _" z' Q; q  d6 D
    4
    % A- Z3 Z2 n6 J$ M4 a54 z! ]. Y! ~+ q0 v( s* C
    6
    ! Y' [6 \8 L6 d/ m( [7
    . e) f' U& t/ Y) p6 D- G) Vstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:( D( h, [7 ?, T; d
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])* `5 _: o0 S; o* L0 j
    pat = '[A|B](\d+)[T|S](\d+)'
    8 r5 V9 w2 b7 U/ H6 Ys.str.extractall(pat); C: E3 ?0 Q+ Y% K+ n! Y# Q8 I
    Out[83]:
    ) V# |; I4 x# y6 h; ]6 ]       0   10 Y" T* c$ W9 {+ `
         match         & E! w; h( Z* @& k( Y, |) _
    my_A 0      135  15
    5 o1 w$ \( W2 Q& y     1       26   5+ J/ i/ j  a" \) N
    my_B 0      674   2
    9 ~8 t& B1 Y1 J6 e2 p     1       25   66 K, E- O3 f# @% r5 J; H
    1
    ) P9 ~) p0 k8 k: c$ k, Z, O# a2
    & `7 r9 S7 F; K  W3
    " }2 {3 T, R7 Q' `  V: |; \" D9 m  h( V4
    - j+ W4 S, ^. ?* S7 d% p, l! U5
    3 P( P9 l+ L# ~6
    5 L* x5 K) K) B: \$ q7
    + T* d2 n( U" ]8 r$ ]. W0 s8
    . w  |7 |$ M4 N; r; |% J  F93 r0 ~7 T: N8 [/ r7 a- x
    10
    : @/ C4 a0 K' @' V4 Tpat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    / G7 ~, i0 y; Ys.str.extractall(pat_with_name), y9 v/ l5 {0 n9 E" r
    Out[84]: * V$ l) F9 \3 O7 p- u% M! x
               name1 name2
    / G5 E* I1 B8 g. |& N     match            2 Y1 p# s* Q' z; Z# D$ A: E) \9 P
    my_A 0       135    15
    + Z; [. n7 \+ X: Y' \     1        26     5
    0 B+ _! V3 P0 M" x6 }my_B 0       674     2% Q' W/ u- w" m5 W6 q& ]# D
         1        25     6
    0 ~7 ~, x0 k; m) N1
    1 V- D( X3 M4 P. Z2
    . V+ V6 O6 ^4 S1 G3
    * m' B, D% e" T2 \$ n! w4
    & r" D. e0 ~: u9 _7 ?$ _5
    1 A- r1 Z! I& ?6
    $ e& e: L, g+ `2 T( a% R7
    0 X2 `) U$ e, \5 ]80 X8 l+ j7 F: v9 p6 w# P+ }9 C% Y
    9& \+ G) s6 ~) z, e- W" r
    str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。6 z9 N0 A7 R2 \) ?/ {
    s.str.findall(pat)
    1 S6 c" j  ~1 K4 N- I! U$ ]1
    - V5 J8 J' g  x) n6 i0 H/ O, Nmy_A    [(135, 15), (26, 5)], Q9 e! G3 H$ o. Q; d
    my_B     [(674, 2), (25, 6)]
    2 _- }: a; K% G3 E' i2 Rdtype: object
    " A: P3 s* `1 |7 U1
    ) t4 @7 a) P5 J# Z+ [1 ?$ ?2
    0 F& K+ H2 `1 [+ x3  ~! [' Q! }3 `) |
    8.4、常用字符串函数3 z8 C8 p. |( ]% D1 c
      除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    ) \  ], q- h* _( @$ g) _* A' C# g% x- T8 D) g
    8.4.1 字母型函数
    - Q3 |. D! ]4 E  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:# U2 ?+ E5 J0 T2 Z
    / t/ \; M0 J2 s* _3 o
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    ! D( N% _/ ?2 K- @8 y. w6 u# r# t3 o& r3 Y
    s.str.upper(): ?: B1 X! @- W8 P
    Out[87]:   A) |! [6 |3 Y' w
    0                 LOWER
    6 X0 h' N( m& }9 x1 W: Y/ D1              CAPITALS, C2 |; U* j1 [, G. n" y
    2    THIS IS A SENTENCE- z7 o' z0 E2 F; l% C8 l% V$ q
    3              SWAPCASE, @- c' k6 ?3 t& o! b8 ^
    dtype: object
    2 Q/ Z" }- O' }1 e% v! S
    & I' n8 f% i. U0 C: ?s.str.lower()' l+ S; M4 G: k5 s( l
    Out[88]:
    % m& I4 b. m) C* h1 q; N0                 lower
    ( n+ g; d6 _7 p3 H, p; D1              capitals) _: i1 D+ m* d' m9 Q$ i
    2    this is a sentence
    & T2 R% O& |+ c/ X7 M3              swapcase' I' |, a3 }/ [; F  u" r& |
    dtype: object6 z* W# t. {; A5 Y' A1 _
    / t6 M9 v7 j4 ^  c1 h9 @# F
    s.str.title()  # 首字母大写
    * [3 l* d; u* |2 c' f. B$ O) ?9 uOut[89]:
    : x; v1 d1 X$ h0 X  r& \; F0                 Lower
      U8 m( U4 i: ?. v2 u2 ~1 `1              Capitals
    0 d; F' A& W" C5 L% E) [  T2    This Is A Sentence' U* U/ O' a0 H' I# ^0 t3 a
    3              Swapcase
    8 p6 Q4 F* }! b1 K. c  Zdtype: object/ c- T" j' U1 V# h1 f* X

    * Q/ ^, _% I+ H8 `9 c+ y+ t0 y* ys.str.capitalize()  # 句首大写5 f4 ]* o8 G' Z) ?* g+ y
    Out[90]:
    # H% A& O3 K! ~( h9 N' I0                 Lower* P. \1 C  }0 o! F& B  B! {' j& t6 n
    1              Capitals
    : W  W' e& c2 t- T* f" V* N2    This is a sentence
    3 O: f" M3 g! M% w" f7 \( _6 c3              Swapcase
    9 J+ a" W6 ~- h3 kdtype: object
    # ^: I4 H8 H6 d+ ~9 F9 G" z) @8 m& U% b- b8 h+ i7 W, S
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。4 o8 Q3 L( O2 Q- q+ L
    Out[91]:
    + f# J+ f# C8 B* i0                 LOWER$ b) g  j3 h1 t( ?# W4 l, A$ r
    1              capitals
    % L* g: g/ [4 @: y" R2    THIS IS A SENTENCE) g5 s! R& S. S/ z5 _
    3              sWaPcAsE9 \" i+ {, Y8 w4 D' E2 N- G1 Y
    dtype: object
    0 C: Z& s9 k  y" l$ X. x! ?* {3 d. `' V
    ( q1 t, g7 l) a3 Ns.str.casefold()  # 去除字符串中所有大小写区别- u) f. c/ o# X7 x
    # s6 J4 [- v' r* Y+ t0 M0 e- u9 h1 }
    0                 lower
    ) [% K# i, [1 K( ~' i& ^' W) w8 W6 q1              capitals
    % v  j- H/ T4 |- m3 d4 v2    this is a sentence; ~6 U7 Z! b  ^
    3              swapcase
    6 }$ y$ H8 C: W; h' z8 w& c" Z  f, B
    " C* b9 H  @, c% L- r1
    3 y1 O8 Z8 K3 w2
    ; c: Z, N$ L8 m' H* z37 U, Z! V% i  t; V+ {; J: ~. y1 @
    4) l* b, h. ~  r; q$ G) @" s
    5
    $ D. A* [* \; g+ A0 q6
    % d, _- I) i- [9 d# [' |$ T" V73 X9 h6 B  a2 Y) d
    8  `" E" I  M. a
    9
    $ A/ C7 N3 R3 s2 x- V6 x5 `% K: c106 {1 t7 [% g. v) i  d0 Y
    11, i/ @- O# v' N( M: I/ O- L
    12" t5 t6 E2 r. H  J" v3 l
    134 w2 P9 N' \7 V, m9 ~4 O6 g$ E8 V
    14
    ' S7 O' {1 `0 i+ d( ]15) ?, \7 J  s4 P' J" @- F; G
    16# u0 l( Q4 p8 u; x4 \
    175 ^' {+ [/ O4 P; y- d
    184 W! i, i# |9 T; z+ W$ Y0 g; S
    19  h' C, u* o) s
    20
    ( R; z. z+ F& u0 X21
    ; _; ^/ Z- C% B; U, p22
    . P( s; L. Y  [) {7 `2 |9 y: k* l23
    4 i! {6 {, S; P0 D24
    6 ]3 y- ?5 Z2 g8 y( t7 f  Q8 t25
    # w1 s* M- ~1 x; j! @: O9 U266 C0 @  E% Y' Y
    277 |+ M7 W/ G8 f5 Q( Q8 M2 a  i
    28
    : v: R6 J4 {* A  [4 v' J$ C# v" \29
    5 c/ g: ]; h' X" I  d  M302 j2 \, ~' Y" e0 N1 |
    31
    ' M. L3 l8 Z" l' ^. T. ]  d& Z327 i  u8 Z, R! z! E, L! [0 o
    33/ R, ?7 m4 P1 R' W2 @
    349 f# A3 ^3 B/ |5 j( T7 F5 s5 o& L
    35
    ( j( S  s. ?0 {: C2 Y- L( T8 ^36
    # f1 i# {) K4 G: R( H3 ?37- L6 M2 B2 u1 S8 n  ~
    382 D) h# i+ @0 n; f" P- o
    39
    # l" _9 d) \0 j) {1 s40
    5 T6 k0 Y, F& K- m0 U2 I41
    ; W  s2 c2 v+ r! |" q429 A. i8 o$ f9 I" J+ c2 n# k
    43' H- ?% C7 E8 w0 I9 z+ s
    44# w1 ^8 V% E# A8 Z. G7 U0 |4 P
    45. S2 A2 K5 J! C( _6 z" t$ V9 z
    46
    2 W  Q# ~# F0 ~8 t( r  k! n47$ s6 f! o) ~) A6 Y
    48
    - n" H+ N" ~, k4 ]8.4.2 数值型函数2 a/ x4 E* a" A$ ^
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
    2 }+ Q, B$ u0 e4 D9 x) \& M2 R, X+ n- O4 u! d9 [" t
    errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:2 L- s- ~6 F1 }* O  g2 _
    raise:直接报错,默认选项. K' [  S% S! L2 v8 J. P0 K1 `# B
    coerce:设为缺失值
    ; o5 a' n% C8 [. ~2 \6 Fignore:保持原来的字符串。$ f; i$ ]* q, o* H; n& s4 ]9 V
    downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。* W1 D9 @( m9 h5 b9 C
    s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])* ?# x; r: D: ?3 ]8 Q; d7 w/ O
    + H0 a& T9 H; M/ h5 S" _
    pd.to_numeric(s, errors='ignore')# M/ a' K4 n- a2 S' h
    Out[93]: % u, j2 B: N& F2 d; h5 Q' Y0 w
    0       1
      j* @, N1 V( H) F1     2.2
    7 i# o8 K9 t  e0 C2      2e" D, t$ J2 ^: H8 F4 C* g4 @2 ~6 J
    3      ??; E: e" ^" ?" x0 c+ E. N5 i$ N
    4    -2.1
    7 ~# F/ n6 J2 \) c& ^5       0
    # Y8 h2 d, p2 x, z* c* v2 c) G- sdtype: object+ }0 q" [: D) K
    6 }& I" h+ }) W9 H6 f
    pd.to_numeric(s, errors='coerce')
    % L5 f7 n% z( ]Out[94]: $ Q% t: Z5 z1 a( `3 Q& J( F% P  k0 Q
    0    1.0
    - m1 \9 ?8 g# G8 ?! y# O1    2.28 s3 d) V9 x" w
    2    NaN
    ; \! T  p# J6 H9 N9 c3    NaN% d! c: ~5 U& Y4 E
    4   -2.1
    7 ]; f8 K- `& O# P0 l, F& |* V6 R5    0.0
    8 W7 k1 y  }, q7 ?dtype: float64$ ^6 z; U$ F7 z5 \; ^" _  o
    ' H7 ]1 o- T, M% \5 c5 U
    1
    0 |2 G' n, k( r4 n( u2
    ! `* ^$ {  B6 X3 ^; D3$ m* g( x/ n* Q. b  s
    41 t) R" I4 C* B! \5 ?5 K/ I
    5
    # h% y& k2 P0 j+ x6
    ( q$ e* ^9 @. \/ S' m6 ^7
    / n! c5 W7 k- t; I" ^5 I9 N8& C; l6 ]: A4 r! K" ], }; o/ h& t
    97 C6 u1 ^+ q2 m2 ?( F5 Q* z( m
    10; a8 H; n$ O9 `
    11  Q5 t) X' `" v5 z
    12+ T/ [" J, z, T6 }5 g9 y# a9 L
    13  ^& Y8 x3 c& h. r( ?
    14
    # V% `% i$ q9 j' p7 K15
    " y# O6 f2 D5 k) D160 t% F. v2 t) \
    17
    7 c) _9 p' y- U7 q$ D* [18
    ; l) F! Y2 o# A2 M- `19
    . X% i4 w5 _* e+ a0 }! d3 a" k* t; ?20& {; S) U: Q" p- D
    21
    ) s2 B( y" A$ p" P7 _$ X, Z; `+ a$ E  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:- V: z6 D5 A) n" U, L2 d
    " L0 i# E) Y( t3 \# H! l( a; M
    s[pd.to_numeric(s, errors='coerce').isna()]& T/ m! T$ [& u4 J  p4 e
    Out[95]: - e& k' ~$ F; Q/ t; u
    2    2e
    # w7 L; O; [: K2 z. t/ P- m3    ??# g, t  b! p! I. W% @1 {) Q) ?( c* E$ M1 P
    dtype: object; E6 R: _0 F2 j5 L  r, I
    1
    ; Y* M7 T, @" Z2
    $ A$ B* J6 b) H  r# E4 ~. T( i7 M34 h& S' p5 c7 V- x/ q+ H( s
    4
      ~, g5 L) i6 [1 ], {) r4 A) R( q( }: X5 E3 {5
    & W! u  v+ C3 ~1 \; `7 e. F8.4.3 统计型函数, z7 E1 C3 A) v  M+ v2 L
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:2 M3 L: U9 [  w6 q5 g' W" T9 H7 a
    + {6 a+ K" A9 S3 W& }- x5 m
    s = pd.Series(['cat rat fat at', 'get feed sheet heat'])! s. G+ [* C: J  b0 U* M2 _$ i
    ! l' \! b; Y( o3 y# l
    s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次9 I  \1 [( O# W! [
    Out[97]:
    + z7 n- O& v( {+ Q) _' b$ U0    2
    ) W/ u- K( C5 j3 N/ r/ E8 ?1    2
    ! I, x1 J  B3 q$ ~, kdtype: int64
    0 g4 R/ c" a1 K; m# z) e8 y: ?
    3 S3 H* j6 W  O; {2 vs.str.len()
    / u/ ^3 i  `4 F$ m5 GOut[98]:
    ( ?* {% g! E$ ?( e; V- p0    14
    & X6 ~! h0 i) f. o# @. r1    19( y; ^4 ^1 D( W( G$ I
    dtype: int64
    9 g6 O" p$ F' B& Q: M1. A* m8 L9 e: s
    2
    . {, t% w* c) ?  o$ ~: P" \3
    . g$ d5 u, R% f- K. A7 V! b; K. u4
    " w% M# O& d; K5 H0 i0 Y2 M56 T6 l1 ~$ F, D) [$ g# u2 z
    6
    0 ]: h( h: ]' R7
    # V: K, I! @: J# l. a8 U8
    # H$ c, |# e' t9! T' p  F) }* o& Z4 `
    10! S& h" g/ G/ |" m* g2 D
    11! Z3 U4 C; F% K4 [- Q" X- V" Q& k
    12' t+ B- N4 w; X1 I
    13
    / E: [) T- x5 a& f. y8 s, C8.4.4 格式型函数% t' Z; {+ W1 \1 K/ b2 W
      格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。4 x  l0 v+ C' w5 _8 Y" d
    2 ?# C( q: J. z. S5 H* j
    my_index = pd.Index([' col1', 'col2 ', ' col3 '])9 i' Y  u* m. T! ^& T
    " X( v) z, C7 b1 o8 q7 o
    my_index.str.strip().str.len(); @" A) k8 H: r  p) Y8 K7 w5 g
    Out[100]: Int64Index([4, 4, 4], dtype='int64')5 [$ j1 O7 r$ G9 A) B6 @: z
    1 D- X1 E) z* l( }2 i
    my_index.str.rstrip().str.len()( S1 S8 e! E1 R7 \) g- W" D$ V) q
    Out[101]: Int64Index([5, 4, 5], dtype='int64')* f( u# M0 G/ X3 C0 c

    0 N  d8 Y+ Q9 e% I! ^/ P# F5 Pmy_index.str.lstrip().str.len()
    # k1 V9 K( l. L6 @/ J9 wOut[102]: Int64Index([4, 5, 5], dtype='int64')$ N) |% E. H# h1 G6 T
    1. ^" o% y. V! h3 J
    2
      o- \4 X; j' o$ p; K5 e) v3
    # T. T1 ^: F. l. F4
    : x2 g  o& b8 G0 E9 `$ F5
    4 A6 j  W1 N2 y- |* N) l: T) g6
    & S: [( v& q- C  i7) y# G" S* X3 l% P4 l
    8
    1 C; G! g' n" l3 B2 ?3 M9
    4 W' p. e3 ?- g9 ?2 {) g3 m10
    . z+ i  |0 i1 `% h  c  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
    : G6 G5 A& N! J% z7 P# L* I& m
    7 i3 x3 q  a9 R: i8 B1 T/ Ks = pd.Series(['a','b','c']): `$ B5 K' E" D- }6 R# |; I

    4 f! T* R& G3 {2 {: W$ hs.str.pad(5,'left','*')! w0 O& P9 o- G; u& e
    Out[104]:
    0 k: G% H( X! M7 k, C. A' S0    ****a
    8 W4 M+ ^% m3 ~7 j6 B1    ****b" o3 _: N) O6 T3 i/ j
    2    ****c
    9 _- M% T# }" [dtype: object7 h& Y; l2 e7 O( q  v3 s( h5 A$ g
    # `9 D3 z! Z! P' y- v" \
    s.str.pad(5,'right','*')0 N% @% K0 [3 i& T& Z  T, O
    Out[105]:
    4 z+ Q6 r# D* S$ s" @8 |6 O8 M0    a****
    1 }" S- a( Y& ]8 w1 E1    b****6 X# X( m  g# s# i6 l& v  M
    2    c****5 Y' a) f  F8 u& d" U0 d
    dtype: object
    ( ~$ y: j  {+ Z( ]& E
    " a" O  T5 n+ `/ h5 As.str.pad(5,'both','*')5 G# l6 C: s0 z3 X3 i. `( Q3 R* Z( F
    Out[106]: * m7 Y9 ~1 e! {5 l
    0    **a**
    ! B0 p+ q% Y8 X4 e) P6 F3 ~) x1    **b**
    " z$ Z" Z3 F  v2    **c**: n/ Q. f+ _4 a. K- l/ N2 ?
    dtype: object
    : B6 I: t9 M4 r- W' I) w  r; P+ @. i; {" D
    10 I$ e- L+ y, K# w4 @
    2
    , b& B  Q5 @. @' t& e3( H" i3 L5 c0 r' v! R1 e8 N( _
    4
    1 t5 J6 `, H( n8 {. K56 J# j6 p- b! @2 J( z4 W
    6
    2 _* N9 o2 m- ?  b8 ~0 @* V7. H# e8 t. f: _3 E1 i+ d4 l6 \7 N5 R
    8
    % u: Q2 v1 ?) \7 G7 f9' u5 A! C: o. _2 d
    10+ \3 v1 r& ]& ]3 {. p
    117 C' Z- A" g; V# D* C  @6 i5 A
    12! ^( S. d- ~8 u+ [% s6 q+ d
    13
    ! f2 W4 s' B) }! w0 E  e) ~14, ?/ c: W: ~# k6 H
    151 A8 k2 ^+ @6 L& |& C
    160 I+ z- R/ x& X* `  c5 p
    17
    $ V. B# J0 R( X2 f& y& g, [2 f181 a, Z. G3 k9 j; ^' U7 Q
    19
    $ @* S9 \  n$ d6 G/ d  a$ t20
    / K' x. r: G( ~5 z+ J21& A# f; M1 f# V$ x6 _
    22
    ' g- E# W4 f0 Z  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
    " L& t, D6 |5 U* f( n+ P# G% ]9 I% s$ F2 o* |9 e! e0 H3 X5 ~+ w
    s.str.rjust(5, '*')5 Y% P& r( m6 ]' _
    Out[107]:
    8 T8 a; ]* A1 S4 @0    ****a$ z" w; ]+ [: \" X. _3 v1 n5 P
    1    ****b! B1 \6 ^( V4 Y) a1 V. v
    2    ****c; G3 F2 ^0 ^/ A: R8 e- v
    dtype: object
    2 m/ C( e. |8 m5 |
    & P- H! l8 O' A0 Ps.str.ljust(5, '*')7 a- p' H0 g: y% V7 K$ J) ~8 @
    Out[108]:
    2 j/ S4 j6 x4 W3 g) ?3 z( K5 N. s0    a****, l0 B3 \0 ^! O7 R7 K  h: v9 f5 {
    1    b****
    + q% U9 \' U( s: M; R- {6 P2    c****
    3 C: |/ Q$ I: ^! `; Ddtype: object/ Q8 c: x8 f! B0 E; j

    ; a, x" @  H+ u$ m1 V: @s.str.center(5, '*')
    ' N, S! }/ `: L8 ?) DOut[109]:
    ' Y, m& X7 O" v9 }0    **a**
    2 x6 k; ^( n% Q) z( x( b5 n* j1    **b**" v! ]6 o' V. G3 j
    2    **c**
    $ O3 Z- H5 ~0 n4 Qdtype: object, C5 |; Y" q" S& l6 N
    7 k0 X7 l: R9 N0 V
    12 j. A+ O* q% U* O1 f; Y& Z  i
    2
    0 H0 q* }5 ~* U  ]$ s3
    9 \  {# E- w& B% w; H0 i  |4& z7 b' ]3 }+ m* u  T# p6 B
    5
    0 _3 A# t; w/ u! w5 Z7 y3 i  Z62 j+ V. i2 K+ u* u
    7
    3 @* Q- ^$ t7 t9 h; \86 l' k, L. r6 L3 u8 u3 L3 T. f9 ?
    9
    8 n# F; J5 `8 {6 f0 Z  r0 o10
    ( p0 f. S1 [* u3 K11
    & j  Y, T  F& T: F$ _/ \8 |. d12: `1 P$ S. M3 N4 q6 Q* }
    13! k+ D& s1 Y+ s- E
    14$ \8 {8 }* @: d2 A8 G& o
    15
    ) p% x, U+ G3 G1 Z4 L: y" ]1 j# f16$ j7 L7 Q+ h$ Z/ @' a7 {/ i
    17' B& \0 X: ^/ V; c; ^
    18* M: h! J( i) j5 F* I
    19
    0 u, X* p- d) R5 ?, u6 ^( s0 V2 b20* f. w0 B! V  d2 m4 k0 l( h
      在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
    % }9 ^7 {1 i4 S) d* [6 p4 @7 R; K3 \
    s = pd.Series([7, 155, 303000]).astype('string')6 r* O( A* G% {

    9 k6 \; E% s, a4 {: ~s.str.pad(6,'left','0')
    4 {0 v" {6 n  MOut[111]: 6 ?# C* t& q1 H" l7 W
    0    0000075 o. z* [9 h% \. q. z
    1    000155
    0 p, @1 t8 F- S" M" t2    303000
    4 J% m7 [/ s7 Z  B. |dtype: string" q" a+ z1 E' p! n3 W8 o
      W9 W5 M- u$ L" ?1 ?& V$ N
    s.str.rjust(6,'0')
    2 J+ l% e- F$ H  F  y& Y! o+ WOut[112]:
    1 T- t, w; o2 s( U, x0    0000071 P) e9 m: J- L- {6 ~/ g
    1    0001559 ]( C' _7 F* V! S5 C8 `
    2    303000
    " o; e( t% R( |: W1 t7 Cdtype: string( P; }+ W; i* O& d$ @8 K) l! g

    " h' ]/ n- c5 s* T* ws.str.zfill(6)  l; N7 p0 C/ U/ c
    Out[113]:
    $ x/ W7 R1 `3 I+ s1 Z4 q! \/ D/ C0    0000070 u$ y( ^+ O( y1 X$ m9 J
    1    000155
    $ M% F' n' _) @# t( o) d% z0 @2    303000
    ) B$ ?  A* T' n2 }3 Pdtype: string* \9 Z5 _8 M' w2 E  w1 ~. E$ o! i2 g

    ' [8 H) d3 t4 f( k* U14 T$ U0 |6 q# p% p3 e/ D
    2# P0 W, p% t$ r9 M
    37 Q* x( r# h4 V
    4" l7 }, K- G9 s# H; b3 k
    5
    % A: O( C' s8 B1 _; n4 c9 n6
    # G& T+ r: l% E: `$ g8 ]/ B+ @! n7/ z; Z9 e9 ]. e2 Y
    8# R( M! V6 V6 l, f/ D1 |0 |. r
    9
    , x5 P. L1 G% `. K- z104 u: c9 l1 X5 S: ?( f+ `
    11: K" i) g' Q/ T, X, g. V
    12
    , C3 o) C: @! T" v3 K, O4 W13
    ; E% T' x" Y' M+ T( D0 F1 i14' O  l! |/ H7 T, f0 h
    15
    4 R$ V- B) F) L$ p  x. `0 g6 F  |16
    0 S3 K/ W0 b( j6 }( D  i9 z17
    . J; C; z0 K0 K- U) R! E18
    9 v5 N  D4 @$ u8 ]% ?3 l19
    % I+ t  M6 B6 A7 H20
    8 ]; N4 ~  k1 ~( \- c) y* G218 v3 D" K5 z, `
    22
    * E* x: U: e# D% R8.5 练习3 o% g. h: O9 [7 ^0 o0 p  A- F
    Ex1:房屋信息数据集- Y. j) _8 W& K6 q& y
    现有一份房屋信息数据集如下:1 D) o, J& S* |$ T" T. b

    3 {! m- z7 [8 n- A5 O* i1 _df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])4 L. r, n2 O1 t3 z# r4 G
    df.head(3); ~! g& Y" }- y) y, q
    Out[115]: # E& ]  s( Y' [5 v+ D
          floor    year    area price
    9 _+ R7 n0 q/ ^0   高层(共6层)  1986年建  58.23㎡  155万! Y, z' O+ r( d$ K
    1  中层(共20层)  2020年建     88㎡  155万
    / `" [2 M; I8 M6 Q! b! G% }5 Q4 E2  低层(共28层)  2010年建  89.33㎡  365万3 D, D7 `/ {: M1 Y2 z
    1" j; n$ G, n: I, e" V  C
    21 a: D% i- |1 @5 Y6 T  z* M4 w
    3
      k& A# q0 O7 q4' F" H' q0 m* m- P+ K1 H$ u# z0 Q
    5
    " e6 f( g: B" h1 y0 r% K6- z2 |+ E, |* g  r8 f2 R# p
    7  @+ X  t5 Y' X
    将year列改为整数年份存储。. E. c1 s" q8 [3 u
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。& y1 Q2 M- t6 q3 B/ w% J# y. P
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数" }+ D+ x0 O. s
    将year列改为整数年份存储。' i' S, }5 m* Y! j, h# k% s
    """$ I3 y" E- v  b0 \' M
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
    6 q# b6 }7 X) ~  J$ O6 `0 Q; _$ p注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,, n7 B; @8 X$ a$ z) |/ J
    转成int后,序列还有缺失值所以,还是变成了object。8 {0 ?: `( s. q: L8 f
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。2 \% n6 [9 f+ s/ ]+ \
    """! Z- i1 M/ O* V: s4 d- o
    df = df.convert_dtypes()6 n4 S$ x, _* h+ A3 R( r+ S
    df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
    6 t: g" v  n+ Idf.loc[df.year.notna()]['year'].head()
    / R" W. ]. t' i) b& D
    1 }8 h8 w; Y5 m: F9 ~2 h0        1986; C$ N, F, G  c& C0 J' @" ]) P" w
    1        20201 u% A9 L8 P8 M5 a; {
    2        20106 h+ T9 \- }/ n2 B$ N5 N; q
    3        2014
    $ w+ U  T' v- c1 V8 ]  A4        20156 p8 U$ k7 u& S2 j! W  M
    Name: year, Length: 12850, dtype: Int64
    ( ^6 U7 F* F& N( ~
    & H& z' @9 v& h. f- U0 P( F19 ~- c& L: s! }6 M
    2. C" W$ Y! N" A& h, w/ B% O0 b
    3
    / ~4 V9 a! X1 X" w. t4
    . ~/ Y+ `  A5 `' f( |5
    . G  B8 N5 L4 z  }" \6* U4 b- s% ]. Y5 F  [, d* Q+ o
    7
    " I: f5 e" k# @- n8
    8 u  d  }1 o( i, \* r2 h9
    " u7 v/ g+ j( O% R- }10; u+ r% ]; w: G7 @. {
    11& ~; A. h1 t7 C% t
    127 k  h# m2 J+ C" I" ^
    13' `4 q4 m8 N6 {- E* B6 U3 c1 z/ [
    141 l# O: u; q$ ?. e, b6 V
    15# X2 V8 `: X/ [) O+ O
    16) O. `" m, W( M
    参考答案:# u. |+ E" l+ ~! c( b) H

    ' S# R" H- g9 R6 y. L$ S& N9 M不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么! f1 U& O, _9 p! N3 a
    - }/ w' u* |) I8 B
    df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    + |& g4 b; o" Odf.loc[df.year.notna()]['year']
    4 m4 o, M7 H& e) V) y5 \& Y6 l; }; b1; Y% {  M' l, N8 q9 [+ k
    2
    0 y9 h1 S2 h0 q$ M# n# u' A将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    $ [; ]: x4 t5 J# h) z4 R0 Ppat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
    " K7 ]" p( h$ x  E) l. @  w: m& Hdf2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次- X  X- z8 O5 n
    df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型" Y2 W( y" W3 t( Z4 G' ~/ u. C' x
    df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
    1 ?) t1 I. u4 d& X# M' {0 cdf=df[['Level','Highest','year','area','price']]
    3 o1 V: X! w* F' `7 @df.head()
    7 K+ m& k6 X/ N+ p( C* T; L) a/ k; ?/ Z/ i+ s7 F3 N, ~$ a( L9 ?: }
       Level  Highest        year        area        price4 _) x$ U2 v: S. q
    0        高层                6                1986        58.23㎡        155万" r- R! e3 C! u  U$ [* [, r2 N  b
    1        中层                20                2020        88㎡        155万! ]7 Z5 J) C, h' A% x( F( W
    2        低层                28                2010        89.33㎡        365万
    : k8 y: a) N# y6 I, Y3        低层                20                2014        82㎡        308万: j& g( p6 \, _3 |# L
    4        高层                1                2015        98㎡        117万
    " c* J7 A" q3 S% c1
    5 a" e# w/ Y3 Y4 z0 ]) }0 Y+ s2( `( Z) D6 W2 H7 k8 A5 w" |7 z9 J( I
    3; m& w! z, c2 [/ y
    41 d) U) N! l8 f4 J
    5& E0 T: M+ y  R3 Z: _( v! w
    6( R) d! ]; Z+ y$ a4 P8 J
    7; j2 g+ O% [2 x5 c
    8; Z3 o5 b+ f% D  }8 V# B
    9
    5 u* o+ n9 H+ a6 D0 r106 g0 w$ K, p9 Z, r7 P& S
    11
    ' f. T4 B. y5 i12; P0 N! \9 I4 J* |( M+ g4 }, l
    13* U6 U. Y$ `. L* x/ U3 L
    # 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了0 b( p, Z( X5 W, R/ t  K5 G
    pat = '(\w层)(共(\d+)层)'
    . P% o- k" E0 Cnew_cols = df.floor.str.extract(pat).rename(
    / x& u( G6 c' n                    columns={0:'Level', 1:'Highest'})7 U, I, L. h1 j: R( R3 }2 G6 a

    $ c* B  t$ m+ B- L. P4 ndf = pd.concat([df.drop(columns=['floor']), new_cols], 1)
    ) r7 P. {! a1 U5 h6 w7 ~df.head(3)# P8 x# u6 b$ w

    9 m) ^; {, ~5 D4 Q' E& |- q1 V7 mOut[163]: ) s' [$ C; h5 k7 e) R; u
       year    area price    Level Highest
    ; U3 q( j  x7 \2 `0  1986  58.23㎡  155万    高层       6
    4 l" Q. m9 ?5 t( ~+ M1  2020     88㎡  155万    中层      20
    % L1 {. q+ l- R5 a2  2010  89.33㎡  365万    低层      282 g+ A, Z6 w( q! L6 W
    1, E5 i  S7 D- _* {
    2
    + ]# \" S' L3 u7 g3 D9 I+ P# R3; g/ h$ E7 L; L4 j1 {) j! |& w4 i
    41 _$ A) `' ]- c! s5 r# N5 R$ }
    5
    ; `# Q3 y  S4 N5 T+ R( w$ u- ?' X6
    0 p) U1 X! V% Q# B) `" g7) f9 z* x( K/ W, q
    8' e/ Y5 M+ P% h* x2 S8 |% E
    9
    + J4 l6 v$ ?& B8 h  X4 Y10& R# n* h7 z+ u1 `; |5 z
    11& Q3 ?  D1 H+ V, W% e' A
    12! Z! J! R, Y: o% l& V# G  B% x
    13
    * }$ ]  @6 F" }* X& ]) |5 B计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。: [- S5 T" R5 R
    """! S: k1 K+ c8 m* R$ E6 c
    str.findall返回的结果都是列表,只能用apply取值去掉列表形式
    1 ~6 o9 U/ H$ h$ X- m) p0 `参考答案用pd.to_numeric(df.area.str[:-1])更简洁
    ) Y" [$ o9 S6 {由于area和price都没有缺失值,所以可以直接转类型
    5 Z: A: ^) V% p! k"""% U/ {3 a7 Z* [" v1 k4 z8 l1 R
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
    ! `! `8 g6 n" T2 B) m" J2 T$ O0 @5 cdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
    $ w% g4 e( u# C" U" _+ T7 H$ X: I3 Odf.eval('avg_price=10000*new_price/new_area',inplace=True)% G. V' b9 s7 R' p0 {6 _( t5 e4 w
    # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
    5 u  C' T. y7 u9 a0 ?# 最后数字+元/平米写法更简单
    * U0 L$ n6 r$ f8 b! R6 |df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'  t( G0 H. v3 B( X9 K8 R% V% }
    del df['new_area'],df['new_price']
    4 M3 j# e% R1 w: U2 \3 E, v8 [df.head()6 o6 C+ ^. A' Y( u

    . i# v, \3 C! f, w( ]) g: |- f   Level        Highest        year        area        price        avg_price
    ! V: v3 T9 `- W' e0        高层                        6        1986        58.23㎡        155万        26618元/平米
    6 p  Y: `! q" ~  [  L$ r( i1        中层                        20        2020        88㎡        155万        17613元/平米
    8 Y+ E- h' n; E4 ~( Z2        低层                        28        2010        89.33㎡        365万        40859元/平米  `& `9 ^9 h$ _3 M: S3 A
    3        低层                        20        2014        82㎡        308万        37560元/平米+ z& K0 v) @8 A
    4        高层                        1        2015        98㎡        117万        11938元/平米" U" Q& s5 b  s" f& v

    ( _+ I, x) y. R. B: R1! w/ a" p# t4 E5 U0 T
    20 L8 P: M$ p/ }1 e+ A: d
    3+ [; i( }* }* y% _0 U- m: T
    4
    1 ^2 j# I6 T! k& }- J50 z0 r4 p2 y9 B
    6
    ( q# Z- R# i0 z( }7 a7 k79 v; ]8 l$ R) h- W. m# n
    8
    ' Z  x4 D" ^2 n3 u3 A9' G& N; o$ P; v8 e% H2 k6 u! H/ J
    10; q' M2 P1 v* J/ ~0 L+ j* G  E
    11, ]/ L8 F! Y6 Y1 o% V2 b: h. V
    120 I# O0 D" `7 v3 ?8 P5 k- r- o, x
    13( `4 U. V: t" D: [4 |$ Q  u
    14
    3 F  I+ b" i; Y3 E7 U' [3 `15
    4 m4 [$ a2 j5 {' ]5 ^8 C16( [! x5 h2 d! X
    17
    ! F# m" h  ~# D; ?, Y18# n5 Z2 H* w* q9 [6 Z1 ~1 R
    19, d; V3 H  e0 _) M; W( k2 z' G
    20% D1 L; w* O/ f
    # 参考答案
      |* R' e: I6 ~* Gs_area = pd.to_numeric(df.area.str[:-1])9 c2 D6 U. {+ n! L# i% z0 h3 }. @
    s_price = pd.to_numeric(df.price.str[:-1])
    ) i& }* ]! G2 Q# f% |/ r: K# e. p+ udf['avg_price'] = ((s_price/s_area)*10000).astype(
    1 [7 h5 a* Q& k                    'int').astype('string') + '元/平米'. o9 W) o* B- X% x

    9 r6 I9 ?* w! R6 w: \" vdf.head(3)
    1 r/ ?# Z& Z# `) qOut[167]:
    6 D: S) e/ q2 C2 p" `1 S% K( S   year    area   price   Level Highest  avg_price8 r2 r1 b! }* x3 ~$ M1 J: l. X8 ^% h9 h
    0  1986  58.23㎡  155万    高层     6          26618元/平米
    2 X4 O  Q- c! P- C1  2020     88㎡  155万    中层     20          17613元/平米" t: v) y& h+ v6 w: y9 U0 V
    2  2010  89.33㎡  365万    低层     28          40859元/平米
    4 O/ N  F7 K. _  Z! q4 L. A14 X% ~+ {, f6 }5 Y
    2
    , H6 P4 y4 ~' ~* [6 E3 }: n3
    6 L* a. U; P! c5 g! k! u* O- J* U45 c3 s! d' e; v
    5
    - T7 h; y4 |$ F" n+ L2 I6
    0 T5 t! M" Y0 n7
      o  x1 ], S1 D0 o; \80 _' Y: W4 b7 g/ m3 V# z- c
    9) b) z* @0 I4 ^+ G; R5 v: D
    10* ?- S# Q& y4 ?, r& I7 `
    11! p# J  P& \4 i
    12
    3 n+ c( d5 n1 O7 |  n# N. ~. bEx2:《权力的游戏》剧本数据集
    % L8 q  ?* B* ?1 c* T+ T. u. z现有一份权力的游戏剧本数据集如下:
    ) [0 z& e/ }- i6 j. \. Y* H7 y. c9 @! P- g! u6 E& Q3 x. u
    df = pd.read_csv('../data/script.csv')$ N0 T5 i! I: C# Q7 ]
    df.head(3)
    6 |0 N& w/ Z! S: t- [6 S% i/ E& i9 |& S
    Out[115]: 0 E3 I& z2 l+ d
    Out[117]: 9 F+ L! F  l/ U+ q1 t
      Release Date    Season   Episode      Episode Title          Name                                           Sentence" |" Q% y7 X+ L8 G! [3 `
    0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s..., @, A, T8 l1 x3 E6 L" q5 I) _
    1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...
    ' b* ~$ c5 N) Z( M; B2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
    + I/ o: c7 r# U. q1
    2 {( ^1 x4 ~4 }( i6 R% g2
      Q, X: c- G9 e# V+ Y- X& x3
    : {2 S2 q2 p% Z" s2 {4
    , D  x9 t1 v  F9 Q7 O5
    5 B" U+ s3 A. h6
    , P5 r5 f1 ?+ ?/ r6 D9 E1 N  X7
    ; \$ n3 [' Y* h* Z% K: U0 F8" w( T3 n. @! O3 z
    9% f2 Y2 i2 i+ q9 C/ ^, Z  K
    计算每一个Episode的台词条数。
    ( z" f9 ]0 C: _3 C( D以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    ( }  W- q; s% M% A) r: G若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。" |' q1 C* c) Q; G' t
    计算每一个Episode的台词条数。/ n# x+ n" y5 ]7 L" r# R' ]
    df.columns =df.columns.str.strip() #  列名中有空格9 Y  g  U. F' M1 F
    df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()% h, B1 g% P& S! J

    # Z- T1 Y  t- D( l" W/ Fseason    Episode  ( r, M5 W. R$ ~: i4 w! g
    Season 7  Episode 5    505
    * A/ E4 Q+ |) j* ASeason 3  Episode 2    480
    / O4 J& l+ t; I8 c5 P9 ASeason 4  Episode 1    475
    & u% I% [6 \0 q* k! ?% Q4 q, kSeason 3  Episode 5    440
    ' ]% i" }/ U: S% ]+ S- S" NSeason 2  Episode 2    432
    & L9 X0 G$ q# J  ?% A1" ~% p" b; C6 R& a4 {
    2
    : ]+ @; v3 P0 o% w9 r3* D* ~1 ]/ u2 h6 q+ q
    4
    8 X( C7 B1 J, K5/ X* D6 ?( s% F' E5 E
    64 j2 r- O7 Z0 T, r3 a, e) B
    7
    " A# A; L* b1 v1 X) P! a8
    ) ?7 \4 ?. m- g  ^* u2 J9
    + h* G2 S! R7 ]# R- M以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    / ]7 W# R4 v3 {. s. g5 z/ S# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
    6 R$ m% {2 f/ ~& j  Y( mdf['len_words']=df['Sentence'].str.count(r' ')+1
    # G# D( T  Y0 C8 E) T* E8 Sdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
    . K. }$ T( f: U! s* ?$ f+ R& p/ T  B- s
    Name
    ; k0 e% p2 r" U) t* ]# ~8 C* t" bmale singer          109.000000' }3 o, i7 j; \0 b
    slave owner           77.0000008 c; j* U- s1 }3 }  Z; W7 C
    manderly              62.000000& |4 ?' Q. c5 K: J3 X# a0 r* R
    lollys stokeworth     62.000000
    - l0 {% e" \5 ~- d  v& [dothraki matron       56.666667& e' ~& e( Q, U8 e+ ?* k
    Name: len_words, dtype: float64
    8 b3 l1 C5 H  d6 k; \1
    ( T' ~7 A& C$ Y& K( u20 \. e1 E  d' B" P& Q
    3
    ) A- F) i& p: @+ q, _# v4
    4 k, Q) l5 c: q7 H! Y. Q5
    & [9 n" j# b7 z) D  _; M- g* D  }$ h6
    : h: Y- n0 w! b* ]3 V4 P77 W. [  W7 \: |6 D
    8
      r: J7 o+ b; R9 _, b0 O$ b$ t; I9  K9 H4 j  C& j) V# k4 ^* N
    10
    * T) I5 X; ~  m11
    ; W- U9 D" x2 A3 F4 z若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。& j8 K  \+ y# W  l. O" X
    df['Sentence'].str.count(r'\?') #  计算每人提问数3 E& J' W# {0 U7 r9 X' ^
    ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0- S2 R  O6 _! X& N2 j
    del ls[23911] # 末行删去
    , m% k! k# _- Z  o/ p1 vdf['len_questions']=ls! ]) z' M& t! a0 ^7 x1 _
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()' h0 |( v$ I/ L* B4 X+ {+ Z
    8 E- z) U1 [+ ?4 @0 x% S
    Name
    1 n6 }3 p/ w7 D! }7 A  c; w$ C# R$ rtyrion lannister    527" a2 T7 Y0 R2 [0 i
    jon snow            374* ?5 J2 B  {5 W% \9 U+ \
    jaime lannister     2835 u6 X5 F  ]$ L1 J4 y2 V* ]
    arya stark          265
    0 A% B. }: e: A5 ycersei lannister    246
    6 T& G0 w# f; d6 D9 F  A& O0 R. IName: len_questions, dtype: int64
    0 ^$ v" V7 p& H- I
    6 R/ R1 r! y" U  T# 参考答案9 h6 t# Y) G' \- p% c
    s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))- [1 L5 Q; S; i; G
    s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
    ! l3 ?% Q% J, ^6 b# N% o: E: E5 w3 f8 C: i2 D0 }  s
    1% Q& z9 E& h4 [/ |3 P, C3 e
    2: N4 `# E7 N' \( s
    33 Z  j; y1 d4 ~% I% o0 n+ S* E
    41 a% [3 k& q" O
    5
    ! n) C9 p1 e5 f$ S. G; _6
    4 C+ [8 T4 U1 V% k9 F3 V7
    # Q+ H  |) d5 ?* f: A# Q8# ?0 B. h" A# s. G; _2 i6 z
    90 {7 G: Q$ n% ^6 \3 L7 G
    10: [5 |5 ?: e6 v0 p8 c1 i
    11& K/ c0 G0 U- A5 o2 s) ~5 d& j
    12
    ; p$ Q9 }3 D! J# S, [! [  Y  I13
    8 h; o  ^' ?" H+ D1 _& k" V; B14& e; s" g. B' ^9 _0 Q$ V6 l+ i
    15# j4 j% ~& X# U
    16
    9 d+ C& ?) |- r$ C17
    # r% K6 A7 E9 d" q第九章 分类数据7 k- g& b4 P+ A; N* g1 E7 X. Y  F" ?3 u
    import numpy as np
    3 i* H! O$ N6 g7 yimport pandas as pd
    5 C0 g% w- x3 ~1, N7 F3 o1 ~$ j5 }9 }: `/ [
    2+ i; u0 @, X  Q4 B8 ?2 @
    9.1 cat对象
    & b7 Q, O* ]3 H: X8 [. }: k9 K9.1.1 cat对象的属性) N5 _( U; v- `5 e) q/ I. h
      在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
    " E( R3 u) i; V/ y/ i3 S' Y3 ]5 @* R7 U
    df = pd.read_csv('data/learn_pandas.csv',4 k2 W; g; O3 r6 Z
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])* @4 D$ \; E& P0 y; |4 o# Z4 k- [& J
    s = df.Grade.astype('category')
    $ J- Z. }* W- n' v: E! i
    5 i  S; L$ O: qs.head(). V6 R3 O  L2 _4 ^0 w' ?
    Out[5]: 9 G$ y0 R/ X9 I, \
    0     Freshman( k$ M& G& J# b. w
    1     Freshman
    # I9 u; t, i4 T! u1 w2       Senior, O1 w) x7 f* h2 B4 p
    3    Sophomore
    " [$ x9 Q" K4 A- `. ~( p4    Sophomore+ `  l) O: P% f! s
    Name: Grade, dtype: category
    ' g" g! u) v% }1 ^1 \8 K% ECategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
    ! |* a9 O% c- V; M, ?# e) y1- T7 ~0 O4 H; j) |
    2
    ) M# j& B: P0 Y/ a3, f2 R- w; v8 h3 N
    4+ w  [0 `1 i% `% u- ?8 o3 i
    5
    4 Q. h" D; ]# v0 j6 Z' k2 L* V6
    / ]8 d; @& l2 j; H) ~0 L) q7  @. f2 D" L& R4 p+ Z/ n1 S
    8: w3 a8 K8 m, n
    9
    2 e+ N* r# ]7 Y105 g6 o, H6 i% D
    11- |. J) k6 ?( O( c$ h2 s! Z
    12
    ; S1 Y: r; U# ]. h; p2 \/ G/ K13, u% P& `, R/ [1 x, X
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
    9 g' X7 }' l" L8 a! _& G; f$ {' r" M
    6 u# |5 ~' B+ es.cat
    # p7 J1 [% U# Z+ N( eOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
    9 t& _9 e: `( l3 E6 T11 b; `, X, l: [9 s( p! m2 o* a
    2
    8 P. w8 }1 b8 j5 N# @) @8 G" Ocat的属性:/ `, x# g& E9 J
    5 u4 @9 m5 F5 K6 C3 b5 s
    cat.categories:查看类别的本身,它以Index类型存储6 a& Z# K1 K/ R$ I
    cat.ordered:类别是否有序
    $ j7 s7 w7 Y: [4 Zcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序8 x# ]) Y& M+ @3 g8 b3 m% ?( a
    s.cat.categories" s$ X, {3 ], _* Y+ ~
    Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')6 W* }5 d. |8 _2 e' I! g* j

    9 [  M% K7 ~9 _1 u, |s.cat.ordered0 g- O7 c4 R7 f4 D4 K: y
    Out[8]: False2 `9 S! Y# `3 x

    + B) z+ \' s" D$ D' e5 vs.cat.codes.head()
    - {1 a) z3 A5 B: i3 V7 VOut[9]:
    7 R* _& X# Q, c) t; `- ]0    08 ?! ?& w0 G& B$ \. @: {; m
    1    0
    + w# {" _$ T) A  r( I2    2
    # }/ @  C$ C4 Z! i' I' X9 d! ~3    3
    ; |# o9 A7 V% a2 N& E4    3
    1 y1 ]; c1 J3 P3 Fdtype: int84 ^9 _) {; S9 q" z- S5 O
    1
    5 W1 e* h5 x4 U' M2 @2; q  H. q4 s0 o8 l2 H: w
    3' U+ |7 X2 m) D* ?% }8 u. `  X! C+ @
    44 F: j9 g  S: p  _% T2 F
    5; w' k& C% N0 U: _8 N; v1 `
    6. }3 ~5 ?2 l' y* Y
    7: m8 u6 X4 T7 ~8 f! u" b
    8
    ' X# b$ k& v" u* V) ^6 o; p9
    1 M9 V, f4 w# R' h" ]7 y10
    , o% B) M( M2 l6 X3 ?111 _' ^0 R  Z3 X, s
    12
    8 x, A5 Z3 Q+ W1 n, C( u2 k( u6 _13
    . {' h1 N: @) D! h- N, y14: }" ~8 U: |* A2 i. n' T, K
    9.1.2 类别的增加、删除和修改0 @9 w- Z- O9 X& [9 I. W9 Z5 n9 U
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?6 o% O( w- a5 a. ?
    , j1 a( {9 `: Y# J
    【NOTE】类别不得直接修改2 w, G$ Z8 N- t9 h1 L
    在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。( O0 E& n- S* B
    3 `, ?0 r0 f; V/ C" T4 i/ r
    add_categories:增加类别5 J' ]9 r) [/ O" ]! G
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
    : `: o! A) O4 F! `s.cat.categories" Q/ D9 ]0 [! f" w/ @- M# h

    5 b: Z4 n& j: {. k7 @" {Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    & R5 u9 d0 H" Y3 j" @% k) m10 Q- ]" v9 ^1 V4 {2 Q+ f
    2
    / v7 W9 l, K7 B1 L! c8 q6 J! b34 z8 p  P: I8 i4 p/ V7 p
    4" r$ C% l& N) G5 |4 U
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
    1 D. N$ f* N5 D, J+ s" s6 e0 |/ H+ Os = s.cat.remove_categories('Freshman')
    ! _1 G) Y/ [; G; U
    ! x; {+ y" e( N1 u; i' w$ |s.cat.categories9 h1 m" j' O7 J' i, \
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')$ A2 }8 ~. E8 i; ^: ]/ S
    5 K6 _' B5 g; P0 i- g  h& F
    s.head()2 E8 s7 T3 @  ]! F3 {: S' x7 `
    Out[14]:
    ' x: ^; E6 u% p6 n0          NaN7 @3 n$ F. q; K* M) x, K
    1          NaN+ K: |/ W7 @: t" n  U6 s
    2       Senior* v% r* ~" H6 k9 R1 ?
    3    Sophomore
    * T! a/ I: R0 J+ I7 F* V4 C# q4    Sophomore
    " ?+ x% e* n/ J" k. fName: Grade, dtype: category  A2 [7 a8 L; V$ i6 b- b
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']: P" Q8 T+ R% S5 Z5 p2 B& q2 ^
    1
    : D1 ?2 U4 f7 G0 g  b' m2+ A2 I. t0 s' n+ U( v5 [% r" w: g9 S' ~
    3
    ' ]: j/ M! y: h, {4
    & G  m  w# ^( y" B- l& K; O5" Z7 U; q( Z' x: c
    6, X0 g# }) W4 y% \% i; K
    7( L6 n0 `; D+ L1 w1 q6 z# W
    89 Y& Z$ J! z( F
    9
    + c5 a+ S8 O+ t# q10
      \: w4 w% G( T* ?  C$ D3 L$ [7 j111 G1 r4 o5 Y$ L' F3 |
    120 L/ f6 Y; h* P5 O1 t" F6 w
    13
    4 D% U, }" D, n. J6 i14
    5 ]4 s& h& T5 m- Z" o; [; F% uset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。; J, c8 Q6 m$ e% Y4 F, r
    s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士/ B" M, V% ?. [0 p- h
    s.cat.categories
    0 [8 E+ K% k9 l- n! |9 N6 @) \Out[16]: Index(['Sophomore', 'PhD'], dtype='object')( b5 A+ h4 ?9 G, Q; q; E
    6 F; }3 E' R) J; i$ |7 e
    s.head()5 \$ ^# C3 r' F7 j4 b
    Out[17]:
    6 T" a3 L4 p1 |0          NaN
    1 j! e2 d/ i; q2 ]1          NaN/ y; x$ }* ^! {! Y/ b9 ?
    2          NaN6 X& D7 F* M* y1 g2 h
    3    Sophomore6 r" f- s" `! u" W' ?( d( `
    4    Sophomore
    ( y0 O- |8 l3 L- h% \Name: Grade, dtype: category
    ; Y8 [/ o) h& L# G+ t1 B0 P, xCategories (2, object): ['Sophomore', 'PhD']* z3 x% v1 R& p
    1" X% a7 G: s, X# c- v
    2
    0 C# `( c" ~- e8 B8 C$ G37 L) i/ C4 g$ a3 x! m
    4
    3 X( J' j3 T) L. l59 o5 c3 J: r  |7 D
    6
      }6 D8 X, o/ A7$ v7 Y1 y& P( e7 b% T$ d
    8. Z( f2 m! ^3 J- d( d* ^
    9/ `' V" c" z" C7 q
    101 m! J$ P9 `3 [$ b* v
    11  v# ]; J3 l7 Y: h8 [
    12
    4 g- ?0 b/ b- X. y4 m/ n+ R13; |5 M% r# D1 e* ]% `+ b" |& M
    remove_unused_categories:删除未出现在序列中的类别
    2 A5 K& e! R5 |, s% @s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别* F& f5 f: r2 {5 |$ A
    s.cat.categories
    " W0 L9 J. A- y& i! `* ~$ q, b+ n
    + ~& a" D' l( fIndex(['Sophomore'], dtype='object')
    2 |& D7 l( s+ R4 \& z1" R+ I' e: }* _
    2% N! `; W, |: Y  L/ D
    3
    : V1 Z- ^& a" j4, e7 n" P% w/ |+ R
    rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:6 x, j8 O/ m1 M! q% e* [% I
    s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})2 L% P$ M. d# i/ D; i0 A4 W8 Z7 j
    s.head()
    & ^2 ?0 z6 j! {9 {3 g. d& F. h
    / B. Q/ _, u) k' u) v; h7 `0        NaN4 o1 U! D( d( n
    1        NaN
    ) [: Q2 Y/ V7 M  g3 g9 j% W2        NaN
    ; x* P$ C6 X  r; j+ s" m* `3    本科二年级学生
    + @; D" q6 i$ g( Q: _1 F: b4    本科二年级学生
    , H+ Q8 Q  q# z% Z- |Name: Grade, dtype: category& S* @6 c. j. |; I+ N0 t! J0 T# h
    Categories (1, object): ['本科二年级学生']# L5 t* ]3 E$ j7 I$ l1 e# U
    1
    5 e: A" X5 E+ A0 a& u4 Z; j2
    : L& |/ x/ K+ u$ T30 t2 P5 g' V7 w  c7 X
    4
    & c& T  L- y6 ?# C; g51 `% R- S5 B) R& E$ d4 ~$ o
    6
    / }1 s: @  N% Z. f3 t. A7  ]6 v  b+ N4 W0 ~0 t4 ?& G7 g
    8
    9 i" u, p1 p' j; N9/ I2 d; y% L" w2 S( b* i3 P6 |
    10  u) x9 H' P4 b! z1 @
    9.2 有序分类% E1 H$ U2 ~: V( c) S/ i, U/ R, r
    9.2.1 序的建立
    ( w. O, K" Z' C4 {$ j' B  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:3 V2 x  K7 e5 W
    + z5 d1 S2 |# S2 p8 i
    s = df.Grade.astype('category')6 M! o6 U* e/ F& d) k4 j& z
    s = s.cat.reorder_categories(['Freshman', 'Sophomore',
    , x( [) J7 s0 N& G1 X                              'Junior', 'Senior'],ordered=True)
    " V; o+ `9 z) L" A4 ds.head()
    ' |. z( z9 N" n( `2 [# @& c* @Out[24]:   Z  v2 R8 t& i& l
    0     Freshman/ ^# [4 A( y- [9 W3 o% f1 t
    1     Freshman
    ( ]3 j& S( S/ Z( H2       Senior; {  g2 u, ?2 A
    3    Sophomore1 Y5 X! A4 ~& z* m& D
    4    Sophomore
    6 ~9 N1 k2 M+ W6 \# xName: Grade, dtype: category0 w2 v2 F# h$ S+ ~' P
    Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
    ' Y. g% D$ |1 _0 L/ s4 L' r; H5 r" c  h
    s.cat.as_unordered().head()9 V. y/ H% J: n: A% r
    Out[25]: , `- g: g, ~1 k( \1 }' Q
    0     Freshman+ t/ [* E6 K* r' R0 s
    1     Freshman& L( t; ?# ]' @* v3 M9 l+ r
    2       Senior
    * a5 a9 Z8 ?# r. q4 v7 Y' m3    Sophomore
    4 b0 B& `: f6 W, j# O) F8 a* _& k! T4    Sophomore
    . r# z- g; a& \* w5 I, T8 KName: Grade, dtype: category
    ( H' n: G1 T! m% |Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']; b/ E* Z+ Z7 ?  X: n1 h/ P0 [

    , Y1 f0 m9 h2 L. p1% |/ O$ E: f# }, S0 q; @
    2  ?' x7 }  t% n) B
    3
    . l% u7 e! s2 s* r4" z9 {# w1 W1 p8 |/ M1 j3 y
    5% H6 U) N' D4 `% G2 T% e8 z5 f
    61 O, b, N, u$ b* c; L6 f7 q
    78 Q( R. K2 I! C, Q) A" w
    8
    . K4 A6 K' _! f. ^9( ~9 l- }' T1 ?  H
    10/ ]9 P/ Z3 d9 u5 O5 m' ~
    11
    ' i3 |5 ]& P- U* m$ P& M  ~1 ~12
    , ^; D, [6 T+ G13
    ( W1 n: N& O/ L1 A144 G0 ~6 }9 y2 ]' w. \' L8 w
    15) X6 \8 r* {; e7 e( s6 R8 u3 }
    16- h7 _) |8 [' O: K! V
    170 K, C  n; w( @
    18
    . k* t8 b6 h" c& ?2 `19: j, O  J$ Y6 n$ P% M" ^
    20
    2 ?( L) q1 t& Q- G21
    ; s0 z& d/ z3 ^3 T! y22
    ( T2 L& x. n  e+ U& M! H  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。9 _6 ~% g$ _0 ^4 I

    + @: j9 B1 @8 Z. _9.2.2 排序和比较
    + s; s+ {' G  H+ A7 x4 t1 i! s$ @在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。; P* v* z: B5 ^/ A
    % ]( d0 |- o8 j: _+ h* a& ]
      分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
    " m' x5 N5 |8 w% A6 ]) w2 ~7 C* u2 R# c: c9 b  n  `- [( G! U4 T
    df.Grade = df.Grade.astype('category')7 `! x) f( C9 a0 E, f
    df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)! m; H1 s# ]* e. R& |  p; j* M
    df.sort_values('Grade').head() # 值排序
    8 t/ j, i9 Y0 B  G9 NOut[28]: * ?% C% {  X5 h" x- G5 Q4 z
            Grade           Name  Gender  Height  Weight
    2 Z8 V# c- x+ x- q4 n0    Freshman   Gaopeng Yang  Female   158.9    46.07 }& O( ]1 s  z/ l0 u9 G( O
    105  Freshman      Qiang Shi  Female   164.5    52.0
    6 |9 ~/ V9 }+ U96   Freshman  Changmei Feng  Female   163.8    56.0
      o9 J* e( u+ b& j, Z2 g2 Z88   Freshman   Xiaopeng Han  Female   164.1    53.05 Z/ t) s6 c7 B
    81   Freshman    Yanli Zhang  Female   165.1    52.0
    1 z* n  \) a" d" p  Z
    , S8 i( k7 ?# G7 t7 U$ ndf.set_index('Grade').sort_index().head() # 索引排序
    % \' @2 E7 x  c. ]Out[29]: 6 E/ B  j5 d0 ^( s# ^
                       Name  Gender  Height  Weight
    ' m$ c- X6 O2 BGrade                                          
    # S, ]& O) b, c5 _4 r: cFreshman   Gaopeng Yang  Female   158.9    46.0) q$ @4 ]5 z. @3 g4 l0 E$ t9 C
    Freshman      Qiang Shi  Female   164.5    52.0
    3 L1 w- I/ G( U/ |5 D& qFreshman  Changmei Feng  Female   163.8    56.0. W& D+ ^5 S; U7 u1 e+ A
    Freshman   Xiaopeng Han  Female   164.1    53.0  L; N, }) n, U3 m  b; G8 ~
    Freshman    Yanli Zhang  Female   165.1    52.0
    ( h$ Q& c1 f" R- @) ~0 \2 c2 I7 {# Y. U
    1
    , P/ ]* [. s9 f" j4 D4 n5 j2 G( f2# i- `  H2 k* @. o* h( J8 H9 I6 I
    30 [1 |6 q9 O, d2 ]$ w; w8 A
    4
    3 `3 i' v4 K: w6 x/ L56 e1 y* I; C4 P+ ^: _1 i$ ~
    6' y2 [9 o* {0 Y4 ]0 ^
    77 ]: @1 B  j6 t5 A2 b5 M/ R8 O
    8
    * q: S" Y' c# S  w9 d! Y97 g9 M" l! A" N8 X& J5 \
    10
    2 M7 Q+ ~6 q0 h# q; v+ D6 }' M. T11. L+ N( h9 h, @3 B* {, k4 C4 i
    12
    9 l) l$ L7 t+ W' P' Z( N# Y13- S* c/ C0 F) v* ^6 ^9 N* ?
    14# [" F4 I. E4 B3 b' q: b. k6 y% S
    158 V2 R1 J: i9 Z7 o  L: u5 b2 n
    160 s' L% M# c  k
    17
    * X. F- w/ m7 [# ]8 j7 U181 Y: |+ b  A7 O
    19
    . z0 S: f7 w' i( J/ h$ t20
    # i: e& O* K, D+ n8 f  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:  G4 j* X$ @9 L

    ' Z; @/ `! m& P1 u) S==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)! f  s' e# ^, o8 S$ i
    >,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    . ~$ ^9 \- [/ ]5 q0 Ures1 = df.Grade == 'Sophomore'" T; N" b* r5 I% Q: ~: W

    , \* k1 Y# P1 B) _res1.head()
    : J2 `7 u: G5 i2 u7 L9 k, M3 X! IOut[31]:
    . o' O. C+ w4 |( r0    False# O; ?- Q1 b& O4 Y: {
    1    False. Y% h. g! R- B8 @
    2    False7 `8 F1 b+ q; E% a8 C: p$ U4 Z
    3     True
    % m+ v, Z6 J$ {& ?. Z( n  H7 k, L4     True
    " U# v4 K. o& Y) V+ |0 k5 h' g* L$ j* OName: Grade, dtype: bool
    2 ^6 `. h* `( t- J9 i3 g0 n3 E( W/ |5 g# g  c, ]) g
    res2 = df.Grade == ['PhD']*df.shape[0]
    " u# R. ]3 |+ l+ s! U0 F4 a8 |' t8 d
    res2.head()
    " z& L' I, e+ r& J5 o( SOut[33]: 8 N7 C* M# y0 l
    0    False
    ( U2 {$ E) H* H8 L0 b1    False) J4 F/ L  q- H% k4 D1 k6 G3 I
    2    False) A, M/ O, v' e0 N( @
    3    False
    3 a4 j5 p' p1 c0 O1 q7 Z4    False
    ! F; q1 B' [- e* h/ R1 j* j3 JName: Grade, dtype: bool
    ! m& m  `" B0 D$ k! D" T7 m' f  _1 C6 j
    res3 = df.Grade <= 'Sophomore'& M0 }+ S+ g+ ^' E% }
    ) i' [$ \& N& W6 [- w5 N  V9 x7 _+ z
    res3.head()% [# O& v' a) D- K
    Out[35]: " v( Y  W: M3 U' D3 [
    0     True
    , I; N) V* c5 J, r- n. E, P1     True
    . }! h% A$ Y  C4 F0 z2 r2    False
    ' V) w. [0 Q( [, `% u6 x3     True  a- U$ B4 x3 r) J  d) v' r
    4     True6 F9 G0 f& ~$ G0 k% |
    Name: Grade, dtype: bool& J0 v, z2 T( K" B$ d

    ( p8 n, Y/ C0 A1 `! Y# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
    & i, e4 v  w- o) I. c1 c7 lres4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
    3 @+ ^) v- B6 r( W, y1 s7 L; m5 ?. S& N
    res4.head()
      H- f/ ~$ |8 m1 G# ]7 N8 f) u' OOut[37]:
    ; ^0 ~. N" z* |, k0     True4 B" Y  a: P) s1 l0 c3 T& q- i
    1     True
    . Q% o: U% p  t% k% f% N, Q+ D* A2    False" ~9 G: ~) u% P# A9 p! P
    3     True8 S& ~1 M+ s! u6 u( d5 \$ L
    4     True
    ; a8 K* h4 h3 z5 R% O. N4 hName: Grade, dtype: bool, H, w  O. P: a2 |0 e' d2 j
    2 V' Y' m+ w" ]3 h# Z! z
    1; t) @* M: b0 H! |- w
    28 N" t, L0 L& e1 J6 ?! k4 M$ a
    3
    ! Q# k' Y" a3 K1 _& M: c4 T" F; ?4
      e/ k6 d( U% }' M  K# y57 M) U; q! _3 \
    6
    % ^2 D4 b! ?# x7
    1 u/ ~9 r: q( J5 j9 ]3 T8
    . K( A# h4 j4 u" R9. x, ]$ ~: Y$ B0 b6 ?! y% w  ~: k7 L: z
    10) N" B; j8 A9 M/ L, H
    11* V3 n4 V9 U& m! p7 i
    12" |3 }9 B9 n3 n: s9 q6 `
    13
    ; d8 N6 }2 _7 b9 t* T6 Z14; u# E$ x/ A! }* \6 P0 j
    156 [: u, Z0 e$ J! b0 w
    164 @! J" o% m; u' O
    17$ T. u. g$ ^- }) x
    18
    ; {( O  C% v2 }% |% r19
    : M4 P" P1 p  p% b5 M2 _20' l6 m3 v- X& n1 @( R) @- e
    21
    7 b- k7 k! D. Z1 O3 d" A! \22
    ( [7 O% J1 l- x% p, u( f+ L  i$ m! J23
      I- I$ v# K3 ~! w24
    * `4 y8 z0 x1 z1 {25
    4 J8 G) ?( a9 E! ~3 @9 L( e26
    1 z$ b$ x2 V* [; [27
    1 }4 V9 F1 y) K) |) L- ^28
    5 ~* j  L1 l' g& a) r; B( Z296 X. D/ V& R1 m5 ?! |3 \4 v
    30/ q( t4 b3 [( p! `
    31
    9 G( X9 W; Q' r' x& }32
    & |! p5 D9 Y3 U8 P, F$ @33) P1 w- A, e* A9 {8 z& y
    34
    + N$ c4 b- G. S7 ~( a4 I2 d352 h2 b  \+ C! j& w$ i2 c' f
    363 I& f1 j( ~/ ]! p
    37
    2 \* A6 w8 Z2 x$ N- Z4 h; A- `. {38
    ; C( c& N' z2 I* J$ y; v39
    % w6 V/ X2 Z" ]6 Z! w+ S5 d40
    # c- Q+ g! _) T, k7 P# z416 o8 l. w0 L5 t# b$ Y3 p
    42+ `$ W3 a2 s4 m
    434 D4 G7 p; T% e
    44
    ' s6 D, |7 b0 _1 j9.3 区间类别% [: ~$ |* p. ~
    9.3.1 利用cut和qcut进行区间构造0 h! l7 T& k, S1 u! d7 v' R
      区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。  \# @4 m+ x0 `! @' w# b( y
    5 P6 k2 K3 U4 Y
    cut函数常用参数有:
    0 Q) h9 L+ t* _/ U  cbins:最重要的参数。8 w8 F# D( Y- Y9 K1 Y5 N
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)! F" \- T- K' U8 A  y, f" j( W
    也可以传入列表,表示按指定区间分割点分割。
      e! Q2 P* ]2 C6 _& n  d4 p  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。, {8 q) N6 a& ~3 o$ W# h' L* o
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
    & s$ I$ r1 K  p7 M
    ) r9 V5 ^' P) ws = pd.Series([1,2])2 A- n' \* n0 ^# |" j7 m
    # bin传入整数' K7 H' w4 S# E/ M4 `

    " g& B, s* v, ~9 u$ dpd.cut(s, bins=2)
    ! P. B' s3 z2 p% z1 U' z' eOut[39]:
    3 Y' V: a8 N' v+ u4 z6 ]0    (0.999, 1.5]
    4 u) d: p. D, ~# ]1      (1.5, 2.0]
    & C8 h4 Z3 O# s1 @dtype: category5 {; h( r7 i& u  x
    Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    # s# K/ `3 W, f) m! L/ ^" t4 Y9 U+ E$ ~0 H  N  G) n+ ^. S
    pd.cut(s, bins=2, right=False)
    * R, c+ Y4 N! C& xOut[40]: 8 N* r1 N. v3 C/ J9 V' f4 F
    0      [1.0, 1.5)
    2 M1 p# R- O6 j' |1 g1    [1.5, 2.001)5 E+ z% C+ R, M( t/ V9 ]# e
    dtype: category
    5 r4 D8 o* t7 x! x0 K" a, DCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]; N) X# e# m, q. D1 t5 m; F

    0 w' B* m* u2 L6 z0 Z( K+ I: G" _$ h( l  l$ r
    # bin传入分割点列表(使用`np.infty`可以表示无穷大):2 ^# F, P* m* V" M9 t
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])+ F. Y% U- b* D' O
    Out[41]: , ?3 e) y1 c6 I' F! C/ I
    0    (-inf, 1.2]5 c6 B, v2 k$ s  x
    1     (1.8, 2.2]! g) j" E, [$ m, z# ^2 z! k
    dtype: category) J5 n. E% O( z# _0 L/ ]8 K) k
    Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
    ) x& B- x$ M. Y1 i3 a" n, u3 j# j
    + x9 i. z) w( c8 P+ ]14 D, f1 k8 J5 P9 J- `7 `  Z
    28 K/ ?* L- T8 }8 t% O
    3
    + h, k( b- }% Q, ?. O7 E4( Q$ |6 ?) G* J- ^1 y) G) H# W! g
    5! b8 `* i% }9 i( P" Q
    62 O( x9 p: w3 u; W' t
    7& |) Q5 L, f* m
    8
    ! D/ R) ]8 u9 x4 h" X6 M9; n! P# \( G" R; i( f" j
    10
      X* Z5 \2 U+ D( P, w# I; ?3 O11
    , D' |! V7 H7 R8 I12
    5 d7 g5 @, \& m3 \) f; `13% n/ Z3 S8 e5 [" Q) Y3 u. K
    14; D4 u- u: u* @. A5 a/ v
    15
    ' z! @2 t0 y  \16/ \; n5 `0 L0 S) H$ ^
    17
    ; @4 u# d. c; N* M18: f5 o# m9 O( D7 v0 q
    19; x7 D5 Y5 j$ V$ k" S/ V
    20
    / E: K/ ^) M- f& p21
    ; |8 o/ W4 [9 s22
    9 n8 L. ^% r; P/ X3 a23, v: _6 h+ F$ Y7 [' H5 m# u
    243 v0 }1 R- U- D' o- a
    259 j0 x% q. b3 m; h+ ]+ [7 C. r5 `9 K( e" g
    labels:区间的名字8 \% r" j5 h% y. }9 Q2 [
    retbins:是否返回分割点(默认不返回)9 U( T: {9 E! F+ D7 o, l
    默认retbins=Flase时,返回每个元素所属区间的列表
    , b0 k* H% j0 uretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
    - o/ L! p9 o* D! E! f* m7 E6 W
    8 f0 Q$ e7 y% F& d' L3 x' M2 \s = df.Weight
    * }- k4 Y# C7 O# [. b( d) Ires = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)2 V0 j1 m5 v( w* y) t
    res[0][:2]9 V4 f! {( P+ C( v5 z
    ; K3 S- O2 N9 A4 `. f
    Out[44]:
    9 t! k7 m7 t2 u% I! n0    small
    ) e( h, H, X3 i1      big
    ) J3 d* l; Z4 [9 rdtype: category
    " D4 v! n! ]7 J- J* L% qCategories (2, object): ['small' < 'big']
    " b' y2 B4 ]* R+ T2 j0 t5 K6 |: Z/ q3 s( H$ W
    res[1] # 该元素为返回的分割点* Q/ a& f& q6 Y# H
    Out[45]: array([0.999, 1.5  , 2.   ])
    8 h. L; G7 x& I8 x" i7 d1
      D# U; N" e5 J2
    6 O' ^; }7 v' E3/ r& v& m/ q# Y9 a* l$ [3 d
    4
    * M3 {4 O5 N1 F1 H8 ^* j5 h& u5
    , }0 x; E5 S8 {4 ^" W9 H  v  F5 A: ]6
    * b' d/ D1 z+ Y9 ]% Z: Y+ q7
    ( r$ U) C- O$ G1 I8
    , W* P6 }) J  d3 C& T9: S- A- i8 p& v- e7 m. c% K. \
    10( w& Z" ~; M# v& b" L) U
    11
    : }. x% k$ K& q/ U+ V; S. A12
    0 k+ |' Y7 I% J( Jqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。! p) E( f0 G3 P5 `( w, g
    q为整数n时,指按照n等分位数把数据分箱
    1 J9 _' m& C. W7 D; ?  U. h1 Mq为浮点列表时,表示相应的分位数分割点。
    ; K; \: m3 z) r) {" d5 Q: L0 [$ @s = df.Weight
    # B2 }6 b" i3 J% k4 m4 w* t9 u) k9 u/ a9 R! m6 E; j
    pd.qcut(s, q=3).head()$ u% R: u8 t: p: N" X5 B
    Out[47]: 2 [+ F: j! k- q  n- A+ d) b+ ]
    0    (33.999, 48.0]
    2 f% E6 U  G# ^1      (55.0, 89.0]% q+ J9 g9 R' U
    2      (55.0, 89.0], j" V; K8 f# O- n! E; ?0 u3 W
    3    (33.999, 48.0]
    / ~' t. f% F1 j$ i" s4      (55.0, 89.0]
    8 x9 ]7 D3 M$ _5 ZName: Weight, dtype: category' W% ^+ ]/ t# S( b+ u
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]: R8 d4 X) x/ z' d1 Z/ o
    . N$ D+ J' H* Q$ a& |" N5 z
    pd.qcut(s, q=[0,0.2,0.8,1]).head()
    : C* c4 u' B5 V. g% a, VOut[48]:
    $ s) B4 q* S; W/ Y0      (44.0, 69.4]4 m4 g8 |" Y" H4 x6 _& e( s
    1      (69.4, 89.0]; @+ h& ?8 E, K) g4 j" I2 \0 ~
    2      (69.4, 89.0]
    + Q( B, R6 T4 z7 @3    (33.999, 44.0]
    * t8 S" |* _  o# O4      (69.4, 89.0]2 m* g8 z$ H* V& L0 V) p
    Name: Weight, dtype: category
    0 j0 D) X) p  n8 V; D& y2 SCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]
    7 t! U) m/ C6 l0 O- b
    " W! ?. Q: F" H2 ], P1
    , Y, W8 b5 }& J* k2( s6 B. S, y4 d% u& X2 j
    3
    8 t7 C4 T  [, G46 X$ W- k% l0 _4 L
    5% V) V. `2 `  N
    6  Q. E4 P0 {7 Z3 x0 f
    7
    5 f+ D' B. q. C. r8
    ( ^& x5 C% K% h& A1 q9- w# O7 P6 q6 y- ~
    10
    2 P. _1 _) I$ J9 D0 V( P+ T# C11: M) n5 D3 M: |3 ?! y
    12
    * P  {5 a! ~- X  m7 `13
    $ W  p/ {: K- V. C, Z14; [% n6 e+ K+ ~) Q, Q) X% v
    15
    2 j. o8 U' `& s16  _) u* v* L- ~$ w! z7 s  r
    17+ ]% t! \4 d$ v# [- Z+ I
    187 X7 g' [: n0 F& ]7 v
    19
    / ]7 A0 v% ^( r2 @20# l/ Q6 R- }4 q+ u8 A" K' T
    21
    % T/ f! E8 X7 d5 z& o- d9.3.2 一般区间的构造
    3 ~5 @) K/ |9 ]8 w! U. e  w  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。+ N) X  F& J4 {  b; @' Y4 }, O

    ( F/ W8 E" X. c( X% p5 g/ i# ?% Y( J开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。8 w' Q7 T+ ~/ K6 [' k: o& Y3 V5 h
    my_interval = pd.Interval(0, 1, 'right')
    , s+ C& J& m8 _0 T& P7 o( u4 l& [8 ?. S$ S9 O* d
    my_interval! }! ~, w2 ^; V7 C5 T
    Out[50]: Interval(0, 1, closed='right')8 `0 D# K& k( p; o" O
    1
    ) H7 b: ?/ [# |3 P  t* b2: N0 v; X4 p2 ^; ]3 J. y1 R" l
    3
    # r) D, x: |7 s! o4 v4
    3 t9 j) S' W% q  z5 u8 n% ^/ P/ M区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
      u- N6 e. a, m0 Q8 D% F使用in可以判断元素是否属于区间: [' \! d) g& N. D9 i$ o
    用overlaps可以判断两个区间是否有交集:
    ) H8 F/ s( z6 ?8 D+ [8 ~; `# A0.5 in my_interval$ q$ `/ C- c5 g2 D

    ; v! X7 r; x+ a$ n( n( mTrue1 t% e" H: m& q. I
    1
    5 h3 o: z7 d2 g$ P: h4 X, I% E2" H" T! T  R! d4 Q' |" v
    3
    8 F2 z3 i" J2 p* Z5 Pmy_interval_2 = pd.Interval(0.5, 1.5, 'left'); z6 M! ~+ J  v" A2 H7 u! A
    my_interval.overlaps(my_interval_2)
    , u6 S. V9 D: H3 [2 o
    ; G9 P, c, Q" R1 ?) R2 J: aTrue; j3 t" L8 }" b( Z- E
    1
      G! Z, N& v1 ^. j) {' C2
    " ^5 I+ w3 R/ N( e5 i7 V: r3
    ' H% R, {4 _# q4
    : Z* K' F0 P3 ~5 ~0 l  f- \  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:/ l* c* c( f+ t" g! I. X% \

    9 B+ P% N! P' ], N+ s& Gfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:/ e2 e, @/ `/ @. m! h, z! H- C. v6 u
    pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
    * `* b' g2 D) d3 E: u& |% m5 E( H6 ?
    IntervalIndex([[1, 3], [3, 6], [6, 10]],
    9 u/ n8 J& K; Z% U4 ^               closed='both',
    , Z- J/ O: m% i$ l8 T  f               dtype='interval[int64]')4 ]0 Q3 B. Z9 |" U% F: y
    1- p* F- m  c* G: [$ z4 W# Z
    2
    4 J9 h* {, P. i# A5 w, y7 K3
    4 x& h- E- }: J" Z7 X* b. \4
    " j! [( a7 \3 O) z5- t: g' F6 w& z, O
    from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:4 a; h; e4 J9 J+ H, S5 X( {' u
    pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
    8 n& b' f+ J# Y- ]: k# S6 U: C% ]9 ]/ ~0 @1 _) \7 G' x
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],: D; i4 t+ U; Q4 {
                      closed='neither',
    ( w1 W" t- M; l( A                  dtype='interval[int64]')
    , h. k1 s7 Q. @2 \) O0 y  }# Q0 o1
    ( D; h. R. L* ~2
    $ f  t- j$ @: B" K3
    5 i$ z2 o6 B3 W* p) V" l/ j# C! n4
    # J: v6 ~( N) E( ^7 t5
    . M" M! I8 U) G9 ^% lfrom_tuples:传入起点和终点元组构成的列表:
    1 z9 }$ E. Q$ q9 p1 @2 I6 ?- q# ~pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    % z# N: W4 p% @
    0 r7 p, y- j* [# i' d- OIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    . N1 ^5 k' \; N7 H              closed='neither',$ _, V1 g4 g1 N/ N6 m( w( c( ~
                  dtype='interval[int64]')
    " ]& R' C( @* k( f1
    ' {. D4 E$ o4 t$ V. o2: f8 y$ F% c) D5 J' L/ R
    39 l' z# g; e: A8 w. A0 Q
    4- h1 g$ o/ E6 `1 c) j- v
    57 q$ |  K/ u" S3 h9 c8 g
    interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:6 h5 a1 J4 _- f/ a
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
    * V: U7 O: k$ \/ z' [0 |' tOut[57]:
    * p4 z: V0 b6 `. TIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
    0 e! B5 T& V% Y- G+ @" l              closed='right',
    . f! F4 C  `6 R9 X; W: P              dtype='interval[float64]')
    / i0 [, P5 ~) {2 Q/ p8 N1 |1 u
    & E! L7 X- q6 O- e' m) vpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度- I) A( b9 Z9 E" x5 m) l
    Out[58]: * d. W8 \- l! ?! N
    IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],$ [3 h3 V7 L; h7 S+ n+ d0 A! ?  W# J
                  closed='right',
    : s) B$ k7 g7 g4 g; }  z              dtype='interval[float64]')! `1 b7 b4 {1 |2 h- j
    1) r$ A- b5 D8 P" \7 s( L# Q
    23 Y/ `; Y- _% k6 M
    3. C; X) d6 ^) D
    4
    0 ?. H; a# s$ O$ G2 s. G4 h5
    ' B) \# N* ?/ E+ P/ d' Z6
    - y: G6 h' u8 q. _. h# U7
    ( x* k7 r# H5 r/ m3 S8
    . X2 M6 v. i, R$ ?, h/ @: d91 @; U  [/ G! O; D0 u
    10
    , c' c/ E8 ~6 D; X: M: g: P! Q11& t* E0 n1 A/ ^5 g/ i! U+ r
    【练一练】
    6 g! V9 m% u3 _( Q4 X! s: f  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。1 g: M5 m. N3 ~; A

    ! r* q6 M" _0 m+ _; ?/ M! A  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。- e% r5 ~. V% k4 J- i2 i; {

    % X1 z3 c# \0 e; l4 Omy_interval
    / g& w) B' @) _% K2 FOut[59]: Interval(0, 1, closed='right')
    4 @, B8 B6 ^5 I/ Z
    , H; N# t7 F' M& y5 ^# Lmy_interval_2
    ; M/ w/ d1 c# C& z1 _Out[60]: Interval(0.5, 1.5, closed='left')- O' ?* J6 f1 J& ^+ v  S) \

    , R% T, K, h$ a7 }; ]+ `pd.IntervalIndex([my_interval, my_interval_2], closed='left')
    6 p/ {+ s- i& E: IOut[61]:
    " m  k- G* x9 ]# k: b) }IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    * o" X' z" g8 U. A4 B& T4 m) v              closed='left',
    # s" ]9 ^3 S8 R! g/ u8 ^( p) O              dtype='interval[float64]')
    , y; E2 @. r1 Z; V/ B* I. W1* H8 Y' l2 K" s0 E5 Y
    2' \- A1 E' |5 Y: |: p! P
    3! j1 A# r& t; f- [1 q
    4
    - Z1 p3 ?2 R$ z# c, ?: t' X  M55 |9 o) Q& B! A
    6
    8 @' R8 a3 A& ^9 K" s: c- w* W7; l5 o' I0 H7 y' D
    85 U: `4 X" H# {& f6 i' u* F  \
    9: Z1 c1 ], q9 D6 ~3 x
    10  s$ [7 U. ^+ A
    11
    ; A  v" a! f- B1 d! q9.3.3 区间的属性与方法
    ' T0 [) p. u: z+ e2 N4 s1 ]( ^5 f  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:7 p6 J7 `/ `9 j2 u9 l, F4 @, D
    - ]7 m8 m. X# E+ h
    s=df.Weight
    , l2 T4 Y" `, `% }% lid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示  C5 q7 ~) Z4 y7 b1 W+ H
    id_interval[:3]- s- N. j2 Y! l0 Z

    # ?' i4 t& g' B6 s  X, a+ IIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
    ' G* O( K% w% m' A$ A7 _/ Q                 closed='right',
    9 q7 t* h% V! Y: G3 }0 Q/ ^! b                 name='Weight',0 X7 j' V* V7 }& L6 {
                     dtype='interval[float64]')
    - U& ?, `7 Z& A14 P+ k0 R, y7 n
    2
    # n2 Z/ w% W( e$ q, y! D- n3
    ) N. _3 h" x0 D2 U6 B! e4 K4
    ; R: l- E& ~! w53 }, l/ h( k9 J; d4 D
    6
    6 w9 i/ }9 a$ b7
    & j6 @8 f: d% F1 i2 c, `8
    3 s/ j! }$ v2 k5 X' d与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。+ L6 F( ^; d, _
    id_demo = id_interval[:5] # 选出前5个展示
    " y6 ~1 r  [, Z4 T8 y4 Z+ u& Z, Q# ^9 c$ G* z! ^
    id_demo# K  A4 c9 y9 K1 c
    Out[64]: - p: p9 s7 L. k
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]]," G- v+ [6 t! a. L* b
                  closed='right',
    5 T! b. E" z1 h0 N/ m              name='Weight',
    ! U6 y/ c! B! L% ]              dtype='interval[float64]')9 [/ H' X: r% G6 P
    9 Q' l6 Z2 r) o0 T6 ?
    id_demo.left # 获取这五个区间的左端点
    , b8 k; q" o1 _, H4 A$ U1 NOut[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    3 u/ Y4 ]) |) a  F1 E9 w4 S* E9 u$ L- K) S+ y0 d0 K  x% v2 X' g, H
    id_demo.right # 获取这五个区间的右端点
    - O- z( C7 |' Y- P* z; P3 wOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')0 [  _, y7 `) N0 d

    3 G% v+ a6 |. O  tid_demo.mid
    / f0 i0 t* j' V( Q0 DOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
    3 n. t8 r) f* L+ d
    3 P7 h, I  V. p) u- @1 N: _. Nid_demo.length
    - f! G9 H2 q; m4 `Out[68]: % x& Z' N; ^" P+ A# l! v9 z
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,
    ) |2 K5 A! S+ p5 q; J% u              18.387999999999998, 18.333],' Z' w* C; i- q7 p) G
                 dtype='float64')6 F( R  W8 T1 d! K
    4 E( w7 Q& _7 s# r+ ]+ B; Z/ ^
    19 I5 i6 q2 e  c! G- g/ K8 C7 j9 X" x
    2
    , D/ I2 `. j7 f5 |5 z1 a: I0 j39 g8 H2 L4 w+ g  E
    4
    ) P, C8 S" D" {2 Y! @5  v! k( b7 p) A
    6
    $ o  G0 B0 t0 X4 G70 G# I. m6 r5 D# x& x
    81 Q; X, I8 {, ]1 @2 c
    9) w. h7 @! X4 y6 b# k& W
    10
    * L# `0 s, U% m+ D115 ~- A4 a: Z- x. B3 p
    120 l4 \" t' q/ S5 a. ]7 G# M: T1 Q
    13: e  K# o& w+ P
    14( `" a& L: X( Y( _
    152 U" N2 o* E' a; F
    16: @6 `- b  h; t9 N, f7 v- ~
    17- R( w* ^& Y! J( ~% W$ C$ Z
    18- f6 ^4 v' U! E6 O+ X; O1 e
    19
    * g; n+ R7 E+ T20
    2 \- B* f/ h1 m! l# p" u1 `6 J8 o21
    7 S+ }+ G8 H; ]* r9 i) S% K22
    # p4 w! E1 u8 `23
    / ?( X, b" p, c$ _3 e5 wIntervalIndex还有两个常用方法:
    4 S* t9 c1 t/ D9 W  n) S: I# Y8 `contains:逐个判断每个区间是否包含某元素
    * {$ K0 g) H+ |3 a* Foverlaps:是否和一个pd.Interval对象有交集。
    ! V! d% G& y3 u2 w( g9 Hid_demo.contains(50)! l$ b/ x! E3 G, i- N' V+ S
    Out[69]: array([ True, False, False,  True, False]). a1 f1 p2 ?, o: m- I# r5 i( c) N+ _+ U

    : [* {8 Q6 G; t" h/ `* Rid_demo.overlaps(pd.Interval(40,60))9 e7 M; Q" v1 f& F8 P
    Out[70]: array([ True,  True, False,  True, False])& ]# O* H  ?8 d/ w4 C" V
    11 D7 q) G# [# F3 |5 q
    2
    $ G( n1 O. l% `/ y& G37 l3 k, R* B7 k& m9 m/ l' H
    4# C) A; V4 S$ [: P# L: s
    5
    7 O1 F6 `2 L0 S( C( H7 X. ~9.4 练习
    8 n$ r5 c2 p& n7 P* oEx1: 统计未出现的类别
    * i- H/ f+ U1 T' o) `) a  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:5 _5 z( S6 t) U- Q
    5 \# A+ v5 y/ z) y
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})& d: G3 Q, Z9 D
    pd.crosstab(df.A, df.B)8 F) M8 p4 L$ ]8 g1 m* |& q( ~

    2 Y$ h& |% S) _$ h- l' x4 l" B% qOut[72]:
      A$ Y1 f2 ?2 A( ^B  cat  dog7 ^  M% C9 a9 h4 z' x
    A         
    $ K0 n: Y4 o/ \7 v9 `5 e! O+ D. Aa    2    0. i# ^3 u5 r+ i# b% B  p$ C
    b    1    0
    * ^7 S5 F# n! ~6 Vc    0    1
    ) V: @/ |5 d5 s4 L0 d$ d, q7 C1
    % [! Y3 I4 C7 g  `2( j8 f- X# ?% q+ z) b% Q9 l
    3
    8 O6 o6 E$ d* A8 }4 k& q+ }# s0 M4- c' T9 h- M( I; {% P; k
    50 S( C1 h% {! h$ G
    6
    : w' A6 c: f$ r$ n7
    / E2 |7 X+ n& f; N: s; R; b8
    5 j; J: F* S; ?9& D# u" I& H; x& m% E/ V# a
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
    ! u: ]4 O9 r, x& ~7 f1 H6 S' b, q# n2 ^$ T/ N( e* f' O  l
    df.B = df.B.astype('category').cat.add_categories('sheep')' ^. I- v+ I0 X' U2 n
    pd.crosstab(df.A, df.B, dropna=False)
    2 r9 f; r; I2 R/ y% k/ K& k7 T0 |0 N6 k" J4 {7 b
    Out[74]:
    0 C8 b2 g& G" nB  cat  dog  sheep4 O, N1 V4 w; D' i0 y
    A                 ' V" r: S2 |& M* j
    a    2    0      07 \0 d# e: P2 ^/ t5 u- b
    b    1    0      0( P, a& j" U1 \: K8 m, t$ |! f
    c    0    1      0% ?) }8 X* J, `8 W# j4 D; r
    1/ x. f' }3 f+ @1 J1 K# R
    27 w) }) d% N( E: ~
    3
    ) Z7 H' K7 @* K  \: K' G4. U, r5 x" f4 Y9 j+ Q" ~, D
    5
    % D2 Y$ F3 i- `$ g  u6! s4 Q" n, o7 d9 d
    7
    * |! r1 q; U+ z1 [8, [4 r2 Y* o; s  F
    9
    7 Y! _) I9 a* X. T$ |  r. }4 B请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。7 s7 ^/ D/ p! w& k2 o6 @0 N  g: n
    % s- u, X8 g5 m
    Ex2: 钻石数据集. J9 W+ b) T0 ]' c- _# f
      现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
    ; _9 u6 m8 g/ w' m1 h4 _+ E% h" Q; R) l. n& E7 K$ C+ I
    df = pd.read_csv('../data/diamonds.csv')
    ' k/ C# b  e3 m2 q$ I% E: ?df.head(3)
    : }. o- S) D& b; ~/ u
    0 [, ?! x; o4 I6 O/ DOut[76]: 8 k& O, E- s+ b; |+ r/ I
       carat      cut    clarity  price
    " Q4 f1 ^9 n3 I: t% W0   0.23     Ideal     SI2     326
    ; Z! q# r  {6 R4 H% E0 r1   0.21    Premium    SI1     326
    4 C7 ?% T) X1 o2 C1 w2   0.23     Good      VS1     327
    ; n6 ~3 ^" S$ {' [$ i* M% U15 y2 A: }; N0 Y
    2
    # m; W+ ]7 X  Y7 r32 x* p0 G1 ], J/ ^6 C. f- H: D9 [
    4# _7 R. ]) x9 @# F# t2 {7 H
    5
    # k1 ~" ?9 E) _, v6
    % R  ^; }6 B& M, H6 i/ q/ Y74 n+ j( T) \+ J; M  K, \/ V: A* P9 \
    8
    9 g; z+ K* {: e: m9 X2 O分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。; F" ^" M. H9 l8 y  U
    钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    : l7 T* O6 H( f分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。4 X: }* d- \5 R* a& \1 I0 A
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    6 I" v& i2 N9 y+ Y0 z0 S, \第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    5 l) ^" k$ s6 x$ q1 A0 `6 m: m对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    3 Q" k, D$ D  o5 ]: J5 d" h$ m& A先看看数据结构:
    8 p& w; x; {+ J3 E# F" n/ _9 z$ }' ^- ]! N0 w! `$ a6 R* C( l$ ]
    df.info()- ~9 {* V. ]/ d# I$ U
    Data columns (total 4 columns):
    % ^; R' ~7 y% H8 x #   Column   Non-Null Count  Dtype  
    6 }: [/ ?" A) K2 E/ q---  ------   --------------  -----  
    $ U% Z: O4 a1 |, i; E/ ?9 j 0   carat    53940 non-null  float64
    : C0 U7 a& J7 i9 c2 W) w1 m1 C 1   cut      53940 non-null  object 0 C$ C  w9 O, q0 S+ f7 w
    2   clarity  53940 non-null  object . Y+ M$ r3 {+ _' E* }" S6 i! j
    3   price    53940 non-null  int64  
    + M9 N' H5 g& a* f, w$ P3 rdtypes: float64(1), int64(1), object(2)
    ! o& T  ~: h: R# D1
    ) v7 U5 T4 G4 v8 F2: v% E+ ?9 N* N3 c1 U7 m( Z
    3
    ' n' v: R9 {0 V  H4
    0 P$ l! ?' Q( O5
    5 U! P, v  g+ T/ r$ C5 u6 h6
    4 `/ Z# l; U! g/ ^78 ]: U+ W  l" d
    8
    ; u5 K1 X* z5 t9 Y" n9
    . r5 P5 k# P( ^0 L# h比较两种操作的性能
      S) D8 i& j' L%time df.cut.unique()
    + B( b2 T& l% D: B& p& ?5 l9 e" p* E
    Wall time: 5.98 ms$ B4 L% z6 N' c! N, Q; Z# P
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object); D5 A3 B6 Z) Y; @" g8 D
    14 v" J. h4 E$ A, V9 ~2 |/ {
    2
    * y6 m7 n+ w, A9 Y/ |; {3& u7 P# x+ K$ Q. a3 @8 O# f
    4" L4 ^7 w1 \9 s0 \  b
    %time df.cut.astype('category').unique()8 P: V8 d: O2 J- @

    , x& E% p) {- a1 l( e& yWall time: 8.01 ms  # 转换类型加统计类别,一共8ms
    3 r% C5 \4 |5 ?5 `# G, a['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    / i( c4 H( a9 S) Q, `6 U0 x) FCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    ! ?6 p8 e; y5 N8 G( D- E& ]1
    9 ?' h/ g) X6 K% `4 L1 T# c24 g$ z) @4 X! o4 p) k7 Q
    3
    " ~+ g3 n7 O, r, y  l( K" K2 U3 A4
      w; {1 N* r0 X7 q5
    . ^6 W1 g8 X' cdf.cut=df.cut.astype('category')
    & H+ I6 U. s! g1 O: C%time df.cut.unique() # 类别属性统计,2ms. \1 w, q% ^7 z4 Y+ m+ y% v
    1 f( R* T" c7 [& w& @
    Wall time: 2 ms5 x0 G* F' U% [) ]3 m. W4 G
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']( d$ b6 u0 l' L3 a
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']) C5 z" I$ U" r* |1 ^, |7 _- [2 T
    1: j5 U  P+ \+ k: ?  D3 @
    2
    3 f& t$ F+ L, Z9 I+ `( B- u34 L: W$ ]7 W/ {7 ]& V: F5 b' H
    4
    : v' C, B* e2 f5
    & X. H  y) P3 v# t- ~  y4 c7 D  d6
    & q6 k1 e% i8 ?1 S9 p对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。& g' s" H3 _, V: ]6 {
    ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
    6 H7 a. J' c! Q: T' f) q7 H$ fls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    % W" K' [( J: J' C! m! edf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    ; L1 I! F1 {* B* @2 d' A1 `" H) O+ `: Pdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
    " p' p( O5 f# G- ?# w6 |* E! H! K$ S, m
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    0 X8 v( u  W  `! d
    ) _! \) f1 O. q; X9 Z7 N# g        carat         cut        clarity        price
    0 L6 k  a( Q: v( U: [9 _315        0.96        Ideal          I1        2801
    9 h4 l% _8 x: P+ d% M  _8 u% I535        0.96        Ideal          I1        2826
    ! G; V& W& E( w4 T, p1 x' a551        0.97        Ideal          I1        2830  s& x3 o. B0 e1 M. _
    1
    & g, n- f. p3 C- A* \/ X2 S2$ c% _' p3 }+ z1 a: b
    3
      z/ H. ^& o  O8 p- y( Y/ ^( u4* t. Q. w8 \2 A$ M' O2 Z. ?
    5- Q0 ^0 t! |" H- L
    6
    7 @" C* A* ]! w( h" p% [7  ?0 M' _! y' r, g; r
    8
    ) [& |  v' Q& H8 q90 e% c$ s8 C8 t8 t" e
    100 s6 j4 s( V) O5 L, e; X
    11
    6 N8 v% P. D0 x5 }- i分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
      ~, Q% B' U8 Q# 第一种是将类别重命名为整数
    # S- }5 e; _7 s" c- Ydict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
    1 [% O" ?5 M) A: I! ^' i& kdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)])), g0 ]' @* {5 K8 t$ q5 v
    + v8 I3 @5 I/ B+ d$ i' C
    df.cut=df.cut.cat.rename_categories(dict1)5 r+ E+ k: Q$ P$ q' l( e4 T
    df.clarity=df.clarity.cat.rename_categories(dict2)5 U1 \3 G9 Y+ ?' V* Q6 [  P/ O
    df.head(3), }5 l  o+ Y/ ?$ \! e

    ' c* L2 n' K9 J% j        carat        cut        clarity        price
    % H7 d( A' A) v- m1 A' F0        0.23        0          6                326; {8 n# a$ w$ Q9 ?7 S+ m
    1        0.21        1          5                326- a3 c1 a% ?  ~. `" Q! \5 K
    2        0.23        3          3                327
    5 R( t, p; b1 w( m" A1
    3 A0 f: f5 W! {# ^, X6 L) B29 I/ |& W( \5 C9 c
    3
    9 i4 Z) y1 a; f: S1 l4" o! h# c' l+ c7 Q* V
    5
    * \& G; K" r$ W/ P! Z- ]" t62 E9 Z: C! d$ t, W  a" Y
    7# k  o1 _; G" R+ ?+ ]
    8% r1 i1 |; x  }4 M3 q5 `& S0 F
    9
    4 M) ?' o. C# @# v10' R0 x! F& g- ?) Z. p! m3 ^; N
    11
    7 m! f/ K$ r$ I+ A6 Y) I; ], Z+ ?12( l. J' K: {% p+ I2 R
    # 第二种应该是报错object属性,然后直接进行替换
    . R! y4 y( n- o) d* H- r+ \df = pd.read_csv('data/diamonds.csv')
    " C1 x+ F3 R" B- ~% D0 G& B. t' Vfor i,j in enumerate(ls_cut[::-1]):" j. Z0 {/ c) ?3 c: t; ~  Y
        df.loc[df.cut==j,'cut']=i 5 [+ ^8 }. m- y! Q- c# t* w- k
      P. E# ]- @% H( n1 E: J
    for k,l in enumerate(ls_clarity[::-1]):
    ! d! Q# R+ C/ `; ^5 C' m$ F" o    df.loc[df.clarity==l,'clarity']=k7 V% L! E3 A, P  ]$ m$ b% e
    df.head(3)# i9 E& R) h5 h0 x3 a4 U. a

    ) r: Y' ]) ~$ }5 t& f( M        carat        cut        clarity        price9 w  v% e, F+ @
    0        0.23        0          6                326
    ! f" P1 E+ g4 J' [7 Q1        0.21        1          5                3262 y  x- b! d& A
    2        0.23        3          3                327' J3 f0 \7 y* G( @8 ~
    1
    . u0 q7 e1 |8 @. M2 r8 U7 f2- n: ~$ m: g7 X8 e* B; s) ~% D- K: E
    3, [: j& c6 J1 u
    4
    " v" x" v. r3 t/ A2 G5
    0 x/ R+ r( N" Q! z% `  W! p. S6& q& H; s8 u+ f  P, M
    7
    - o  t0 z9 c9 ?7 [% B; q% e! ^8" M& J8 G7 S% e# d: Q! v
    9
    2 u! }% X9 o9 s; \6 Z, o103 o, J# I" ]9 q$ K4 j! O, D
    11! {; j2 [6 z- N6 N8 |# W
    124 }3 c6 I: \" V" v9 x. }8 B
    13
    " X. r8 e- V" P0 h  Y5 J$ n1 V对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。) a1 Y% D6 U( s# s; B: w
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点( H* x4 t8 N2 g/ u
    avg=df.price/df.carat( ]3 u: j* [# |. `, M0 L4 G

    0 W9 V( [2 {( `7 W$ idf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    % k) }# A" ]( V9 y# G                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    ; }, S) d* t4 G" T- h1 ^
    ( C* V5 ^5 x% H$ Mdf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
    % k3 t4 ?! J! F                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]( L8 a6 l( M' @$ m( }. I( b
    df.head()" _) _8 ^$ K3 i' E, k. P
    / x3 e* ~3 [  c7 |5 W/ p
            carat        cut         clarity        price        price_quantile        price_list+ x1 L  u/ {/ s5 P
    0        0.23        0                6                326                        Very Low                Low
    % M3 ~% H4 X% `1        0.21        1                5                326                        Very Low                Low. G" X' R& D7 F" D" B& ?
    2        0.23        3                3                327                        Very Low                Low
    / L9 P0 }- _3 Y( I3        0.29        1                4                334                        Very Low                Low1 o1 f  p2 }5 v  A" {6 Y: s9 \
    4        0.31        3                6                335                        Very Low                Low                                       
    2 w/ \' u$ V5 k# U5 @7 O$ S. A. K' i1 H* g6 {1 o. b& \) o
    1
    ( z" M/ G3 l& E" s24 c# F/ w, S" P! i: ?5 Y! ~4 D# r
    3
    6 M  R+ ^( e/ v4' @6 r8 c  Z9 J' ~- T( g  C
    5
    $ A4 ^: ]. \& X& l& \6) Y4 j. E! g- {. q" d0 W
    7
    7 k5 R/ ~6 u4 S' D* Q. W8: \0 ?8 ^2 b* S8 U8 F
    9
    ; c# n  i' X/ b! J8 {10) M* |8 N3 x3 h) D/ N! r4 f; b3 Q1 ?
    11
    , t( d- \  D$ K/ Z% S12
    . o3 z2 r; }- z3 V" g# x6 h: \, x: R13  p2 ]: w+ u" n6 E! @7 J# f
    14* \9 }- x5 q* d  T
    154 t0 n7 I! L! v1 U: N- \! |; E5 F& |8 }* g
    16
    7 s- y( U4 v4 m3 {7 p分割点分别是:
    1 v+ V9 o2 w6 f* R) m8 Z8 B: r8 W5 C8 }$ c
    array([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])
    0 r8 K0 x& B1 ]; P3 G* {. W% @array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    1 }; s/ s5 E% d- T5 s5 A/ M1
    , s3 l3 u; g# M7 G  q' ^) k2/ F2 [& v  V: P( U3 r; E7 c
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
      ?; i0 w, m3 Y0 X) e+ F: ~df['price_list'].cat.categories # 原先设定的类别数
    2 d8 ^* z  E% o5 T# OIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')) `6 u; L7 C% a+ \5 |

    : X) t! F* l8 ^6 D" Y$ t% A7 Rdf['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别- E! T4 s9 R+ C1 F
    Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    3 G# i4 F+ m) @# n9 D8 w' N1/ f& J. \6 P+ c; W9 L( H
    2
    % v) N, n2 o+ n37 _0 v( Z6 \0 A  m# j
    4
    ( {* }" M. s+ [: C' A5
    0 H; t4 |2 D& U3 @$ a# `, Ravg.sort_values() # 可见首尾区间确实是没有的
    ' N2 j& G+ i' r- _  p31962     1051.162791
    . O# n3 ]7 q& T% ~15        1078.125000. h6 Q4 k9 Z+ |$ s6 J6 [6 I
    4         1080.645161
    . ~: q' M, ~4 F" S& {% ]. K9 y5 y28285     1109.090909
    # H: h4 v3 J+ N% @3 b13        1109.677419' Z+ j' h/ B! X; e! X
                 ...     
    " W5 F2 @' D, H26998    16764.705882
    * V3 o/ t) x# O  f" z5 g& D  H) h0 I27457    16928.971963* v7 ?4 o  v! b5 c& ^- v
    27226    17077.669903
    + T  H4 n5 P* R6 p3 J4 V# N4 V+ D6 y  c27530    17083.177570
    , U7 T5 Z" W6 T27635    17828.846154
    " t  _8 k3 Y. c0 t, F4 _% F1; U" b- z8 H7 w7 M
    2
    5 {& a6 t" z" e* A3  ^  x$ S4 L) d
    4' Q3 w- M) H6 w* I' L
    59 M1 j; u: q3 [/ @
    66 k6 p- h' [. u+ o$ D3 l8 {( C' L
    7
    7 c6 A& f( V3 G0 c1 m. R( c+ k2 M8
    ! g. g6 J3 U$ k( D98 v7 H8 `- U/ X& w4 |! k! B
    10
    + l, z: M7 A! j; \9 [11
    , ~! }3 x( Y6 l" ?  d) k122 b! _+ ]3 R* S9 Y# B; J$ B
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    + }" f% S. N' f% [# 分割时区间不能有命名,否则字符串传入错误。
    " V3 q4 o* o* e, {7 G# Hid_interval=pd.IntervalIndex(
    ! W; Z' x; g, ]; o' V" O    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]
    8 t' y8 n8 l: Z2 [7 X" k; Z" U                            )
    , E* x: W- Q4 o* X0 ^id_interval.left
    * A/ y8 y$ g  x8 w! sid_interval.right
    $ f' |" v. ~( T# pid_interval.length                            ( @) t  U+ {4 g* ^/ s
    1
    ) w& W, F. p* ~( a2
    5 M7 i0 H, f$ y; M32 X1 w2 @' m3 \9 Z9 K& A* f
    4! y3 I6 f3 }- {  s( w
    53 B0 u+ }% j/ k, j5 \$ ^
    63 S7 T8 w, [' W0 ~0 U4 N% ^! T
    7
    . S( q+ K0 t/ Y3 [  B- a第十章 时序数据
    / B$ x. h* _* Uimport numpy as np6 g- t9 ]: p* h: `4 b% ]
    import pandas as pd
    & \! h) l) }# m; |  i10 {( l! {% W2 F' J% s
    21 g6 r* A1 k; D) s) x7 D
    2 R7 @/ o, u/ L. o- t, P4 K2 T* e
    " x- s0 [8 \: m- W+ Q  T
    10.1 时序中的基本对象. `0 R+ I4 t9 y
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?7 X* P: K; }  J! v1 @0 u
    ! [* X9 u3 z  U8 Q: b2 t& a
    会出现时间戳(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的简写。
    8 I' p& H$ P' S. g5 @9 g* _4 h$ L2 B; n: P* L" ?2 z
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    5 S& y3 s' \& F) I
    * P8 I' @5 Z( L4 i会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
    3 f8 k" Q& E0 n1 H$ h+ M8 `8 |! z5 o
    会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    : B' f3 A4 |$ S+ t1 J1 i4 I$ S+ Y9 E" |; W) y% c1 ?& o7 H
      通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
    6 M+ P$ i8 b9 L/ x
      [0 ^' T* b* \8 [概念        单元素类型        数组类型        pandas数据类型; M+ x  N9 m7 z* N* n
    Date times        Timestamp        DatetimeIndex        datetime64[ns]0 n3 c$ D& F6 X/ m  _
    Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    4 b1 y0 a" P- g5 x/ Z9 p1 rTime spans        Period        PeriodIndex        period[freq]
    0 q5 _2 J8 o0 ]Date offsets        DateOffset        None        None
    - q2 o) I" J/ ~# C! R  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    5 e& Q3 S5 A  c+ o
    / ?2 Z1 U, u5 d  i3 p10.2 时间戳6 D8 h+ Y# F0 d9 Y" n) @" }# t
    10.2.1 Timestamp的构造与属性( I$ {( y2 {: |& [6 L' j1 m4 J2 d% W6 E
    单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
    # I0 B# Q; d% R) p; k# A* y& F' H( V5 b  [: X5 d: O
    ts = pd.Timestamp('2020/1/1')
    , r6 I+ \. @+ [5 H7 `% l* p: x/ Q; m7 x& j
    ts1 a, l0 Z" `+ p! f4 i7 Y$ ~. T! w
    Out[4]: Timestamp('2020-01-01 00:00:00')
    5 e6 U) h6 [: ^. V+ j" |" D. c5 I9 D$ h0 D% t7 V" Z
    ts = pd.Timestamp('2020-1-1 08:10:30')
    , [( m& E" n3 v' H/ S- Z+ j+ t/ r4 x
    ts0 K0 F; o3 B' j' @: l6 `, N
    Out[6]: Timestamp('2020-01-01 08:10:30')6 d9 X7 J7 R. R9 @
    1. V" A  n! U) o& U8 q+ o
    2; N4 X2 b* ^; L9 [' |
    3% ~$ }7 {  B# {
    42 r% r( ?" m# Z8 }( [6 Y
    5: T+ H& D# C/ K, o4 z
    6
    9 `4 [2 P! W# ]+ d- X( E7 u" a7
    ( \( L% L  `. C8
    ' v4 @; a7 X5 n. J( c6 l9. M+ [' i2 |8 u6 i5 a
    通过year, month, day, hour, min, second可以获取具体的数值:3 v+ o( P/ O: ~
    ) c" }& s. e/ v
    ts.year$ t- H' Y' S/ b) ?
    Out[7]: 2020" a2 F8 C, g5 G7 C" A* P# ]
    $ i; N4 ~. K1 ^/ y* h) e8 \
    ts.month' g- h- [0 u# Z/ [" b6 {
    Out[8]: 1
    ! X4 L: e8 l$ F9 e# u
    0 Z0 J: v* a5 z0 [ts.day
    / C% ^$ ?8 u. s  C6 p6 UOut[9]: 1
    ( w0 r! z. I  W# ~. T! U6 D2 B$ @* l  B* f; d  A
    ts.hour* W4 I' c3 R* F* h
    Out[10]: 8
      J6 A' i4 [2 ~% t3 M6 _- q
    # y8 [, [8 O% @% a  Tts.minute
    6 }! r$ A+ }; U5 S  J! lOut[11]: 10" R0 e8 p- e4 A6 P- M' V
    ' t( ^3 a; D6 L$ Z
    ts.second  z; M% L$ i) W# J1 k
    Out[12]: 30
    , u6 m; \( T. [' B9 }7 Q! d3 I5 t7 p9 W# e
    1
    * `) s  {: \. S) x2; K5 R% d% a) A9 k6 q, A5 j* V
    3+ D" }3 U4 Q" A' a! M1 ?
    42 @  m( m7 j' U/ K" k- H3 t
    5
    & H8 r3 O/ E( |9 H, a4 P- t% M0 W6
    3 N5 _; H& @/ R7 ~: |0 Z76 _( t& e: g! \
    8
    3 R9 _" l; R" ]' K3 z9
    " K: x& S, [, W/ |10- }$ ~! w" l' K: u1 s
    11
      U- F# t3 X$ @12" |5 v4 J% r6 E. ]/ I7 y
    13
    8 U& ]" ^% h+ K1 `* j2 e14
    # N! I+ s8 k8 F; k& f, p, X" o157 L- B4 ?. o  _: c! G& J1 I5 L/ ]! h/ G
    169 w1 }( x0 k7 _1 D- q
    17
    . Q0 `  p2 r; A4 }' g# 获取当前时间
    ! A, Z3 b, r, k8 m8 L; |& Qnow=pd.Timestamp.now()
    ( O7 c7 T. C7 S% D1
    * J7 e7 K3 J" {" _# `; g2
    3 c3 t% ]8 I" f在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    " q7 Q& @- c' F5 kT 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)
    - }. o2 D: a& w! P5 ~; j7 BTimeRange= 4 I0 C0 g: ^& X
    10
    : v  I: o1 v3 w; X* G9
    , R: G0 f: X/ [. z8 L# R3 G ×60×60×24×3657 B3 t+ z0 b7 [! p+ s! }
    2
    # F" k/ ^0 t8 n: K5 o2 Z64
    : B! s5 z0 ?8 `3 D
    * `! k/ h, `: f4 c! G# q
    9 |1 c2 I6 J: w  V+ q ≈585(Years)
    3 Q- N# x. _; [: j* Y* x1 ^6 @; l3 T# i) d8 D# `; P4 T9 F' p0 P
    通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:' p- b- P: R7 N# A

    ; c' j- M( w' X& O" W$ C: x' `$ hpd.Timestamp.max$ G  Y" `4 ~5 ], Y. v
    Out[13]: Timestamp('2262-04-11 23:47:16.854775807')/ j9 r, X- X! i: q8 A! O4 p

    0 V; L+ r( q" C+ a2 vpd.Timestamp.min
    3 b' Q3 V) a" O) T" _* ]" _Out[14]: Timestamp('1677-09-21 00:12:43.145225'), C5 N' ?9 {! B/ ~

    1 Z2 x, C; C7 M3 S0 n8 b9 R  Kpd.Timestamp.max.year - pd.Timestamp.min.year+ v. h& {1 T* Q5 U
    Out[15]: 585
    ) F/ Q' m& Q5 Z/ H: b6 _' h+ L1& z% D/ [* ]7 J. f! A
    2- p) R' l3 T) v- S( ?1 H
    3$ R' J# {1 @& ^% R8 p! ^5 L! {
    4
    " N$ I/ ?1 `# u52 I$ g( g& u, f) o* Y" }- j* K
    6  P9 G% X4 Q0 J9 Z# E) k
    7; M6 w" O. o3 }7 u
    8) w3 z1 J% }  q1 _: l4 Z* t& G
    10.2.2 Datetime序列的生成
    0 C. W2 h' b* a2 k% f. i/ `pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
    - p: P, o" S2 j; ~+ _  I  L- N                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)5 @7 x0 U/ h) Y4 ]
    1
    ( S% b8 l: u* o3 S2
      z: S( v) L0 A5 i9 D/ X! [* z2 apandas.to_datetime将arg转换为日期时间。
    * q4 v9 n9 l. g" V" m  h  }1 _8 @' L6 m$ {5 K, h. j: T
    arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。  A0 w8 }2 U2 P+ U7 [
    errors:
    ! f7 t- W* \/ ?+ ]- ‘raise’:默认值,无效解析将引发异常( X: |) W+ u3 s: `
    - ‘raise’:无效解析将返回输入7 l& j  H' w0 ^- k3 L# D
    - ‘coerce’:无效解析将被设置为NaT
    $ M5 G* ^' a& \6 b# M+ Gdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
    + K, E; M7 O8 w; ^6 u& H5 Tyearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
    ( p4 r) S. F( D3 L! R& uutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
    ; W1 R! w4 X$ C; J; Cformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。2 e1 z- k/ ^+ k( J( p) |$ a- n
    unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
    ; ^" S7 q5 T8 ], x) y/ b* Vto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
    * B7 r# X" C* r/ l+ z" Y2 e# ?pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
    . a" P) b- f& ~8 [' V' e3 H9 {$ y* U9 g
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
    + p+ D- i% ?% M* X1
    2 ^1 }* K, Y$ i: u2 S3 X2  M3 |9 O, H& a7 A: N  P3 O
    37 Q& k, H" @5 @$ ^, o& D0 X& j- a1 |
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:* |+ \' w! B6 X
    ( M  B6 M( `) o7 n
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
    - a* c& v% t/ T1 f/ ltemp
    6 A0 S& c# t& c/ p; c# @+ `+ E" ^# `0 N6 o$ b7 S
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    / W* s  e9 C7 q0 {- l11 L, I2 G4 q4 m  h. y8 G
    2; t7 {) m$ n1 |# S* c
    33 _; A! R) P) A% m
    4
    ( R) v( h6 w) C, H( J  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
    ! z$ V( p! {( R7 l  l
    ( h3 J7 Z( ^& G- d+ u* Fpd.Series(temp).head()- m( D! I/ s3 B. P

    ! ~5 O$ ~; x3 a9 W0   2020-01-019 \) Q1 G# V2 x+ w. A6 w
    1   2020-01-03- P% u/ w8 r4 ~" W
    dtype: datetime64[ns]0 j# D; v4 M% J
    1
    5 b+ m' U- R, ?& B2- i  N0 K. Q4 {7 ?4 f! j" `7 r
    3
    ) v. \$ m9 Y1 _; N0 N4% L+ K% D* P, ~) J  ?
    5
    - }3 v) e5 a. T' [下面的序列本身就是Series,所以不需要再转化。
    1 m# n/ w, C9 \$ s  Q
    . l# i, t4 F9 H) i; Z& b& Odf = pd.read_csv('../data/learn_pandas.csv')' Q5 \9 w) P: m5 e  U  x8 k8 h
    s = pd.to_datetime(df.Test_Date)
    5 T# m& @  n' Z/ p# Ps.head()
    7 L3 E! @( U" \. y" s# N: _. W, n- K/ S$ t: r. w
    0   2019-10-05
    ) n: d/ x9 J9 |1   2019-09-04* p3 Z5 U0 K! d. g! {; g$ Z
    2   2019-09-122 L1 ~7 w  Y; v$ J' \
    3   2020-01-03! v4 g! m) [8 D1 F2 o4 P/ M& f
    4   2019-11-06
    4 x6 r$ W6 ^; o5 A+ [+ _Name: Test_Date, dtype: datetime64[ns]4 Y2 {3 v/ I6 z" s
    1" K! h$ ^' [$ H, O
    2! O, U7 `: q8 q2 m+ n' J
    38 A5 }  g1 A& @* _6 ?5 g
    4$ X: J5 v( S/ B8 M  y1 o  @; q
    5
    9 I  t2 b8 P, d. B7 l7 v9 G6
    7 h7 p! `0 n# T/ c' t7. [6 k; c, G- ^& s% p; L$ h1 o& H7 n
    8
    ) i7 G7 }7 D* w) l0 V5 }% V7 J9) E$ p4 R. k/ K) i& q
    10" ~5 ^) U$ R) Y
    把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:% x1 A% l! f! U+ R' s* R6 r% g: G) d
    df_date_cols = pd.DataFrame({'year': [2020, 2020],
    / d" _' q! p* K3 _                             'month': [1, 1],, o! j, j; f: ^2 W
                                 'day': [1, 2],, {' o6 W+ O" P* F, ?
                                 'hour': [10, 20],3 e( d8 c4 ~# F  W5 u6 w4 W0 q1 G2 B
                                 'minute': [30, 50],$ [2 H6 `7 Z: z2 a. j
                                 'second': [20, 40]})
    " W! @# X8 e7 Z  X% vpd.to_datetime(df_date_cols)
    ) V/ [7 h+ A! r$ s6 _3 @" \, J' R; n* l+ h! M, q
    0   2020-01-01 10:30:20
    * @, K* s' m/ m' ~1   2020-01-02 20:50:40
    ; p7 z  d* {: M* B/ L' \dtype: datetime64[ns]9 U6 R0 I( V# E1 i# \, U2 f6 a
    1
    * Y1 I$ E1 T$ R, A2
    6 c7 r' N2 P( J/ O. C0 L3+ `# `7 Y% H4 {% }: e+ [# O4 ^
    4" q2 D( r- i  F1 s0 D9 Y
    5( W& a/ ]/ F. g2 C! ?
    6& |# N' g- m! ?" u3 O' I5 n' _
    7
    ' S$ z  H- U/ w9 d3 S  T7 @* Q4 @8
    3 b0 n1 G# `0 G; G3 h9 R' j: y9
    " [0 Y4 V; u  z, a8 X10
    ( q2 `% e+ X) M11. Y7 I$ r- I8 G* ?- R
    date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    & N6 l4 [, I) T) l0 xpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含! B" z% m8 r! G3 ~" W2 v' k( P
    Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    * O( [$ Z. I; C8 j& q& O' H) T9 P' \' S! z
    pd.date_range('2020-1-1','2020-2-28', freq='10D')7 j6 ^% D8 W( ^' ]
    Out[26]:
    1 |6 c" E1 ^' `& Y. C3 f- rDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
    0 o7 ~6 W4 @6 z) Z7 Y. r               '2020-02-10', '2020-02-20'],
    ' H/ p( }. p% V0 b              dtype='datetime64[ns]', freq='10D')( R1 l9 ~- U0 _% C, ]" G% ]
    ) h- n0 C+ d/ T) ]  s+ K/ c
    pd.date_range('2020-1-1',
    : L) v$ m0 |6 N8 x( R  y4 n& V              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
    4 J" e0 D+ [  X& Z* d$ j1 B  |9 Z. G3 M( @4 k1 }! K! {
    Out[27]: ( L2 _) J3 w( _! V& }
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
    , }/ p$ y1 T9 q. J' b; X               '2020-01-24 04:48:00', '2020-02-04 19:12:00',
    ( A8 i# \) @) R2 |2 w               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],5 x9 X7 X1 d/ d& A0 l
                  dtype='datetime64[ns]', freq=None)
    + o+ H/ n6 T: j5 o" f- \$ `) L2 L8 Q; z. [3 T" ~% S
    1: @/ s% l6 G9 n# F) n1 ~! y2 c& \
    2# ~1 t$ ^" V! w. F3 v
    3& p& k+ s) \$ o3 h) x
    4
    + q: R8 E/ {  {58 B3 ~) N4 w+ ?1 `7 }8 c
    6
    6 `, F( ]; z2 r' Y+ Q. z7
    2 p6 D  U' S" E. _8
    , T9 r0 ]$ ?3 A+ F/ q" r$ e5 s92 r5 Z% f  ]2 |! B# q
    10) ^( U& y6 `* H" f. v; b
    11
    9 C. V; O0 X/ U! e3 ^+ G/ i( d12
    # s* O* ]" Y6 ]5 s: V13
    ! {" {4 U+ V0 |" ]8 J14/ c$ w6 H9 h6 s- f2 v. N. l1 t
    159 u1 z5 F9 B9 N
    16
    ) b6 o8 E5 [: J6 h4 V/ w, Z" z17
    6 @2 ?  ]) `4 [+ v) }这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。0 a) s, `5 X, x+ }" _9 m% b4 L

    1 _) y, b. Y$ P0 s# |【练一练】
    - J+ d) x3 @$ b, D# I" BTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
    1 O5 x# x+ t' ^, O; p7 i
    0 d+ X4 M, b' zls=['2020-01-01','2020-02-20']
    ! u( ~: \% @5 @) vdef dates(ls,n):
    + M" m& n, U/ @0 J8 a    min=pd.Timestamp(ls[0]).value/10**9+ Y& Q3 y* Y( o4 c
        max=pd.Timestamp(ls[1]).value/10**9( t6 J+ Z$ e+ J
        times=np.random.randint(min,max+1,n)
    3 ~8 \3 ^9 g# ^0 Y    return  pd.to_datetime(times,unit='s')
    1 A/ c, G) v5 ^3 S9 ?dates(ls,10)
    ! B1 T7 L& Q) h' X
    % X- `: p7 y! ^& Y* x/ g% k3 fDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',8 E3 h- F# E5 [7 m3 P# |9 |5 R6 R
                   '2020-01-21 12:26:02', '2020-02-08 20:34:08'," |# m3 T: s" U6 I; ?
                   '2020-02-15 00:18:33', '2020-02-11 02:18:07',
    2 W) z! S5 L. k) O               '2020-01-12 21:48:59', '2020-01-12 00:39:24',
    / i) }7 v4 e  \. ^               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],4 N3 T* h3 p! K$ s
                  dtype='datetime64[ns]', freq=None)
    0 ^% d( D  W  }! j" N1
    2 Z6 C7 Y' f( e* j' g7 s2& }+ W: e$ x% i0 U8 G% ]
    3( Z& y8 k' o) }6 E1 b
    4
    ' o( i9 a3 Q, h6 D( c5
    ! @% \2 H) ~0 x6
    % F! O6 v+ `3 B" S3 H" A7+ ?' {8 N& l3 r& l
    86 c, O4 b7 ]; c. e1 V. S4 Z9 U
    9
    : W5 P7 O" x  ^4 u& \10
    ' \, Z) A0 u3 ~4 }7 v11
    % E8 v6 n; j3 {12/ G( z+ d$ C; m9 V; _7 v
    13
    + b# D$ y5 z' R% l14/ f; A6 U8 @! P4 x% }8 y
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:; x4 j; P5 b- h* C/ b
    s = pd.Series(np.random.rand(5),  W/ i' i; K) G( w
                index=pd.to_datetime([
    & e/ k' W- {2 I                '2020-1-%d'%i for i in range(1,10,2)]))
    ) C% R! m3 Y( h" e3 b# x- O6 @" r7 r# d" l: Z2 m0 d1 H- t
    * F$ b: @8 K2 ^8 k* W2 x1 o
    s.head()
    5 ~- K0 `; T! W9 Z$ ?Out[29]:
    + S+ v3 F2 {$ E2020-01-01    0.836578
    8 ?; x0 }, N* f2 U2020-01-03    0.678419% M; W" [  j. D
    2020-01-05    0.711897
    . H+ u4 t) C7 i2020-01-07    0.4874298 P$ G8 |. k; H9 J, Y6 `7 k9 u
    2020-01-09    0.6047053 i0 l7 H7 L6 J( C# l
    dtype: float64
    2 V6 q% q$ n/ v; f( i0 y5 Z2 V) Y! ~3 ~* \
    s.asfreq('D').head(); P1 r0 O3 g2 ]. Z2 w. K$ X! H! E
    Out[30]: $ _+ S2 j$ c( p
    2020-01-01    0.836578
    6 }/ D; b2 `' B2 d+ [" R) l2020-01-02         NaN6 g: y, I' T" \3 o
    2020-01-03    0.678419
    " N, ^% t) e0 q) V2020-01-04         NaN! X! q9 Y6 a' P  @) @  h
    2020-01-05    0.711897' Y' t, i: a/ k: P1 @" p
    Freq: D, dtype: float64
    $ {" i9 |. }; |0 z) l7 c
    * z1 c! L1 s" Us.asfreq('12H').head()
    , i& R: A) D1 M! n# q: R2 ZOut[31]: 9 X, h! @  n$ [' o
    2020-01-01 00:00:00    0.836578  I% v8 a0 K$ B  F" j
    2020-01-01 12:00:00         NaN
    : [6 K5 s5 R. \* U: r* l2020-01-02 00:00:00         NaN  x" v# a! B: n& T& M7 x+ g
    2020-01-02 12:00:00         NaN
    . N. S6 d0 m5 E! e! N- w2020-01-03 00:00:00    0.678419/ X& s  \7 t# [& Y
    Freq: 12H, dtype: float64
    1 W4 ?$ u6 i6 K! E
    : Y/ J$ i, m* w* t16 p( o7 O6 A. n9 A5 F
    2
    + l3 \( C8 k0 x! h% v# A& ^3+ I9 h9 z, d2 t* u/ q- V" }
    4& d* w% M( g6 a5 _# Z
    5- D( u: Z. s9 L
    6# u: f. [/ X# b2 ^) n
    73 J- ~) E: a- R1 g, |
    8
    3 I, f! G0 ^3 o- p  G5 m3 q! D5 t9
    $ n  F+ c2 `* h10! V" T! I+ f' N! }: ?8 @' S4 B
    11
    7 h3 O' x) Q) k. O1 {1 u12
    8 }5 |9 z/ C; I4 p' L' M& S+ A) x13
    * X' j! I# y5 b/ }, [8 P1 w14
    $ v: r: I( y- r; v158 S- }1 I" Q; d
    16: E( Z$ ]$ T: V, \3 `  H
    17$ h) y2 `8 t( F# G
    18
    + O. ?8 `6 g4 y2 e5 P6 R19
    - c  d3 ]& ?5 \. @0 v. F2 d$ M20
    ' H% _$ y( @& `) x21
    7 ]( y+ v6 {! e+ N! W1 q+ C22
    6 c# ]' V- U6 k23
    7 y. ^/ ~; ^6 V1 B( g  c$ ]3 f24) @% k: Y; u- b" M0 k
    25
    6 h# }$ N: c6 z# j7 ~3 v! j26+ g% Y5 E* @. P7 N
    27& z9 O; T& Z. J' m( C9 C5 \
    282 N. H8 p, |) u6 z8 g  j
    294 I# G2 W# \" ?; h" p& U6 }
    30
    . D1 h! i8 [* a6 ~/ F0 U: x8 {31
    + q- i  ?, ]6 B2 s( w【NOTE】datetime64[ns] 序列的极值与均值, y$ `' g2 C  P) t: K& g: K
      前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。; n, G) K: Y$ @& K! u( ]
    9 E4 O& |% A+ U/ c1 _* |
    10.2.3 dt对象6 O  U, l4 E$ J* V9 C+ L
      如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。9 R# n: H/ a% S3 f$ g/ N
    9 N0 f" Q5 d' x4 |7 b. t5 Y& ]
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
    ) v) F5 `4 _( Y- @; Xs = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))5 a8 g8 N% h8 q/ y' ~0 F

    9 I3 c4 _+ k' z2 m8 x3 Vs.dt.date
    * W5 `5 }! A* V" @: p2 fOut[33]:
    ( p. Y- G) t, u9 U9 L* \+ G6 r0    2020-01-01
    ' X- [& Q* c3 F9 C1    2020-01-02
    9 M% v( ^2 e& W  H2    2020-01-03
    , m- A/ @: k/ B- V; @) P3 n7 {dtype: object' n6 c4 q% m$ k
    $ q8 W& c9 }9 t7 y, X' W  L8 K
    s.dt.time5 y! K  w* z, u
    Out[34]:
    * P, k3 `1 H" Y& h0 Y! f0    00:00:001 b! |. ?% v9 Z/ d9 V4 r
    1    00:00:00
    $ L4 J' G- }, J( C3 f2    00:00:00
    ! s5 b; ?% |1 t# Hdtype: object+ n5 Q) G! W% h! m/ x: d7 r; ]5 l, T

    * |0 `/ c0 r2 Q/ m: js.dt.day
    " Z1 B% \8 ]( }' S, UOut[35]:
      o+ k3 a" L/ L7 p* }$ Q/ l0    10 y4 ]2 Z3 s9 J# b, R
    1    2
    1 p" ?4 X9 r( H" w/ }4 T2    3
    + v0 e+ w$ z9 C. @" ?dtype: int64. v0 u) P" k2 l2 w+ |
    5 r2 s  z( P1 t7 \2 l
    s.dt.daysinmonth
    * z1 }* p6 d7 s/ z2 K4 `- AOut[36]:
    % ?) r- B. D5 @# y7 }0    31
    0 h1 v+ S+ e- P- z% \: o. X1    31" s7 s9 f! O2 u4 r2 a$ L1 M6 y
    2    312 A2 `* R' Q4 L3 d. d+ {1 `
    dtype: int64
    + I, W/ g3 y" [& e- R: ]8 @. L8 d3 E7 s
    1! t' }4 i  Y; X  v3 v: d/ x. N
    2. ~8 F) P3 ^( K
    3! _1 ?" C8 E) p. J/ e" q
    48 w/ g7 c, v$ K% d$ ?( h
    5
    $ L( `' R7 }; m& T3 S6
    + \6 G8 m+ ^) l: Q3 I% {: l7
    ( L) x1 D; p% _1 ^* W6 s8" k9 L6 @+ A6 J
    9% k, L! y  d7 x7 o, f9 d
    10/ `5 `$ z$ b" [- c
    11! Q# F% l8 k: z8 _2 Z1 q
    12
    " M0 c7 f& y5 c" }5 O2 n' Z* T* W13
    2 e$ d# y: C% F  c8 D. Y145 t7 @) T- M4 ]& a  p3 u
    15
    0 V6 v3 g: I& D7 v5 K3 z$ [16
    & s: j; P8 m& D, l17' A$ M9 v- V1 J7 b4 h/ V
    18" s7 D! W- _* Y- Y, ]3 x& d! T
    19
    : L: \0 h: G: `) b% B& K% q* Z20
    1 l. w2 J& y  r& G# _& A% K( B21
    ! J( I! q: d& W$ ?  H% W, v22
    , O! {. H. `* U+ s! m23
    ! d2 J6 U; V, C6 |7 a( i24
    + B: z& S7 v& w25. \0 r" {$ O3 H9 q2 i
    26
    4 A. z/ N- S! k! H5 h. Y27% g5 _" |5 {+ I8 G
    28" Y! x. \! i' n: m
    29
    : K+ E8 F5 ~3 I3 y* V, W: p8 S) r  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:; b1 z1 E0 H4 s1 W4 F7 ^& c

    6 a1 K* z+ q" M: `. Ns.dt.dayofweek
    9 d( X! ^- o  ?0 _4 \$ sOut[37]: " |" F7 y' [2 c+ P
    0    28 O. W2 ^% y7 Y4 W
    1    3! ]* m0 }1 U! ]0 r5 d& d
    2    4
    $ ~* X, x& n( Q: c9 Gdtype: int64" j, i9 B( g( f* O

    : z, {* S5 Y: vs.dt.month_name()
    3 d( D' e( y: eOut[38]: ( J; [+ r+ S7 s2 p* |
    0    January
    7 O( k9 b& k3 o5 C8 {5 W5 j1    January
    ! S3 ?8 q3 K1 ?7 n( [: N2    January
    4 |% }8 S3 l. W) Xdtype: object
    ! Q3 h$ X9 P5 b1 U
    3 b6 [" n" c; xs.dt.day_name()- E# p6 Q; `0 d5 R
    Out[39]: 7 `+ h- _9 T/ A0 F3 a5 o5 s1 f
    0    Wednesday) T3 l( w( `) q: @# i
    1     Thursday
    # L8 x6 ]4 Y. c- l( Q/ c$ e2       Friday
    9 X1 o% s/ ^2 T- B  F4 @) Pdtype: object
    7 F4 t( p5 `8 R" s  L5 ^. Q8 n5 X
    1
    $ `1 `$ v3 i# Q2
    - \5 g! x: M. `, {3% [0 }: y8 ~. t
    4
    4 p9 U) E* ]* J! b+ `+ T/ o) j5# f# |/ n  f* x3 s
    6: Z0 A1 K0 c# l5 E) O( p% \
    7
    ( |0 L) `' N- K3 b9 H% [3 N83 [, B2 l9 A& }. x4 k
    96 p' n' M8 O& M& s! T6 s
    10
    2 @# ^6 y( o! c( ~/ R7 Q- S- J3 D11
    # s7 q6 o, ?. {123 C) F/ ~2 D! W# T
    13. R8 m0 i) F# E3 H; B" j5 f
    14
    4 m, f  o$ \% r4 Z4 ]15
    ( W/ O9 s+ J5 W% `% S. Z% m1 d* B166 E8 R% i' m, L% ?
    17
    ; m  H* C' t5 n- l) c7 n18
    2 j4 J: _; A; Y, G8 n; k* G" B/ \19
    $ [( k$ D4 z: {  |" k: W/ M, C20
    & t  @! a! Z' x9 _# q第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
    * [+ j4 @5 p) D: n1 I& is.dt.is_year_start # 还可选 is_quarter/month_start, K: [' j2 t& G, c& Y5 }: B
    Out[40]: ( w+ N, c! @  g& D
    0     True) ?; O( B5 Z  y8 D% C# Q2 Q
    1    False
    ; j- [8 W0 w; F* O: t" R+ x2    False
    $ D/ w! ^/ N6 `. `$ d/ s  j+ s% odtype: bool
    9 c& }; m8 L/ [6 b. I9 v. V4 M3 N* }+ Z0 R3 ^5 ~& V
    s.dt.is_year_end # 还可选 is_quarter/month_end
    5 ?4 g! ]0 F) K/ E6 `6 [6 ]: pOut[41]: 7 H. @6 a; X$ N& `- S4 u
    0    False7 D/ S3 g) e3 p3 [* p( E) A% b$ \
    1    False
    ! K) Y  b5 C; V7 @4 A: J2    False
    9 N6 e& j& v% W  h& k4 P# @! J, ndtype: bool% t, {, X5 v9 u2 n9 d) ^
    1, c$ ?+ ^; L. E: Z" X
    2; N8 t8 g3 L! i' V& ~
    3
    " p* w7 I7 j* c" h4  Q" c+ I: j, O* [+ R
    5
    $ _( [) P* N, V' Z0 }6# h6 {' N1 |: G, o  E5 E
    7
    6 i9 P- i9 M, G! x( Q8
    6 j( M& [8 B) }" ~1 p9
    ! f$ S# y+ d% p0 ]9 r! m; y10
    9 ]; z. O2 k) W# {$ n  Z- F: W: t11
    ) _7 d4 L  K/ X12
    , p: u" x6 U. x0 k2 C! U; p, H139 _, ^0 W1 D# e- G* i3 D- ~
    第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
    8 g4 g; C5 W  [& {' Ss = pd.Series(pd.date_range('2020-1-1 20:35:00',
    + V/ }4 b- O" r% b3 Y- \4 D9 O                            '2020-1-1 22:35:00',
    0 q: V3 n$ m& o: I$ Z                            freq='45min'))/ D# i( y$ z% S6 h
    $ ?+ ]( ]+ E. p6 Y( K1 Z- O

    , j8 V( B& i! A" `# Bs" Y1 {( |7 m( Q  G' T: K
    Out[43]:
    4 l9 }/ i& k! Z' {% p6 C0   2020-01-01 20:35:006 E) b8 P3 `  `3 \9 ]1 Q
    1   2020-01-01 21:20:00
    & k  v7 s1 {! `9 j6 C- j  E0 m2   2020-01-01 22:05:00+ _! i, V8 C* L
    dtype: datetime64[ns]1 G- J0 v5 O; s! ^( {8 @, e; |
    2 s! V; G7 K" x9 U6 v# q5 T
    s.dt.round('1H')
    . R( ~3 E* w" {$ ^6 GOut[44]: - l: a- U6 R% A* x
    0   2020-01-01 21:00:00
    5 }$ |4 E: R* `4 S6 j, E4 W5 S1   2020-01-01 21:00:00
    2 h5 H$ a* M# l/ V# m2   2020-01-01 22:00:000 X9 b5 ~+ r+ F3 W$ s
    dtype: datetime64[ns]' k" u4 P4 z+ [8 [6 t7 e) m
    . _: W; n/ R8 f6 f# b  S, e
    s.dt.ceil('1H')
    # e4 Z- }( g8 g5 OOut[45]:   A; U3 b9 ]7 u
    0   2020-01-01 21:00:00* [) l& p2 v7 |! `# ]9 ^
    1   2020-01-01 22:00:00
    ; n9 ?- o) a7 Y  |4 o2   2020-01-01 23:00:00
    * O. Q/ ~% ?+ a0 J+ ddtype: datetime64[ns]- o# M; f& S0 @9 L) b- t( t

    6 `. c- U1 ^+ R8 Ys.dt.floor('1H')
    # g" F% p( \  _9 k$ O7 S  E: r0 ]Out[46]: % B0 g; F$ \' f$ A
    0   2020-01-01 20:00:00) P0 {. s" }1 |) O$ W
    1   2020-01-01 21:00:00
    2 y, U/ @# I1 X  ?7 @, s7 e2   2020-01-01 22:00:00; |- D8 a( M( Y/ X. T0 r9 J
    dtype: datetime64[ns]: i2 y* r! \% X: ^% f
    6 c/ [: y& C7 I1 X# Y; t
    19 G7 l6 T  N- W1 m
    24 W, F) B2 A2 X% T0 z3 D
    3/ o) m/ E+ R; a" l" c2 o
    4
    8 q2 ]  s! k' X& D  u7 Z: i5: x+ o, k5 n5 t" F8 \' e
    6
    ) R1 ~: l% g3 _; X$ G1 d7
    % g2 K* ]0 N3 v% ~3 }8 T8, I7 \( H6 V* O* R" {, [  C3 p& D
    9
    - Z! S/ L& z( o$ A/ i  e* {10
    8 i, n4 D% ?5 z! H0 T) O. Q6 {0 I2 @11
    7 @& \( o7 H$ H9 b' r12
    # t& d4 M1 m" k  W( U13" U6 c) A) K+ r- c7 _& t1 W
    14  B# \, {8 x8 ?$ Q8 x( d
    151 Z  W2 c' c/ W& w$ t7 @
    16
    0 j9 M. K- s3 G. Q. [17; P, O' l$ O" o& J6 V4 S5 D2 C  }
    184 A( }% ~  P( k
    19' F  M, k2 ]; d
    207 e6 ]$ s- v" {- s
    21
    1 n- c, s& D: q: ^22, n  f, C/ X: j7 [
    239 F) R" f) \% T: H5 J
    24
    # D) o2 e# N# s/ m. `; g3 n25# W0 b; d( X' }9 A3 u$ p
    26+ }, U; A8 O4 h9 q" J: v
    272 X5 q% N+ f' ~7 E: D  j5 w
    283 E8 C: ]" e- F9 M+ h$ o
    29
      q+ N: J% O/ t30) I( E; T3 L0 v  M1 ^+ z4 w! F
    31
    5 f3 Y/ x+ l1 S. S' K6 v1 S32
    4 s; }- X: x3 w; v10.2.4 时间戳的切片与索引
    & J0 @" B7 N2 g) E; k! f! ^  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:
    * Z. g* Y( a( j/ Z. S* ~" |( h6 z9 F  l' B: j6 b9 g
    利用dt对象和布尔条件联合使用
    1 S: {1 i% Z/ n; d9 B3 R& i利用切片,后者常用于连续时间戳。3 {& D  J1 w. _' q4 L$ _
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))* q' m) ]! Z: p
    idx = pd.Series(s.index).dt* `, j, X$ R6 B
    s.head()) a1 p/ H% X5 W8 F4 [' n7 x$ }3 ~5 M1 U
    / d4 x4 ^- o1 G& N
    2020-01-01    0( x4 q" }  e% Z# f' Q! i; z
    2020-01-02    1) S0 m) B# a1 Q/ ~' d: y9 m/ o
    2020-01-03    1
    ' i! o/ J0 `  o# [6 ~. P2020-01-04    0
    % A% N) C, _! W0 A9 z, x/ \2020-01-05    0
    ( z' \/ k- H1 M3 \: F2 I: j/ z( YFreq: D, dtype: int32
    3 Z6 Q4 {- R* m8 l4 j1# i% Q% A4 X1 I7 [% `
    2
    - T' [" ^/ w) p3' J+ ^* p( E7 [( m4 X+ p( I( L1 W
    4
    4 v6 g; s/ ?% V; v. x% R7 P54 \, P9 E2 Z& J' G
    62 J, i- m7 k- R4 M1 T( R5 w; R. L
    77 I. X% r$ _/ N3 _
    81 ?2 e$ Y3 B8 I
    9
    % i3 k# p: d' |- U7 Q$ e- H10
    - P7 L" V1 Y! g& ^# h- TExample1:每月的第一天或者最后一天
    - M9 M/ j3 Q4 g# i& p- D  U, x2 r- W. N
    s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values( Y! ~( u0 m1 _3 \1 X$ H' o7 R* ?( ?
    Out[50]:
    / K2 }2 r! I, f. ^# B  T; K3 j, e2020-01-01    1  t8 D1 o# O) {/ T" q
    2020-01-31    0- D6 D8 N0 E. X5 }/ j% h0 r
    2020-02-01    1
    & d5 P4 W! k% ?7 @1 ^& Q' _( w2020-02-29    1
    ; K/ q8 `) l  p4 g- C2020-03-01    0* [% F8 p* B! b' g( k, h: j3 \
    dtype: int32
    . c" l. C0 ]% M$ P1
    % w/ U* ^5 e4 Z; P+ Q; w& p2
    , Q8 t  R1 T1 ]* u3 l3
    1 }) ?+ n- p5 f8 X- O  h; Y2 U. p4
    ' C6 F3 L3 W3 V5 l* ^) H$ s8 p9 Z5
    $ c- v! x# h2 @; G- f4 y+ Y4 l3 b6
    ( ^$ n( t& G. ?' `* D  S6 Q/ ?7
    6 Y- O9 C& X* {" b: p" J% W8' ]$ x  m( F1 z5 h7 W
    Example2:双休日+ c$ n. ]* a# e* y; m+ f- j8 H
    3 o% G/ z$ R; A) l: k9 o/ ?
    s[idx.dayofweek.isin([5,6]).values].head()+ ?) E# s4 T8 @- I5 B
    Out[51]:
    3 s! }% h7 A1 g$ E1 p0 ]2020-01-04    1) y/ M9 _, @+ A; m7 \7 z& q, m6 `
    2020-01-05    0
    1 w/ h7 O) M  p2 F: d6 n- a2020-01-11    0% H; I4 m6 l0 j* ^) s" n
    2020-01-12    13 E5 J" E2 f2 X  G: P
    2020-01-18    1
    1 l, N8 n4 u+ B3 W# C% X7 A: }dtype: int32
    * P; e1 @  [! y, i1
    $ V; Z  E% z7 w* w2
    & Z2 t1 y+ y- X% `  Z# F- E+ D3
    3 z! N% z2 P2 m) S' [- z6 A4
    $ A) s' x* {7 t6 D) ~6 S( ~+ W5; V3 ~- Z0 l) a, z, f9 N4 u' J
    6" @+ R0 _2 P  K. C& S
    7
    , d+ e+ P  \" L* e9 r2 O8 m0 P8$ `# U* w( ]7 p3 C
    Example3:取出单日值$ z% D' c( @8 U! H

    8 U& E, R0 F; D; Ys['2020-01-01']
    6 t% Q1 x+ l  T9 X  }Out[52]: 19 N8 }7 A# J' c

    2 c  b2 X$ J# w& \. {& Xs['20200101'] # 自动转换标准格式$ Z7 E: X+ ~! N+ U3 K
    Out[53]: 1
    % ~6 E1 {" R1 K# l6 q1
    - [" k% `0 d5 y; @2
    1 I% H, f- N& N* a1 T$ r5 K3
    ) q& n; d  j6 e4; r3 y8 T* A. U1 N/ f: b2 \8 a
    5  u: O* E4 O/ u+ X
    Example4:取出七月
    + K: n# \3 ]  G+ K2 w  g, |! J# D  B4 Z7 T" ^9 q2 i
    s['2020-07'].head()- f4 A. O, y& ?
    Out[54]: % I9 F! i8 q$ ^
    2020-07-01    0
      S0 E1 Z. e  v+ E- x3 w2020-07-02    1
      P4 }# }. j& U3 k, `; y2020-07-03    0& q+ R. N! u, \, B- U
    2020-07-04    0- |* W( i3 H3 e& b# X1 A
    2020-07-05    0
    , @% _  w; F( H% T0 P) X) S! q, VFreq: D, dtype: int32
    - @9 Q3 b4 g3 l1
    4 U6 @3 l- d3 p$ B; a0 l" G+ _1 {: b23 @# Q/ I2 Z' q* |
    3
    2 f9 Z( c0 _% A( H9 L% c4
    ' |7 y+ ^+ Z' ~" W+ E5  x+ @0 L2 x0 Q1 P
    6
    0 ^: ]4 q7 A0 ?. N7* q8 U0 B" G0 g  [3 F, J3 m1 ~2 o
    8
    # x0 K* m0 t& G5 H, aExample5:取出5月初至7月15日
    ' R  [( u6 ~4 E( R( t0 ~" |8 w5 J' M( z/ w8 J
    s['2020-05':'2020-7-15'].head()2 f) |8 ?/ t3 h/ f# z
    Out[55]: 9 T. H# l4 |4 u9 i% K2 c( F
    2020-05-01    0! D/ m6 _& B) _1 b
    2020-05-02    1, ^8 l1 U. l5 @; K
    2020-05-03    0
    5 j+ s+ ^" f& q; b8 U1 {2020-05-04    1
    , v9 a* |8 o* |# A2020-05-05    1
    . G: w$ \% V2 S% j: oFreq: D, dtype: int32" q0 U1 ?4 a+ j2 I* R; D' u* j

    8 n! P. Z/ x1 R& W. q* ys['2020-05':'2020-7-15'].tail()
    4 ]0 N$ k* H0 s$ u$ DOut[56]: - L4 C7 r* I1 J# V7 f
    2020-07-11    0
    6 F, \! z! f% H7 U" |2 f2020-07-12    07 j5 E: e" R9 E% g
    2020-07-13    10 }1 X" Q$ @4 s7 U. }
    2020-07-14    0
    + L  Z4 i% z) q6 }; D9 ~  r2020-07-15    1
    ) q4 i  ?- L6 |& ^$ k) ^Freq: D, dtype: int32
    " D  B; U8 `3 \
    ( m1 t( A3 t7 }4 p% m$ ]; B14 A% M' q: Y% |4 ]4 E1 L4 D
    2
      H1 N" g& i0 N7 v. z3
    : a- O6 I2 t: e1 H5 G4
    # C$ w" P" m* l; o9 A4 M: ^9 D5
    ; g+ E5 a+ @) ~; B4 G5 P. k6
    & d+ h0 m9 I! A75 `, x# Z1 B2 I. O" F) v6 k" y
    8. Q  Z; A- @! P7 w! n9 e) i( E, B
    9& {! f$ x( Y7 m- L* r
    10
      ]: j  k9 o+ f! A* f$ z6 s+ |4 w: D11
    & g. y+ c! A" _126 {9 x1 j# s# _: c
    13& ]% \! j4 x; Y) S* ~5 P
    14
    5 V3 r' N5 G: Z- u5 O0 ?1 z" U15: @9 u, q. M/ E$ s* U; V0 k$ v, `6 C
    166 m' s: Q. d! z' {
    17
    ( v3 j8 h& ^% s/ t/ _10.3 时间差4 P, h2 B/ e+ `1 r
    10.3.1 Timedelta的生成
    ; G* y3 f) V" J  _' b: B2 Y4 @pandas.Timedelta(value=<object object>, unit=None, **kwargs)# V& L- d' X4 ?9 n( v) p) @2 M# [; e
      unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
    ) l. N0 o1 P: L1 V! K7 ~  可能的值有:9 t. D5 a$ L5 A

    1 [) k) e: f7 @9 {‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
    : O, c0 J+ j1 n) I4 U! U& [( A* w‘days’ or ‘day’: u! Q+ r' W5 ?& [# ]- {3 [, V
    ‘hours’, ‘hour’, ‘hr’, or ‘h’
    / n% K. F; d5 b% I9 r% e% ]‘minutes’, ‘minute’, ‘min’, or ‘m’  Z  }+ f, B) ^$ q1 S3 `
    ‘seconds’, ‘second’, or ‘sec’
      o9 \0 v8 h5 X* v毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’! C2 V; e4 A( X8 ?6 F) i7 t
    微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
    + J( {. H3 b( |/ \' F( }3 b9 h纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
    ! t. T/ z) W0 O2 I7 B' Y( i/ I: ~. A时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:% i- D/ t9 Z( ^& t, e2 j
    pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')$ N& C6 t2 S- N
    Out[57]: Timedelta('1 days 00:25:00')
    ' D( Z, U; N1 \) A) B" _' l
    1 T! y. n; r. V* y; r+ _7 Tpd.Timedelta(days=1, minutes=25) # 需要注意加s
    / g: U4 F5 E  |) D# mOut[58]: Timedelta('1 days 00:25:00')  p7 u) o* r8 [9 o+ e
    * J' e7 U5 \) m; E+ Y+ t- ^
    pd.Timedelta('1 days 25 minutes') # 字符串生成
    9 j1 d( e+ w: I5 o7 {! W, IOut[59]: Timedelta('1 days 00:25:00')
    ! ^! h1 ^4 r8 R; z8 i, K( Q: p  [4 I, l+ J! s; n' @7 g- K' ~
    pd.Timedelta(1, "d")
    4 M4 `; {: x% j: AOut[58]: Timedelta('1 days 00:00:00')* T: x0 J" i' S" b' g. P
    1# F, \7 Z7 f. o) q
    2
    ( ]/ B1 N/ @# p% |! R4 }+ ]: k0 s5 q34 p; o0 |% N8 Z3 K
    4
    " N, `, m0 l- P% U4 ^# L57 U( T* Y# w  y* Q
    6
    3 \) x0 e1 E$ e/ R7
    4 r2 s9 J7 E- [2 z  Z# f8
    5 x9 z5 s( B5 |" q/ o, w3 {93 J: G1 E- I8 }8 V7 o3 J: L
    10
    ; m6 B' J+ B; V# k& e& f11
    * [$ [; i- O! F" e生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    % U( E( ^& z4 O$ Q% Hs = pd.to_timedelta(df.Time_Record)' q) E: `' r4 |! D
    : E! Q( e( Y4 g; t9 J
    s.head()5 _/ N* S$ |3 f! C$ g+ V0 q
    Out[61]: . F9 P( o5 D1 V* K$ s% c2 u
    0   0 days 00:04:34
    0 U" I  I- L  V5 X) }1   0 days 00:04:20
    % V' ^! a- y9 @2   0 days 00:05:22$ i- y* C7 u) Q7 D8 u# `1 V
    3   0 days 00:04:08
    7 _/ z+ b" l2 [4   0 days 00:05:22
    : k* j' M# i+ p5 HName: Time_Record, dtype: timedelta64[ns]. W: D' S( r$ P% d  i& t- ?
    1
    : m$ N& @. @- P, @2$ D. F# k: R9 b) ?: D1 d
    3
    ! }7 ~2 _& c5 V# r3 L( r. U4+ v) k; M, ?% Z' t
    5
    * X- Z9 t4 y' [1 A6
    3 d3 Z, J# ^2 k" N5 @9 R% \8 u; j7! ?) g7 o( f4 m% ~, m
    8
    : \5 i& m2 [/ r# s$ {( u# O/ a5 T9( u  A* I8 c* o. \! Y) G
    10
    $ W/ ]) V4 w  W2 T% x9 @' ^! Z与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
    % U/ R. o# t- W0 w+ S$ a' Rpd.timedelta_range('0s', '1000s', freq='6min')
    , \; S3 [, L& I6 M3 jOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
    / O" g: N  z3 l1 v+ ?+ Z1 T* U) |$ ^  D9 `3 L& K, q3 a( m
    pd.timedelta_range('0s', '1000s', periods=3)
    8 \' D% v. K4 uOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)8 }/ J/ N( g4 ^- ^
    1: \# i4 W4 C0 I2 \. C
    2
    , W' W+ I) {9 L/ r8 k5 Q3$ M+ |0 F% j; E
    4
    . G1 i6 P/ H) n! g& o3 }; G5
    4 S3 A% U# d- \3 w0 l  l$ K; F/ ]对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:# t, x. i4 v# p
    s.dt.seconds.head(). {# K+ }9 ?( b5 y9 c! M
    Out[64]:
    & L! @8 _! q/ Z  D2 j# R0    274! f% q5 m" O( z( E) u
    1    2603 r, p- l% L) Q1 v: B( n# l
    2    3227 c% b' d2 C9 t! L6 e" a) k
    3    2488 L7 Q: T/ v4 n# ?
    4    322
    9 c; ^7 g3 W' ]& M8 Y  pName: Time_Record, dtype: int64: _, s; B7 s# q" g
    1
    : E2 i4 d3 [) p4 C" I, q! J2
    ' y% F; O+ h+ K3
    , O/ Q; {+ W1 v+ k4; x" s+ `& g1 b' k( I
    5
      P& Q$ {5 C, D/ g* P3 s, h6
    ' T' u' M5 _2 `2 r- @+ i: N% ?3 B7
    8 s- n) |; D. ~# b- @/ F8
    - n4 E% x6 x5 J  M* B如果不想对天数取余而直接对应秒数,可以使用total_seconds+ l+ D# f! x7 ?  ~) g2 [
    - `2 w1 }/ G+ `+ R- |1 F
    s.dt.total_seconds().head()6 @8 C* }. Y! Z# M& g
    Out[65]:
    . h# m; s6 d1 ~0 O0    274.0
    / E2 `$ z) B/ C0 A5 b1    260.0, u& T$ ]7 e9 M& |# B& t
    2    322.03 p/ M* R2 K3 |. u) X4 [+ R
    3    248.04 B/ R' Q) g) B% e7 t: J
    4    322.0% b( T( f0 T& U. j6 ^/ T* s
    Name: Time_Record, dtype: float64
    # P' m% \# p4 F: d16 {7 h9 T6 n7 A- N* D
    2
    , C, f5 u, O. |3
    $ o3 C( X, m2 _: ?* W3 g9 }: y; N4
      C6 L  `# Y) }& p5
    6 \$ g3 s+ h+ k  Q8 |6( {3 R0 N6 U6 o3 k
    7( k( l7 _& g2 F: X1 C# i
    8- @6 [3 g2 x5 @- X9 x
    与时间戳序列类似,取整函数也是可以在dt对象上使用的:
    # L6 f- y7 |5 u& a+ E8 L; n+ y& u7 _- L7 h- \7 H- d
    pd.to_timedelta(df.Time_Record).dt.round('min').head()
      B# F" Y' S8 i. a5 `- POut[66]:
    3 }4 z' A3 ?. {  ^# l0 _6 }, j( f7 W# t0   0 days 00:05:00
    4 @! ^; H, v3 S1   0 days 00:04:00
    : Q( {& I: x9 p2   0 days 00:05:00
    ! \9 L6 M# f; u2 x% t" ^2 L3   0 days 00:04:00
    1 L( K- ~1 h0 }% t: p4   0 days 00:05:00* Y' e2 w$ r7 f* d2 W, Y1 E
    Name: Time_Record, dtype: timedelta64[ns]
    4 r0 _# v6 f9 M5 H0 L' o% J+ _$ c1
    $ y6 q  c* J1 f2 I8 A4 f2% ]( P/ o8 V5 O+ I
    3
    4 f$ }, y3 \. c; d2 U  W4
    % ^8 C" y  a; t: Y! J" f8 t5
    . c1 c7 o) P7 V4 W. N% U& J6
    2 O0 m5 {2 G( D! q79 T, v% V; h& n$ U( U
    8  Q0 L/ d* g) }
    10.2.2 Timedelta的运算
    0 f7 t* ~/ B: k% x' g单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:2 V* T# k8 s% Z+ J5 |
    td1 = pd.Timedelta(days=1)
    ) q  g. ]8 ]5 f, @td2 = pd.Timedelta(days=3)
    5 g5 I3 S. w/ D) ots = pd.Timestamp('20200101')
    # \- B$ O1 k" E& |  A0 D& f
    7 X' n& q! o/ C* s% X) W0 d: ttd1 * 2
    ! ^" ~1 B- ]4 F0 |3 P0 vOut[70]: Timedelta('2 days 00:00:00')
    , e+ F' n6 I& g# s
    / g# i5 Z  P5 V  ktd2 - td1
    - q0 c0 T" M# W1 H8 }$ _Out[71]: Timedelta('2 days 00:00:00')
    & J9 K7 q& o# ^4 Z/ y3 G  ~6 {, \
    # \" p) ~- q# q2 t4 {. e0 z1 @) |ts + td1  f& p! N) u. e( D5 @
    Out[72]: Timestamp('2020-01-02 00:00:00')
    7 B0 K' Y8 M# w6 S" s( W9 J, y( K1 w* H; l6 ~8 U; Q1 V3 z! }
    ts - td1" G2 o9 f" Z+ H, v( Q# I# e; [
    Out[73]: Timestamp('2019-12-31 00:00:00')0 d. r0 t1 R1 h: J
    16 N/ S: ?0 E; l0 `. E7 F$ v
    2
    2 j0 e) T& g8 y! z# B7 J8 l) k# r3
    " i0 C  n' j% n# E: F  j4
    7 b1 h! W  `7 Q+ g; Z8 [5- ^. X8 x+ b7 v+ G
    6& `+ Y; w: ~# U0 Y- X6 s* O% D& r
    7
    , G. Z% I! N3 I" }; ^& @7 p- J$ @8 z2 z8
    ; R* d' E3 u3 G1 N* C# K  [92 }+ `5 H* I7 _: u9 C
    10
    1 p( X; k; J& e3 \$ t* A* q* s11
    1 V. ~$ s4 V  {: N2 {12
    - `6 m6 T3 j6 B  m+ r! q. O# R, h  u13
    6 e5 e+ g  k' Y/ @' L) X& m) P14
    ' b* i: ?- p- Z; M+ m8 t7 A3 K" z4 S15( W, A+ Q  S1 D6 t# T5 _, j' D9 \& t
    时间差的序列的运算,和上面方法相同:
    4 f0 Y, E8 f, J# X  x) D3 Etd1 = pd.timedelta_range(start='1 days', periods=5)' I0 e2 E/ a4 d$ x) ?9 ]
    td2 = pd.timedelta_range(start='12 hours',
    / Y# P+ e* y) P                         freq='2H',
    ( c5 q) Y- {+ z# |3 `0 ^5 c                         periods=5)
    ! E0 {4 R1 A1 w8 |6 @" its = pd.date_range('20200101', '20200105')# [: K" z+ [2 u8 Q9 z' c& v- G
    td1,td2,ts3 R: G  @( C! E; }- ]0 i
    2 i6 [( V. f$ ?( f/ L
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
    - n$ `/ B3 K& O. l7 w# wTimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
    # [+ Y- X; J& A2 V+ Z# F- j                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')& T( a, D: Q3 n3 N5 S$ \
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',9 u/ Z; b, E8 U3 r9 s# u
                   '2020-01-05'],
    7 R2 H. P/ H4 C: F( u9 \) W              dtype='datetime64[ns]', freq='D')1 j& f* Y8 l8 _  r4 z2 X4 ?
    1
    . ?( x- y( r( F3 ]) d+ h5 y2
    ; m5 o/ V$ ^9 V3# M. C6 b- y5 e7 f6 Z; j9 Z
    4
    : z7 o) t- y  ^' v+ B6 y7 H5
    % Z8 G+ w, ]! z7 n2 j6
    - A$ u  d1 O2 S5 J) H; i: J: m* z7* n% F6 r0 z# F# ?) |
    8
    % T& k: W- e$ M# ~3 Z, b# @6 W  P9
    & ?; M8 ~1 \! m7 y" q0 |, x+ s- q10
    3 |1 P% }% ?8 z  Y- E117 V% e  F& ~3 V+ z/ Q' \
    12  H8 }5 h9 A; Y4 M
    13
    ) y2 t* ?5 G/ C) m8 a1 Htd1 * 5
    6 Q4 S0 S7 x1 ~9 A$ [4 e5 o& {Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
    5 n. c( U% d( B# |
    8 J7 b) y5 p" u9 m. u! }6 S. L3 Gtd1 * pd.Series(list(range(5))) # 逐个相乘6 J& i. v7 Q6 }% ^& Z, H; G
    Out[78]: ( \) ]0 n- V7 D( ]  g
    0    0 days
    " m- L- `9 o4 s0 a& R% K  k% T  v1    2 days
      e& `4 L- P6 l* g2    6 days
    ' C9 |0 j- @+ i/ h. n* _3   12 days) d9 O+ f# u* I# }- Q7 J. o9 A: o, b* w
    4   20 days8 L' g1 {* @. d6 V+ X! [
    dtype: timedelta64[ns]
    2 y4 Y9 \# [  j4 _  J: J! G+ R' v( g9 G  G
    td1 - td2, u3 o& W/ w, v2 D
    Out[79]: $ S# u3 n3 i) v# j6 k
    TimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
      r+ b# Z" x1 q& s  y* O- m% ~                '3 days 06:00:00', '4 days 04:00:00'],
    ! A: l( \+ M4 h/ H3 x               dtype='timedelta64[ns]', freq=None)
    . v! B8 O- r  l: M: r5 ~! F' S( I  \+ V5 a5 }2 e. M
    td1 + pd.Timestamp('20200101')
    0 |6 d8 Q: D- Q7 Q: D$ cOut[80]:
    # {3 r. e8 t* z& H: i9 o* |DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    - u& }" Z4 k& E9 k               '2020-01-06'],dtype='datetime64[ns]', freq='D')
    ; ?! y! T8 h2 Z, z! @' o$ g
    ( \6 i: V, E2 [. B) Rtd1 + ts # 逐个相加6 m  e* L' G2 ?" V. U
    Out[81]:
    1 r  Z: R' }! A2 i( RDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',; Y9 O2 e3 a- Q. W+ X0 ]8 f
                   '2020-01-10'],
    8 g5 D4 G, y4 D2 ?, }. D6 m, r& [, F/ E              dtype='datetime64[ns]', freq=None)- C7 I; j9 P7 @2 A% B) [

    3 p# E4 }$ W, m2 H1( [" m4 J* n' E" t
    2) l' X" Y: a3 W8 P4 s0 f, v
    3
    + U  |# M( ~* C. H4 ?: S, w/ G5 z4
    3 @$ l4 V, x4 g5
    ; i4 |$ d( @/ l; w+ y: u6
    - B; N( `7 m# L. y4 E7
    " W; t0 |7 Y# W( F83 t; |. u/ a5 W' R( F+ I
    9; C4 W2 ?9 C0 }  M: x4 F
    10
    9 N$ m( i2 x& T( D1 Q11& e3 N1 V+ O3 D
    12
    5 ^: Z  D& P( }4 S) L13  S7 M5 \5 x8 [$ A% A& }
    14
    4 t6 n) p% F7 v3 \# Z15
    # r% p; d0 A7 l. K16
    7 |! Q: R& C# U& n17
    9 F1 w  [# ^$ S- O, }1 r- w18! j, ]- f! y* S# z& B
    19$ s" ^% ^% a$ t( R, R- Y0 ]! C# A
    20
    # u5 w, y) p3 m# }6 x3 J1 ^21% b& y9 q4 ~8 p
    22
    : Z3 }% k1 d6 `4 o4 R! t* E23
      y% F- S! D- C* U/ q. {24# _6 c! t8 I* j: D
    25
    2 L& F/ _7 Y5 V" g26# o& ~0 F9 E2 B
    279 |3 Q$ x; |/ Y( j0 D
    28
    4 a( I! q1 }2 k! Q2 {, w' B10.4 日期偏置
    $ w6 r$ o; h; W10.4.1 Offset对象, S& t4 ~, S6 H" Y+ v( b
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
    : M3 E7 g, D, n
    9 H3 m: r- P' T( EDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
    8 c1 z8 I& R" ^. n* W9 \/ z. ?
      p- I" L4 R+ G# I  O. [s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
    " H7 z; j" R8 ns.kwds:{‘week’: 0, ‘weekday’: 0}
    : I% ^  d: f9 [8 ^; Q' ?1 C4 {s.wek/s.weekday:顾名思义( v; z) m5 ?& r
    有14个方法,包括:
    & Z( U: B  ^  C
    , {4 g" R5 |/ D* c9 {  jDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。; Q6 r; ^; h4 v
    pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。& R& m; U" c9 [6 S$ z/ ~5 Z5 `
    ; G5 g1 w6 Z: {' u; w
    有两个参数:
    ( c$ h1 J7 Q3 V9 |week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。/ a- q$ z' T# m8 D5 a
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)  P4 r' V$ u; l- ]
    pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    5 g: l; W7 ?' a" o
    ; f  s' q+ j, A* q# Y0 Rpd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
    6 z, b2 x. L; L$ `  D5 g- q4 |Out[82]: Timestamp('2020-09-07 00:00:00')
    ' h8 G6 z# ]! z, V  ?
    9 |8 g: `* \2 f9 j3 K& ipd.Timestamp('20200907') + pd.offsets.BDay(30)
    6 _# C* J9 o% h/ y/ i7 L& rOut[83]: Timestamp('2020-10-19 00:00:00')
    0 P+ X- \: K# {3 O7 }# }: a# E1, |: L& D, \- Z7 e9 F. V  E% P7 m
    2
    ' u! m" d  d4 N3
    6 p" ~# c3 n+ G( }4
    7 k) [, T7 U; O$ ?5 c7 X5
    8 S8 O* _1 Y# l+ m0 B1 t) ~  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
    ( L+ Q/ W: l2 y' {6 n" f  D
    " Q# l- D/ \( J# Wpd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
    + O! y/ d# c- Q/ y' ]Out[84]: Timestamp('2020-08-03 00:00:00')
    8 m* I* M5 A( A7 T" J' g$ h# n, ]! i$ g* Y6 t
    pd.Timestamp('20200907') - pd.offsets.BDay(30)
    4 b( p8 B2 H: z: x7 V' t1 H" ROut[85]: Timestamp('2020-07-27 00:00:00')/ [0 m" i1 m* p- h/ C

    ! {  D- h- ?* opd.Timestamp('20200907') + pd.offsets.MonthEnd()
    8 A4 L) d3 C2 I& L( `# a" ?Out[86]: Timestamp('2020-09-30 00:00:00')3 {- ~7 b$ g( l1 h# A7 m; K6 s
    1/ w5 P1 g' E* b* P6 C
    2& w0 [9 m' s7 K8 K
    3$ i  W0 k/ I8 i! W. e
    4: m' w' j6 g9 k  s( E, O) V
    58 I1 C9 f0 h0 [. X: j6 S: x
    65 i5 A& n, Q  |6 G
    7
    8 s% I) c& Z; ^$ t. @8& ^1 `' D+ `3 ^5 G+ _6 {# k
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。7 Y; @' M" E' d+ r: r
      其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:; s) B4 ?5 u4 |" E; H4 @1 F
    / a( i4 m( F+ F7 G' s6 n$ L- D& H
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    / X) v( ?' W8 t$ c- adr = pd.date_range('20200108', '20200111')0 _; a& d1 l4 p0 B2 Z

    2 k0 c% r+ w1 q2 I$ x. D8 p) odr.to_series().dt.dayofweek/ q$ z2 I! s# p4 \: B
    Out[89]:
    $ n/ \( e# r$ b) g& s2020-01-08    2  `& B$ c  Y* [- [8 ^& G& r
    2020-01-09    3% n# H0 `9 J" S9 t- Z
    2020-01-10    4( s  b) M( |6 z& _
    2020-01-11    5  @! B& w! J5 |
    Freq: D, dtype: int64/ \8 n: j- E# h  `1 G9 j
    & c+ M& X2 {5 J' [$ R1 b  }( }
    [i + my_filter for i in dr]8 y) M7 {# J4 g( |4 y
    Out[90]:
    7 K: n3 ~( b0 f4 C[Timestamp('2020-01-10 00:00:00'),
    : g' w5 ]4 Q2 ^6 s  T- S Timestamp('2020-01-10 00:00:00'),* g. _) W7 a3 ^3 H" {( J3 b- e, Q
    Timestamp('2020-01-15 00:00:00'),
    % x& k9 A9 i' |% B# o Timestamp('2020-01-15 00:00:00')]+ o5 L$ s8 b! O( G) G

      ^/ c8 ]) L8 W+ u; N1
    + s5 r& |$ X# O/ J2
    : z& m$ O% |5 i" J* e% k3
    5 W* Z' s0 I. f# s4 f. c7 l8 U5 ~4
    2 q# o- s7 W7 [7 W  i  ]5
    ( N% i$ o& L0 H$ C6 i, i6
    6 R8 |' h8 ?9 r7 {  X( x, L6 y74 s2 T; M! f1 R7 o. W
    8
    6 Z. |; |3 s: o4 b9! o5 W$ A, i; _, k7 o3 I
    100 n( M5 K  b( d
    11$ `& Y1 m1 T+ G4 [! L
    122 G8 q9 C0 v( i; }0 E) A: p' G
    131 i- _& Q5 O& b8 [8 Y
    14
    % g- Z" n5 M/ E# p3 a: X1 Z15
    . O5 H  O5 z6 `- b  V16
    " R& }3 E0 m- g6 e" I2 \' ?! }17: ~% C# J( W; K* T
      上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
    . y$ T. i& x" z8 Y( |, y
    ; |, }1 H) z$ r$ a1 `【CAUTION】不要使用部分Offset
    # R. W0 @, o% z; r4 y在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
    3 k( Q. h) ]3 R. J9 X
    + K9 ]7 k0 s7 m+ j4 J6 `- w" W10.4.2 偏置字符串4 A/ ~6 x8 X8 S
      前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。3 w8 S2 d  E! S6 i9 Q
    ( ?4 _' T# D2 c' E6 I: x
      Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。: c5 ]5 d$ W0 n. L: {- @

    3 A/ a7 q3 p* d( m) ~* |1 cpd.date_range('20200101','20200331', freq='MS') # 月初+ n4 o1 z" C% G) H
    Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')6 p# w0 ]7 }# c2 k& Y) _6 m7 z( B2 x
    3 |; |# d3 O8 A, u; W3 V/ S
    pd.date_range('20200101','20200331', freq='M') # 月末2 x) s& b" B7 s% o" d- {
    Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M'): u1 H) \* U. X0 f$ [+ \

    5 K$ o9 n* L) k3 M  E. Ypd.date_range('20200101','20200110', freq='B') # 工作日* e! p3 E+ D" i# i
    Out[93]: ; I5 E1 L5 c9 s
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',$ b. q" K3 S5 g* Z
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    , L* E* z2 u" ?3 l              dtype='datetime64[ns]', freq='B'), X, k+ \$ X. Z0 S( c3 M
    ! D* g2 I) l$ l0 z* U
    pd.date_range('20200101','20200201', freq='W-MON') # 周一: B9 @9 F7 c1 V* y" a! H
    Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    : G/ N: Y! Z& }: k, l" H5 ~6 k6 e1 T, a. F
    pd.date_range('20200101','20200201',
    ; S- P4 H2 q" A) E              freq='WOM-1MON') # 每月第一个周一1 |" E+ O0 j. j" L/ M3 Z% I8 f1 ?, d
    * b; K4 T% _4 v- O/ l. c/ C4 K/ ]% U
    Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')5 B) O$ E  h, z1 u8 H9 D( i. w

    5 I% p9 O9 [5 ?0 y0 Q1 T; i7 S% g* Y1( f, K% S: I7 b& W
    2
    4 L4 j6 [# E; j2 O: H+ n' H; e3
    * }9 L0 o$ V/ @/ A) D& }  h4
    - A0 t$ h' e/ M& v' r2 d5
    7 z9 T' e8 U) ^) @60 }8 l& G# P8 R' f" A
    7
    + L% h1 \7 L. G, h% o. F' M8% }  U( u! m) W% w( a
    9
    * h, w, {. t* k" f' O4 s  p, _10, n8 n% a; ^9 G6 N
    113 j" |0 P" `  T# k4 R; g
    122 e; d1 f9 T0 Z1 ~0 Y  ]/ y
    136 h$ G! {4 m; z# g  D
    14
    # R5 ~; z- \6 g. b6 C2 q! @1 m! b- {15
    8 M+ w$ [1 @' R# S/ g16
    ! Y7 |* f* t. N4 K& s- o176 g( L! M7 i0 h4 g
    189 Y% J1 y/ l- u4 g: Q3 m
    19
    8 e' _) E$ G9 h9 |& X* ?/ ]8 J上面的这些字符串,等价于使用如下的 Offset 对象:( ]4 {+ r% b1 u- c+ ~9 }

    : s8 ~  M- V" |0 P! `pd.date_range('20200101','20200331',
    1 |8 p9 T: G+ {8 s7 w              freq=pd.offsets.MonthBegin())
    1 j* o7 n) x, @8 A0 ]' o
    9 a# a- \, e0 EOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')( ^8 H6 E7 }/ ?5 e+ ?& F5 m
    ! k8 _2 j% c  U# h6 _) o" B) }- D( ~
    pd.date_range('20200101','20200331',
    & H7 C) j, x/ W: u2 h              freq=pd.offsets.MonthEnd())% Z! z# @- a3 J9 i# |

    % O" }0 p9 b+ I. @% b: r$ {Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')5 L+ f$ r4 R, E# S

    & K" p2 w; c( T" Tpd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    ) `- X2 G' W- b4 J  ROut[98]: ( D# j1 ]) T1 u1 S( j: [7 X
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    6 d" j1 A5 w% g" r7 c% s               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],, @$ T3 n/ f; \4 b1 Y
                  dtype='datetime64[ns]', freq='B')
    4 S& |- S- V& s; L1 l1 E6 @  \( p& ~7 y9 }  v- a8 u. |. a% G
    pd.date_range('20200101','20200201',
    2 E9 ~6 [" X- W/ ^! ?              freq=pd.offsets.CDay(weekmask='Mon'))3 Q4 f9 l: f; n; T  a  f

    8 k. q/ g8 B" ?- K  x% POut[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')- S  f; S4 t6 t

    , f, d! F  q3 C& Y! h' ^2 A( \pd.date_range('20200101','20200201',
      V: \# Q8 k& N2 w. t1 ?( j              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
      L. c& a' M# i  N
    9 k! y: t0 h% v1 D% o; b1 ~2 h6 pOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    * P/ W9 s0 X' v/ z4 V
    9 ^3 H, c* h( }2 b1
    5 |: W! F2 }' J+ v7 T7 g# I. n: w6 `2) S6 b# P/ c* s$ X+ n% T
    3
    # N' u9 Z) b0 w- K7 i4
    2 U6 S6 e$ w+ s/ o* u3 `53 w$ r3 e4 B7 y% ]! T2 s+ N
    6
    + M3 N0 `: `- Y" C2 ]7% J' x$ e; Z% y9 i3 p* R( M
    8, z! x( d: ]( J# ^5 q: k9 |
    9
    / U( U7 s- e! s3 y10
    $ \/ I" ^5 O- b2 _5 L11( F+ q. E  w% t) T' C- w7 T1 B  H
    12
    8 |4 i' p, _9 h( Y  \: o4 \9 W131 a, O3 D3 C5 m+ [9 B. ?& n
    14
    3 Q! c2 W6 z/ Y/ J( o15. k3 h: X. ^4 _- I7 o( @  m
    16
    ' g1 E7 c" n8 F17
    " Z0 m/ E; R0 V9 L3 T; t5 O5 f186 ]' _3 e; R! J& Y0 g( ]5 w4 n  ?
    19
    ! ?6 @! a$ L3 v' A, e# I( l6 E20. l5 }) b  y6 R% X
    21
    % u) ]0 \0 c( C! t3 w22
    " p- J; K8 @3 x; X& A& `  B23; t6 E4 y$ Q( z) I+ g: x
    24
    & H: g2 {% p4 w9 c' z) u1 u25
    1 Z& O, p! L" m6 d7 j# n  j4 p【CAUTION】关于时区问题的说明
    / Z3 b8 C$ ?: d. y  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。# S6 e" M: N! x# o1 f2 [4 z
    $ W4 k- x# d2 s! `2 K3 t
    10.5、时序中的滑窗与分组
    & E4 Q1 Y  f8 B/ A1 U+ C4 P' K10.5.1 滑动窗口
    ' x% \, e2 Q9 t* b- Z  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:6 T) b8 M% p8 K
    0 w: d7 W4 N5 j1 M
    import matplotlib.pyplot as plt
    0 s# ?0 J  X0 F6 Oidx = pd.date_range('20200101', '20201231', freq='B')
    - e6 \. d: x8 L0 [! c! O/ d& u# unp.random.seed(2020)5 ^, q* X( A! m3 n: y  i9 ~
    ( x) p. U/ u6 i
    data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
    4 o1 U6 a5 y0 C" m/ {8 Ps = pd.Series(data,index=idx)
    ! l3 ^0 t! C  o  r/ Rs.head()
    ' B' z& Q0 n5 w$ e6 WOut[106]:
    + {$ k. w5 j7 p4 T2020-01-01   -1
    - x. o% _, ?2 L" c* ~2020-01-02   -2
    : q/ p( B) G& y0 s2020-01-03   -1
    3 I$ o& d7 Z! u8 B2020-01-06   -11 U- G7 F" A9 I8 G. u3 o) r
    2020-01-07   -2
    ; P6 l+ o  M' i( Z4 \& s! PFreq: B, dtype: int326 ]2 B) b1 g& Y1 {9 o% b; u
    r = s.rolling('30D')# rolling可以指定freq或者offset对象$ Q1 F0 e3 E9 P9 D- h+ b% W
    - t! Z5 T7 `  k8 Z
    plt.plot(s) # 蓝色线
    : E4 Q8 F* F1 \5 T- j; mOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
    1 A3 i+ \- V* q/ S1 r8 ?) qplt.title('BOLL LINES')
    % V  o+ _5 o( ^% _Out[109]: Text(0.5, 1.0, 'BOLL LINES')
    $ {' Z! b& g4 u$ S1 g8 k3 |
    3 z" A) f2 m* A8 g& v" gplt.plot(r.mean()) #橙色线
    $ ~3 a" a2 R7 `2 HOut[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]# x0 `# z, O# F8 X! M+ y
    1 q# L5 o, q+ K
    plt.plot(r.mean()+r.std()*2) # 绿色线1 G: i; ]' p* o
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    . M7 s% O* S9 |1 O, P( l
    ) I4 g. T7 x2 X9 lplt.plot(r.mean()-r.std()*2) # 红色线4 b$ X- q, z9 Z" `
    Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
    + w: C* T7 E8 ?) ?' A
    2 W2 ~. w. ]* j& o1
    ( r  r  c" U) q. t25 O& E! r: b+ n9 U
    3
    + ^8 t3 Q+ n/ R4
    $ O! G) N# t$ z) ~5 y2 o( O5
    5 P4 m5 A# ?4 |0 I6/ e8 X$ f9 N$ ^+ W1 S
    7
    . [8 Z5 T( e# F1 x7 e8
    $ i: |9 H+ P5 n% V' r9
    . M! U% K2 [- x& w8 h- I10
    4 ?/ M1 C1 @# A11
    ! u" O! T3 h8 i- T* X( @12
    % `3 |! Q6 J4 x2 |; \13
    0 M2 v+ a6 {; U) `( }14
    0 E: O' {7 b5 Z+ U% A154 P! j; M0 s: s4 F" K, ?) [
    16& {1 r0 {. R  D
    173 z3 Q+ }8 D* ?& o' M+ k% K( j
    185 W# ]2 @6 V8 y/ b& A% d8 p
    19* Z" X" k7 e$ U; q' ~
    20  ?+ u; e* T2 w+ `5 m7 _# ^; q. X
    21
    2 X' G$ z3 J( q6 j5 G& v! X# L22
    / M9 m1 C1 Y, S6 J2 }) P3 G23) T( S  R4 b4 b
    24; W/ R5 u- [: J3 N3 G- _
    25
    8 a* [: ^6 ?" ^3 ]. T& U# d  l261 u* p+ l5 Q8 W$ i) i
    27
    7 y' R5 w: r  l: T2 z28  n$ ?' e% e1 K, h+ H% z
    294 W/ [+ h* |6 P5 @

    $ N, g# w2 Z( W% b! f+ ]5 T   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。7 D  q" l2 e  I" G$ x
       首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。- G+ y% |0 }$ o1 ^2 ^  ]

    $ @; C8 q- r" n* j- Rselect_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]( L) }$ n5 k7 p1 Y$ K7 B
    bday_sum=select_bday.rolling(7,min_periods=1).sum()& ]7 C) o$ D( X7 @% r
    result=bday_sum.reindex().ffill()& C; g8 E. F9 {! O1 c" W
    result
    4 j7 `& a4 T4 p/ @
    / `# ?$ i, @0 @: U) G/ W2020-01-01     -1.0( b$ x& g0 \  H* D# H! w
    2020-01-02     -3.0. ~, d- z# \6 I. @
    2020-01-03     -4.0
    2 _! Q; \6 O. ~$ a# A4 P6 \) p5 m2020-01-06     -5.0
    0 P& r+ G1 j* |. h: |" G& N2020-01-07     -7.0! A5 v9 c, m1 h1 \
                  ...  
    : D/ l; _6 F8 c, |* C) \  W2020-12-25    136.0
    / m. j7 \, z" L# |& ?2020-12-28    133.0% a$ P! X% l  z& A7 s4 f2 U
    2020-12-29    131.0. G; x3 z; R1 e7 o4 q% L
    2020-12-30    130.0/ ^$ K! d1 q7 p. S5 s! r9 l
    2020-12-31    128.0: Z! I8 u- f# k, J3 m7 o* `% E
    Freq: B, Length: 262, dtype: float64
    " p( ^6 k+ o5 X" \
      {( V4 P, T8 ]) @! X; }$ X6 _) H1
    5 t. m( P1 X2 g5 e2
    : X1 Z" {. p+ n, ]9 y3: z! p5 d/ q- Q2 s( v2 P
    4: p( t  k/ d' B) F
    5/ N! w0 b# S) F% r
    6
    2 J2 Z% `9 |2 {, L7
    + ^& r" {2 k0 p6 E" o" a  d; H1 m8) T0 o4 {) j' n* M5 J
    9
    % ^. U- C6 y2 g/ e% d10& m/ R/ }& w& M7 ?, O' e
    11( L' e) z; L( N
    12
    9 u& }7 F, v2 Z0 o: U13
    7 v2 H) O' n! {; T1 j14- G. L- a$ q2 W9 B: w9 S
    15
    + F3 q) r, H" w- X164 ^; R. b1 `* O; W
    17% }( {/ a# k3 r
      shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。1 X$ L3 p5 x' c, D

    : f" ]4 w+ D, ~! O- W  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    ( q/ J& w" s1 G& G1 U& u; g8 }+ L! w" D$ H
    s.shift(freq='50D').head()* H/ a; f# Q, z6 I
    Out[113]: ' k8 n/ W' @* j, A. ]* c' X
    2020-02-20   -1" j  Q  Q% N  F& l4 S. V8 B. [7 Q
    2020-02-21   -2% ^* i' I( J& Z% z5 }- ]
    2020-02-22   -1
    ) l' |. a& ?$ `7 p" O* \2020-02-25   -1
    * @2 D1 C7 w1 g# q9 l2020-02-26   -2
    2 U+ A7 L/ k5 Y  Q* {dtype: int325 x2 Z% `* |3 T6 C/ \
    1
    0 u' `/ ~; x. t2 `- p8 p4 y9 E2$ r3 Y! ?. F/ ?. S( ]; f
    3
    & @+ c& S# }" ?  ~4# i8 m( v% }7 ]8 d
    5
    6 n8 U/ r" i; l- n1 o; b6+ y6 L9 }8 s% {/ y* E. b1 l
    7
    ) I" y5 }) U. C1 E8
    0 ^* T) F; ]) H3 y% c2 H7 Y  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    9 t7 t: f  A! ^; Y2 b
    * J3 k2 Q9 d/ e5 O. _( Ymy_series = pd.Series(s.index)
    ' i, }* ^& g" Bmy_series.head()
    8 L5 R# e, j0 H. X' D# v7 u+ n: vOut[115]:
      T7 t8 X# U# `9 n2 J) c$ i0   2020-01-01
      u4 U# x* a7 d3 Q4 V1 d1   2020-01-02- G4 n! a5 G) w" |7 n
    2   2020-01-035 |1 ~. R+ c8 N3 \+ E( \: N9 v
    3   2020-01-06
    ; o5 `$ `7 i8 y( B4   2020-01-07
    8 A& _# q6 l$ s3 L' f. vdtype: datetime64[ns]
    5 P4 j( M: `8 h* B6 @8 g/ h
      G# g6 }, P  J" K3 cmy_series.diff(1).head()
    ; L" r. s! Q, I0 p/ [Out[116]:
    / r. W8 T. U7 L( Q0      NaT; f; J+ Q% c5 B" M! H4 N$ ^0 n5 \
    1   1 days8 X/ w6 a! L% V: q0 v
    2   1 days. k$ b/ O9 z$ c
    3   3 days
    2 W+ w' M6 J' W' ]4   1 days+ d) z2 ^1 t/ s1 {' ]1 Y
    dtype: timedelta64[ns]& ?, q2 j- k! p- D7 w, u% `
    0 u; _! Y6 `1 v& b) ~' m1 Z: F- A5 U
    1
    ' z+ ~3 s. ^" z; i, U2 Z: Z$ b2) X$ x& d% X6 ^
    3
    & A" O) |5 k( k9 h6 i0 g: a# z2 j4, s, O( g3 Z% y: `, y& o: @6 f  Y: ~9 E2 _
    5
    6 ?, L. h, P) E! V, N! G* C6
    ! U) z& ~3 h+ E9 F0 W7) u% Z' I/ b, E% C% U5 j* D' E
    8% F. E% C2 k/ l& C6 d: V
    9+ }' z& }+ `% ~3 R. D8 b
    10- z) V  A5 ^6 T4 Y3 x
    11
    0 p! a# u* r! `" S12
    , Y) g. q1 |- @2 v4 @; y13$ U0 W5 @7 F$ ^& I) _
    14
    - D. S, q4 I. S# a% q15) j7 L8 H/ N2 Z
    16
    ; s7 G  Q5 [( B0 s7 e17
    ( q0 n7 d7 {% D7 R6 I6 W9 d) `18" _( R, f2 k- s, w
    10.5.2 重采样; l7 ]! b9 x: Q/ J9 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)
    , N0 I. V( o! K常用参数有:
    $ D$ a* ]# _9 y) ?9 q4 l# N3 Z+ r4 p  j
    rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    6 b% x, F4 x# b! H) Raxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样- |% I8 o" W  ]7 B. Z5 ~$ @1 Y
    closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    2 o& C# _: E/ [+ ?( ?: h# xlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。, R8 t$ j) x! j- w
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
    6 J. `" S9 S& T% |4 D% Non:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。0 ^' t4 ~% n2 {) D& [0 `
    level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。, ?: w* {4 L; I! v& E. X
    origin参数有5种取值:
    & `, ~* j4 T  E( Z( n‘epoch’:从 1970-01-01开始算起
    ; s, e  L$ G9 S" |8 m9 ]; ?5 x0 \" J‘start’:原点是时间序列的第一个值" R% a. U- _4 o1 T0 Q6 F
    ‘start_day’:默认值,表示原点是时间序列第一天的午夜。% T/ g# L, _" H7 D
    'end':原点是时间序列的最后一个值(1.3.0版本才有)  i$ ?! S% I( ]# B% ?- ]$ K
    ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有). A: @8 J+ K4 f1 W5 v- Y
    offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。! {- E$ o  |4 E1 C1 T  U8 r0 m5 |
      closed和计算有关,label和显示有关,closed才有开闭。/ y9 Z& c" Z' `2 ?3 ~/ n
      label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
    : D7 y4 R# z- c0 K* m: e) Q$ E; i0 j0 R9 [
    重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:  ?$ k" k6 y; k* m' q4 y) G
    s.resample('10D').mean().head()
    3 t* ~  [5 a* w4 |8 t& {Out[117]:
      p  C8 n: z9 N/ S! v! a6 n2020-01-01   -2.000000" v6 d7 E* e, E1 `
    2020-01-11   -3.166667
    8 T) R4 B2 ]9 q/ K5 z2020-01-21   -3.625000- T. u6 m* Z4 b, _  J# D
    2020-01-31   -4.000000
    0 M, m% N/ F% b; g7 R- ]1 A2020-02-10   -0.375000$ r* H: y& E! G: x/ N7 K: F
    Freq: 10D, dtype: float64: {; \$ r$ H+ s# S  N0 [7 O. C  H
    1
    . M# E) g- A# n: }: u% `: j  @0 O2
    8 o) X3 b8 {9 _37 W6 @% e' T5 X) ?" D$ W! a& f5 i
    4
    - p  l! I* J4 u7 k+ W, p5
    ; f& b# x8 l, H( L" _) u6  F# O4 E. r& @- t" m* P: t$ B( C5 E
    71 W' e) ?% M6 w
    8
    ) ^5 P( J6 b5 R+ ^4 S; v: o可以通过apply方法自定义处理函数:
    2 p$ `, m2 t, {$ L' Ls.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
      }! y- `  M3 H3 J" ~# ~
    : C8 v: k3 C- o4 wOut[118]:
    3 c/ T+ X- ^& a' K: w) H) N2020-01-01    3/ a4 x9 E: N4 r) e: B! c
    2020-01-11    4
    0 H$ ]; k8 d* \: E; Z2020-01-21    4+ J5 x8 u, e5 n" l7 s5 d+ }
    2020-01-31    2
    ( ]% q$ U4 ]. {& D" P, {! M2020-02-10    4' q, o. }6 ^! E0 z
    Freq: 10D, dtype: int32" X+ r8 `' r0 U, f( M, ]! b
    1% ~) T9 G! v8 L7 ]0 ^' h
    2
    1 L8 x6 m  D% t* v* T3
    $ E3 k7 `' E6 c, T; q4 o- |4
    1 u# D/ p- V5 o+ Q3 v5 X5; C7 d, L+ {) e# ^  A2 _+ S0 L
    6
    6 [0 L+ W" M9 ]% r7# g0 ~" v* ?" i& X' J% n
    83 E2 Q# W( w3 y7 H3 B' P/ @% U. l9 N
    9: U% e- P& x6 b1 C& T9 E
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
    : w4 Q; Y- U2 }2 L6 U! S8 {1 A$ ^' r0 b' n* L
    idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
    3 z  r! A1 g8 q/ B0 G3 Fdata = np.random.randint(-1,2,len(idx)).cumsum()7 ]( a* G3 K8 W/ b
    s = pd.Series(data,index=idx)
    + x/ S4 r; a3 r, X7 R$ K# Vs.head()4 j( g" C$ k9 i/ r

    / n; l8 M# Z0 kOut[122]: 4 H; `0 l: k5 }) t
    2020-01-01 08:26:35   -15 _. O2 G4 `$ c* L  u
    2020-01-01 08:27:52   -1) @8 m3 p5 I, R8 B' Q3 r# N3 ^
    2020-01-01 08:29:09   -26 U) q  I/ F* v
    2020-01-01 08:30:26   -3
    % H  f) ~+ @$ K7 d4 ~* F2020-01-01 08:31:43   -4* ^. T# e1 E5 o4 o; f3 }( I
    Freq: 77S, dtype: int32' a+ d# }9 [, C& c
    1% `5 J  J8 c# o
    2, V4 i! Q, ]+ m" w) h
    3! S/ g+ m% f( F5 u# K- r/ N" j
    4
    " G/ U9 J8 E* X" b( t# z, y5
    : F7 r( j! f& }7 E( f0 U6# T% I& A# e0 w0 g& ^3 K8 G3 ^
    7
    % Z5 d$ x/ R; f: Y2 x8
    3 U& q, d! U9 m92 Y0 [  L" R3 a* |" H" d6 p8 f$ c
    10
    0 \2 m( K! u0 Y* D. C0 Z- _0 A1 M112 u0 j5 C& ^4 O2 h) n6 }  n$ I
    12
    # ?4 [1 ~- j# {  下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:  a$ t. H9 l3 J$ j. l9 J

    4 R. y8 F# |( L2 ts.resample('7min').mean().head()
    2 J. k' X% q9 i0 pOut[123]:
    * O' y  J0 _. j) \$ r2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值! i' J- l! f" O2 r
    2020-01-01 08:31:00   -2.600000. {: z" W; H2 W) ?  q; p! N. {
    2020-01-01 08:38:00   -2.166667
    3 o$ X' v9 l3 S. f2020-01-01 08:45:00    0.200000
    ! E" ]2 R/ _! F" G: J' A6 _& p2020-01-01 08:52:00    2.833333
    : W2 O* x6 \# a: o* P4 j' K3 wFreq: 7T, dtype: float64; M5 D) \+ z. Y( d$ }) s: _
    1
    % n& Q: E6 F' ]" T! s& P+ I2
    + H5 {" Q8 s! N; ^6 K8 V) a3' S6 k- P" u2 l
    4
    0 ~  J3 f3 C! i! v' {' \5- l1 g& l- P. z. J3 O- @
    6
    ' s7 b1 [9 V. n3 N  ^  R/ h73 {' r/ J  a7 b; w* p/ j& ~
    8
    , [: ]- p- {6 o/ H/ I  有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
    . ?! y# D5 i7 W5 E/ L2 g
    + v; F9 U$ p. w% N3 `3 Ps.resample('7min', origin='start').mean().head()* b' p- w# b( X+ G5 f& G! a5 L/ w
    Out[124]: * |( k1 `! W& R% w! q$ v
    2020-01-01 08:26:35   -2.333333
    3 u" @8 J$ A& v  A8 C2020-01-01 08:33:35   -2.400000( W! U* ?) u/ I5 s6 T5 C: |
    2020-01-01 08:40:35   -1.333333
    - z# O$ U* |$ A% h8 T* `  [2020-01-01 08:47:35    1.200000
    7 V) G- e, D; r$ q# |4 \0 X" m2020-01-01 08:54:35    3.166667# |$ l* q, V& G: H" K/ K
    Freq: 7T, dtype: float64! E3 l0 S3 l" ?* T
    14 i; M/ g3 ?3 X1 s0 Q. Q4 G3 f6 ~
    2
    ( R$ S9 s1 M4 y& K- _4 L3* ?( E4 S8 P, G' i0 L: c
    4. G  l$ }. a$ E! ?, d
    5
    ' ^' ~- R) ~. ?! Z6/ [$ ~- i/ h  j" Z; @* V/ W0 Q' `/ l
    79 Z* q8 S! T7 b2 f
    8% ~& q6 |6 D1 |" }! ^( ?
      在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
    3 @# O( r" L  Y* F' Q* T5 h
      L: M/ U4 a1 z1 }s = pd.Series(np.random.randint(2,size=366),7 a! k) f% U, A- A
                  index=pd.date_range('2020-01-01',  l1 C: g" i; [) e9 S4 g
                                      '2020-12-31'))8 E% E& B! V5 T+ u; j1 ]- s" D

    / B( G7 O5 g/ k, S/ g% q
    ' y# T' `& S9 ?  ?$ i. os.resample('M').mean().head()+ a4 z- a6 X$ c3 A! S1 y/ A
    Out[126]:
    : L, n1 G- c4 R: V) p2020-01-31    0.451613
      G' W; s4 Q6 k0 }& `2020-02-29    0.448276
    $ ]6 P* n( ?2 @$ j4 {# s+ ~1 q2020-03-31    0.516129
    4 h, @0 V0 u5 S/ X2020-04-30    0.566667
    ) U4 t9 m! B6 E$ E: {9 Y2020-05-31    0.451613" ]6 A7 ^  o+ m# \
    Freq: M, dtype: float64
    5 P1 B* v( i, Z8 A4 |3 h
    " V+ d. Q2 o! @4 ds.resample('MS').mean().head() # 结果一样,但索引是跟正常一样2 U( r  \* f9 i5 s0 w/ J0 ]; y
    Out[127]: 5 E% p* m  i0 F8 j1 Q9 X" [
    2020-01-01    0.451613
    / p+ I* q6 o9 l( Y/ ?2020-02-01    0.4482761 ^. j% G: p7 L2 @2 G7 v3 J+ L
    2020-03-01    0.516129
    5 u# l3 w0 n4 g2020-04-01    0.5666675 O" I+ }* N' U9 M
    2020-05-01    0.4516138 Y# \# }: \3 ]0 B5 @& o0 ?% [
    Freq: MS, dtype: float641 i0 g: h: R. {  Z1 C2 Y
    4 [2 [4 o  ]" [3 Y* z
    1. g. t' P' {, p3 E4 z: s. N- h& F
    2) y5 l( l, O0 {+ S
    3
    9 f2 F3 Y6 U5 z7 N4
    3 H' z+ M' o) B5 y+ R5 ?5+ V; a( j' y- z* \$ m" w
    6
    . T6 a8 f# d1 J0 z% l7 w  j# x& @7
    9 V1 Q6 h' g: |- G  l2 N$ f5 q8
    ; F1 F7 T  B" z8 R% U, L9
    8 v( C. t/ L5 s$ _& D. m10
    3 ~' g, |6 y+ A& D6 u) h11
    5 J0 F) X+ G9 `12
    4 B1 i$ f) u% ^5 B7 a8 U: }13
    8 o" z3 k& U- z! S2 G14
    0 g- T) |2 @' u1 a7 {3 r15
    5 W1 M" N+ U  \8 U3 x16% B8 S& z: n7 _- }# x
    17
    , v9 G- Y/ N" J" b. }9 j3 b18
    , F) a/ _$ T4 l0 O8 z; m  I1 E19' w0 o3 v3 j; u/ ]* S1 l/ Q+ J; e
    20) n2 ^# \; L, m& t& S
    21
    5 R5 |( r  c' X22
    ) ~) K. K6 w+ N" |' O( ^# A; T对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
    6 s/ V4 G) L* L& yd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    8 W: l( u8 |+ d3 f! y     'volume': [50, 60, 40, 100, 50, 100, 40, 50]}+ m3 x" W6 w- A) y1 J# u
    df = pd.DataFrame(d)
    ' p  e4 M' i' {/ rdf['week_starting'] = pd.date_range('01/01/2018',
    + o6 B- \3 s! i+ ?. N                                    periods=8,  S2 D5 F! g; H! o  `- ?" x/ x
                                        freq='W')$ N6 N6 F: u  h4 [6 o: e
    df" A" V* o, D) x1 O: ~( p2 h0 {5 }
       price  volume week_starting/ s2 d3 Q; d' d
    0     10      50    2018-01-07# m1 j; ^& |* V% W2 D- J9 J
    1     11      60    2018-01-14
    % w! k* L! X- c( }+ L0 Z2      9      40    2018-01-21; d2 B% U% j) K/ ~1 i' [
    3     13     100    2018-01-289 v9 S2 H1 n( h* K2 y
    4     14      50    2018-02-04
    ' G% u0 d1 c7 N8 z, m7 e& v& m' E5     18     100    2018-02-11) n/ t" u: z, @) U7 e8 x
    6     17      40    2018-02-18
    $ H1 m  O0 N+ W# B% T$ O7     19      50    2018-02-250 V4 T4 k* c0 p/ I' n8 p
    df.resample('M', on='week_starting').mean()
    5 T* x* @$ V, i$ i/ f6 o- C. F               price  volume
    / X. r/ Z- A1 ]5 r: J! Zweek_starting
    $ x3 e5 w1 |2 j2018-01-31     10.75    62.5
    / A$ C  e8 K; \- z2018-02-28     17.00    60.0
    ) g! v& x! e: O1 _7 J( ], a1 g7 S8 N* _$ ?4 k5 O+ z
    19 ?5 D. `- Q5 E3 o! b$ g, s
    28 T# G- l8 B( B. O4 q
    32 T: c4 E1 l7 P
    46 x  j* K  x; e  Q2 Z
    5) U+ b( W  f# q, I( E. T" d
    65 f+ }0 k2 Q3 S; f4 E4 z
    7
    9 A8 Z: @+ q( o8/ @5 v0 Q2 P# a  c
    90 M& z- w2 n7 t6 f
    10
    . D% g  c+ D3 `/ O3 a7 G) {11+ O8 C# B7 A) f6 n" C
    12. [4 C9 t0 L! _3 r& Z
    13
    . i, Q" G3 r' O# m14
    ' Y2 V; q7 M7 G% k7 y( P156 k' G- `6 d$ N+ G9 T
    16$ N+ b7 Q' i2 d" J/ \3 Z! t
    17/ c- _2 t( g) D  ]7 f3 K
    18
    - K8 ?- p+ M" C5 V0 S) b+ c19
    * Q( D2 C# D3 L$ K, {20
    6 ^: p0 F1 T3 V7 z& J6 Z7 Z216 o0 M( b# X2 N4 Q, v+ C
    对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。: d1 ^' E. h% ^, s9 g  u) h9 y
    days = pd.date_range('1/1/2000', periods=4, freq='D'). h: x2 C5 {( u5 L7 F; s7 v
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],7 t4 J5 _3 }2 S! h
          'volume': [50, 60, 40, 100, 50, 100, 40, 50]}) y6 ?  [, J( ]; J. K+ I$ B+ E
    df2 = pd.DataFrame(
    ' @: O$ Q2 L5 D    d2,
    ( D- Y' X$ U& n/ b    index=pd.MultiIndex.from_product(
    6 u3 d/ s2 b3 I2 E        [days, ['morning', 'afternoon']]" c4 V; j0 @1 D1 ?$ k4 |' f- Q
        )
    ! S( j( O! P& {4 o0 v- u: t+ T1 n)
    1 X5 w" ~/ d3 p3 U) Y8 C' cdf2
    ! {& q. R+ m2 k; ?7 V! x                      price  volume
    2 W/ X% F; e/ e0 }2000-01-01 morning       10      50
    ! W. E) P5 J0 Y9 q4 ~  I: x           afternoon     11      60+ B% C' e3 n4 O5 y: H
    2000-01-02 morning        9      40( K. U& E; k# L  s8 p
               afternoon     13     100
    : E' j6 u$ s; m2000-01-03 morning       14      50
    - d6 ~) l6 k6 f! k% D0 ?           afternoon     18     1004 Y5 n4 P; q* c; h: c& V: r8 @
    2000-01-04 morning       17      40
    ( m4 E" `* O% G; N' u' W  ~           afternoon     19      50
    . b+ }& {/ @0 R3 n4 X" @6 Fdf2.resample('D', level=0).sum()6 N3 F, {" K% _' G' G" K# x
                price  volume' p* Q0 B6 m7 r2 L6 t6 S
    2000-01-01     21     110+ N- d& q) m( a1 h) {+ C5 P
    2000-01-02     22     1402 t' p& @) H: y2 e& w3 i  B  C+ g
    2000-01-03     32     150( N6 V7 Z; e% R5 s' F8 k
    2000-01-04     36      90" y* ?0 |' ~, `% O' G

    - H& m3 ?( @' q$ \' Y! G5 @1
    9 ]1 Q: `7 N6 {" N: v3 X  I2( L& i  ?  e0 R* J% p
    3* n* b8 l) n' ^; Y8 k1 c) L0 ]
    4
    . d# I8 Q) h! \: G, W1 q5
      N; W" c/ K) R: T; F- M6$ _" K/ b5 d1 y
    7  H' c1 `5 `7 D+ A- x. k2 `! `
    8
    $ P- x0 ?5 G) v2 X9/ d: P; W/ ~! L% \% Y
    107 o( S) j0 [- E1 \9 V6 u
    118 W( Q5 ]& u/ h- X9 {) s7 s8 z. }! G3 B
    12
    5 S4 {& [9 @: r% D: W13& y; ?0 M' G" u: \$ i3 `/ B
    14( X/ K" T, c  b+ L
    15" }! b% t% v( f3 D1 a
    16
    # d9 T5 ?' Y/ {* G17
      T' T; t2 h% Z* k% v" z+ c# A186 H0 o5 a2 o* p/ M" p9 B, A3 e
    19
    ! w9 O% g* f9 h* }- }# y203 U; S7 q# E  d( [4 ]
    21
    & {1 n) m2 P; }6 k4 [22' x: l# N1 {2 S2 |
    23
    8 S% ?( ^/ c9 u- ^& a4 T% t24; B. p( t, h- d+ r$ Y* y' I
    25. e& c3 Z6 y3 ]8 J( ~. x8 K8 Y2 u. u
    根据固定时间戳调整 bin 的开始:" _$ u5 |! h- T( I
    start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
    1 y7 Y5 d' e- O& F2 j, U) arng = pd.date_range(start, end, freq='7min')
    0 K4 p0 a' ^1 l' |  y9 j% E2 ats = pd.Series(np.arange(len(rng)) * 3, index=rng)
    , G5 M! I# c& G6 \ts
    " R1 u0 ~: \% s5 f: A6 v9 W2000-10-01 23:30:00     0
    2 V! G) H; F" l5 K% w% n; O6 }2 @2000-10-01 23:37:00     35 ?# j* Q( l# i) D$ }: C( ^
    2000-10-01 23:44:00     6/ C4 e' m9 {" z1 m
    2000-10-01 23:51:00     94 m7 P* o) O: D! h" [
    2000-10-01 23:58:00    12
    7 F3 s) q/ `  \) w2000-10-02 00:05:00    15
    - {" }1 |0 L' q3 A. @: ]2000-10-02 00:12:00    187 O5 M# d$ p8 g9 [! V0 q
    2000-10-02 00:19:00    21& f0 R# O* C: M& m
    2000-10-02 00:26:00    24
    5 X9 `  i0 z+ \% Q: lFreq: 7T, dtype: int64; y+ I9 J# n8 U4 C  o; u
    - k* t5 h/ T* I5 x
    ts.resample('17min').sum()
    - Z- F* d4 ?3 G/ Q. Q; D2000-10-01 23:14:00     04 J4 d( q4 a* U' g7 N4 N/ m+ e; t
    2000-10-01 23:31:00     9
    : Z5 |0 m8 m1 @0 B! ^3 O9 F2000-10-01 23:48:00    215 i3 X5 [7 _. v
    2000-10-02 00:05:00    54
    , n- A6 J% h0 P9 E: {; f+ l* ^2000-10-02 00:22:00    246 h/ t# e& x5 F% d8 t
    Freq: 17T, dtype: int64" p% D+ ~# T- Y6 D: M

    # v7 e9 [: R# Gts.resample('17min', origin='epoch').sum()
    , [- Z* r9 {/ l2000-10-01 23:18:00     0+ f& ~2 k6 r# X. {6 g* ~- o
    2000-10-01 23:35:00    188 n( |8 O2 g( n$ ~9 g+ G
    2000-10-01 23:52:00    27& O! k5 i& Y; ^9 u" ^
    2000-10-02 00:09:00    39
    ) Z9 z5 P$ I5 q7 D4 e2000-10-02 00:26:00    24' {& Y5 {2 i; t3 E  w. i0 q
    Freq: 17T, dtype: int64
    7 |7 \7 u/ ~" e5 p# p  ?! Q4 S2 F0 E6 B; b% R) t9 y: {0 y) u
    ts.resample('17min', origin='2000-01-01').sum()" {7 A$ K, J) ?8 l% n
    2000-10-01 23:24:00     35 ^: b$ s2 s# H0 `0 r! P
    2000-10-01 23:41:00    15$ z6 q5 e8 @4 C! Q
    2000-10-01 23:58:00    45$ _7 m! N" F- Q! k# k
    2000-10-02 00:15:00    45
    / B$ Z2 a( j2 k: DFreq: 17T, dtype: int649 X; ]$ p/ K4 x2 t/ |, \

    7 y# Z: \  f6 B% S, s: K1, U  l" K* X0 Z" @  S4 F
    2; @& J$ @( l  `6 B: M, l; S
    3- @2 t* ~9 a; B, ?
    42 v& {! X5 E# u0 ~, v6 E7 R
    5
    : Y$ F' Y8 ^5 a$ m5 a61 u6 e) N# E4 [/ y
    7; |* A- g2 Y( J' U' e6 v! Z
    86 S+ c' p* L) A3 {1 L; W. z
    9& @- D( d( C5 Z# |# n3 j" f
    10
    + G0 L* z' R* B  S$ v11, S" L' l# ~" j. V; A# D& h
    12
    5 C$ \) Q- m: W$ G; ^5 W5 H3 E139 p4 Y  ]# s2 r9 c/ B
    14
    $ R' A' m' s  T# L4 u& V15
    ( i  J8 z9 Z! n16& ~6 v% T  b) u4 X0 z' m/ z* ^
    17) h0 V. ^; a+ b3 A
    18
    7 q6 M/ ~: m2 c$ r# Z. j198 m1 m- i' j6 k0 q( p$ K
    20
    / B4 ^; }8 L' w6 p8 _4 ]# L* V6 l( c21
    # W  x% F; p+ e22! A! M- z: x' n) p0 n: x; s# r
    23# ?- a  a6 j( D# j7 g  l5 a* i5 ?
    24
    ! x& B& S+ j' }8 e9 E25
    3 P; D% {; C" n: S: f26% M( J+ @/ B& U  ~
    274 m. {: I, N- l; ]" q
    28
    ! P7 e6 f% H3 ?4 F3 Y) H29' B* D. K+ l, n
    30, f- Z) F3 h* ~
    31/ y- L* _7 K/ F/ G
    32
    : Z8 q' {+ M/ w* T6 b! g9 W33
    1 E  |7 b$ _7 w( y34
    , k& X- z$ b% b( f2 D5 W( c8 M6 }35
    , u4 a" Q$ X5 ?' I, _% `. c  ~36% U0 [4 x# U$ O! Y* E, L
    37; p& X4 \! K7 l8 I9 Q% b7 D
    如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:( K2 g8 A" b) e
    ts.resample('17min', origin='start').sum()4 c" U6 z7 M- }( Y% q& _! e
    ts.resample('17min', offset='23h30min').sum()
    * q7 \: c- C, I9 o+ i2000-10-01 23:30:00     9
    / n6 S/ l0 R' u" v2000-10-01 23:47:00    21: }4 F- L8 N3 w3 s% w: w  `2 @
    2000-10-02 00:04:00    54
    : B" R9 M. e  k% X  P7 Y# q2000-10-02 00:21:00    24
    ' Z4 n; J# f6 {% fFreq: 17T, dtype: int64" e) |) S# f5 ^
    1
    % a1 x/ c( g9 _) ?5 t+ @. ]! u) f/ C24 b" D; i2 X+ X) ?; Z# n6 d
    32 O( q$ j4 h' x
    4
    6 I9 s$ A, ^  l9 b53 _# D0 x" \0 p# u& x) M
    6
    ' J: t2 Z- B1 I. z6 ^& R" a7; K( U' w" \  \. {2 x
    10.6 练习
    6 m% U, b, z0 w4 S# q8 XEx1:太阳辐射数据集0 D& y: J7 T, ~. S
    现有一份关于太阳辐射的数据集:6 k$ j* K6 F! N4 L' A4 v  p
    8 ]0 Q& W# H3 i7 ]0 b0 a
    df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
    ' K4 i  m% Z+ J2 s4 _df.head(3)
    7 m5 O; M* N* Q0 x" y0 m' O7 W2 Q: F. m% {) F0 Z+ ?; x
    Out[129]:
    0 q) f; H8 F5 ]; F# K; ]                    Data      Time  Radiation  Temperature' U% l5 O. [/ }/ Z  F4 U8 Q$ N0 ?" N
    0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
    ; r* s. O; c+ }- H: V: M  S# B+ d+ J1  9/29/2016 12:00:00 AM  23:50:23       1.21           484 O9 H! K! T" L) }. A1 J3 r
    2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
    & d/ Z7 x7 U3 \% o5 z) ?7 T18 |1 O/ k1 r7 t- q! d4 k$ H( E7 r
    2' y1 p- ]* G7 Q3 e
    3
    3 p  T- g( \& I8 E+ d4
    . ~% t+ i& y0 y5 S4 s) c) E55 [; D0 X  b) P9 C* _
    64 W7 a: X# ~$ ~7 V  q
    7
    6 e! a) Y( s, y5 s+ I) M8& r0 ^( E9 e, v: `) J* _
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。# _# C" s6 p  [5 l! U
    每条记录时间的间隔显然并不一致,请解决如下问题:
    ( ~$ i. @/ h+ V* B找出间隔时间的前三个最大值所对应的三组时间戳。9 V  j. n- Z1 \3 k# p
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。" c" B2 |  R6 p4 ^
    求如下指标对应的Series:
    5 ]) P& w: _! [) M& X- }1 r2 T温度与辐射量的6小时滑动相关系数  a) k$ n  L: }0 ^; X! C* j( q
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列: M1 Z& I3 L7 I1 G* o
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    ; |  k0 @, H' O5 R# Timport numpy as np6 F# m1 I! O9 E' q7 e+ Q
    import pandas as pd
    ; i# ~  C5 |3 B0 g$ _! p  Y5 N19 r( v/ q7 A) m5 P9 S
    2+ A+ N, Q, M) d) u) V
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    ) Z  L! A2 w3 z; B% Ndata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列: T. x- A0 p: }1 o
    times=pd.to_timedelta(df.Time)
    - p- f% N. r7 U- e: w; J+ Odf.Data=data+times/ M. ~) A( F- r# h' Q# @
    del df['Time']5 S5 n2 y: J- r8 }3 h. G; n# Y
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留' x( Y* ?/ b1 r4 c
    df
    $ X6 J9 O- W5 c1 U                                        Radiation        Temperature& l' F  c' d- Y2 o- Q5 o
    Data               
    * i0 a5 t/ `+ X2016-09-01 00:00:08                2.58                518 Z" L9 ?  I0 o1 l9 h1 c
    2016-09-01 00:05:10                2.83                518 t+ z0 _: N3 l6 V1 j1 j
    2016-09-01 00:20:06                2.16                51
    6 l6 t$ e$ N+ T: s- p2016-09-01 00:25:05                2.21                518 s( E$ ^! j8 s  q# m4 w" z
    2016-09-01 00:30:09                2.25                51
    2 h8 t% E3 G! V) T6 e" G: ~. a...        ...        ...
    7 t4 X/ g# E5 {1 ]# B) x2016-12-31 23:35:02                1.22                41+ ~- ]0 H4 J# Z0 R
    2016-12-31 23:40:01                1.21                412 v! E& w) B) h2 [0 ]9 O
    2016-12-31 23:45:04                1.21                42
    - h* W7 X/ R, X( o# h" Y2016-12-31 23:50:03                1.19                41
    ! w" ~! r0 [8 V* ^- D1 O, x6 p2016-12-31 23:55:01                1.21                416 }  g" @# w; C
    0 ~( I" Q4 t& N, f) p+ |
    1
    1 Q. l! `2 i: |% {8 E3 n% C1 q$ E2/ z( q, ?( f0 K- B8 Z# Z# c
    3
    3 I  c+ a% W6 j47 J# S4 {; ^) l% ?+ \
    5
    ' ]& {8 o1 b0 M% o+ j6
    3 d3 ]9 T( s. T% i5 V' V4 s7
    9 k& [, N+ A1 }/ _4 P1 I8
    $ i0 q. v4 W- m/ T+ n; t# A* k9) u% s( a" X, L8 ^9 {; l. D
    10/ _$ b3 f. U; m& M* `
    11
    " j" E: E* G  p. W12
    % m. t1 c( X0 T8 W; d13
    : w0 J" ]2 M. \" M( _% C7 O14# ?, ?& o4 X' N% |) W4 ?% o
    157 r) b1 Z8 J4 L
    16
    . W1 `. H3 N/ s7 d17/ H9 ]5 W9 q( k8 k5 Y$ b
    18( J" N7 h( _9 q# Y$ u8 H8 |2 y
    197 k, u7 ]; ~% ]8 @4 \  X' \
    每条记录时间的间隔显然并不一致,请解决如下问题:4 _1 M) C7 L( E/ Z
    找出间隔时间的前三个最大值所对应的三组时间戳。/ U3 ~+ Y# B3 h  W5 U+ l. h5 _
    # 第一次做错了,不是找三组时间戳
    * B# J! Z0 @7 {; `2 J5 o0 k% aidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    0 U* D) r3 A, R  [' X: @df.reset_index().Data[idxmax3,idxmax3-1]
    ' N2 c/ A) u* c, ?! ?3 W
    5 P$ o* O+ I3 m/ v: P25923   2016-12-08 11:10:42, @% s& o9 {* x% O( p, Y* C, n
    24522   2016-12-01 00:00:02
    4 k1 n( \! {: o; W7417    2016-10-01 00:00:195 B8 }7 ~+ U$ `
    Name: Data, dtype: datetime64[ns]3 c, v# `! J4 ?
    1
    ) |9 p" j& q2 M& F- E27 }  S6 ^6 s9 H: ]% o
    34 y/ O5 l, {7 ]6 W, k6 n
    4& J6 k3 d0 Y! d( }$ w
    56 V8 a0 f7 ]4 E7 T6 w- o8 C4 G
    6' q  C5 \: n0 f5 S$ R7 I- u
    7- p8 i1 U- `' W6 P# H0 B$ x5 M
    8
    ! V9 j" d  m* u4 A* R, jidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    # Y3 g* \) m" x  J5 E* k0 I" dlist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))- P' e+ ~! F" U" J; L' {* o

    4 k) {1 I  _9 V3 j8 e[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),3 A; H2 ?# P# t# [0 I
    (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),' E% a( u& y, _+ F9 f' o, r/ ]
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
    8 S  t) G9 e5 J1
    . q4 T9 u8 l! g1 K3 o' z29 I5 U6 A7 j' S  \8 O
    3. v; C8 g- \* A; s+ o% T/ @
    4! w' }5 U1 ~  {) A
    57 D* t# b) M; ?) p' S
    6# L3 U/ \% N; D
    参考答案:
    $ D$ {* ~. B5 T6 ]7 R
    ) e( I& l* I, b' Ts = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()6 O! q: d7 }9 k# @( X( ]
    max_3 = s.nlargest(3).index  s8 Y- I. u, \( S9 w* _1 A
    df.index[max_3.union(max_3-1)]
    3 F" d  A/ W  `$ H0 n& B4 v$ k$ M' v) K8 U- t  s0 b8 ^
    Out[215]:
    6 z% j/ a3 t3 S4 g% p1 V2 a+ _DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
    9 `6 P0 C% W3 ]               '2016-11-29 19:05:02', '2016-12-01 00:00:02',* q. Q' w! X6 O1 w1 V9 N+ h' F
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],: L1 v' j7 I# B& V  i* p5 E# F
                  dtype='datetime64[ns]', name='Datetime', freq=None)) ]5 P/ n+ i5 k4 G
    1
    , R  f2 q8 w9 p8 \1 A  N' h2 ~2
    , j) X( t. ]* D% z30 ?6 I7 F7 }) ~# N4 p
    4* ?" p. R. {* [( Q+ Y! ^) l
    5
    8 o1 w- ]. U7 Y& l; h5 A6 l6* ?5 J$ W6 M) N4 ~5 u. c
    7
    ( v, G7 u4 e: q( \2 C8
    $ T4 I7 R: h, l2 o9
    4 b  v& G) f4 A是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。9 ?" l4 O1 o  D3 k5 W
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
    . V2 n. U3 X& q4 R- O% x. w' ns=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    - \; F$ L& y5 L! f" C" {s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
    4 @5 n& _6 B0 p8 w" w
      X; O2 M# x2 w+ P+ J(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
    9 y$ r. ^. w& H! o* ~1& a1 f. r2 y' D  K
    2
    " s* ~5 w! E8 q6 N/ B0 C3( i* }) s1 _  m* j, X( U) l7 ^
    4
    ' h+ D  S$ o/ f3 i2 m4 q5
    * Q' H/ X) N) V0 ?9 h%pylab inline' V2 x2 g8 a3 c9 V6 O1 Z$ l
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    . F* H( T2 c# }plt.xlabel(' Timedelta')9 e* u# \6 E, o4 V- [  ]' ~9 a3 N
    plt.title(" Timedelta of solar")
    , S. l- f7 [+ @$ G6 W) G1
    % R1 g; F2 k) N9 X/ A- U2. q7 P" E7 u+ h
    3
    8 w: b* F7 A- p! o0 e4+ A& `7 q8 i' n+ Z, x
    ' m+ j) g1 r0 C! f$ O! g

    2 |8 B: S2 R& w$ u1 m求如下指标对应的Series:; J, K8 ?4 _  `* b7 B" z; a4 i8 _* U
    温度与辐射量的6小时滑动相关系数3 R- s2 e1 w9 j0 Z; F7 [! ]5 Q" x
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列" e: N2 T! U" ]( e
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)4 c  C9 s: j" \5 S) r
    df.Radiation.rolling('6H').corr(df.Temperature).tail()
    1 W: M  V+ @" a1 \0 _% {
    + m( n2 k8 @/ J+ |# a; r( KData
    7 j5 b8 ]0 A* y+ R' k& T5 d2016-12-31 23:35:02    0.416187& C! T3 o6 I& H2 T+ n7 G& g* k
    2016-12-31 23:40:01    0.416565
    ' t2 P3 D( a8 M( d2016-12-31 23:45:04    0.3285740 G1 R6 N" I0 i# o: F7 c- D
    2016-12-31 23:50:03    0.261883" y7 c3 l' p) n  K, s/ Q
    2016-12-31 23:55:01    0.262406
      o6 r9 w. ~, I5 G/ H( e' k' [dtype: float64
    ( |9 C& J& k; ~- r- S1& a8 Y! M. Z% c$ }$ \
    2" D' m9 p! d1 L- @& W
    3
    " z: q5 O& {# ~, R0 t. V4' E/ t9 V' x8 e) t7 V3 \8 [
    5
    8 {; k3 o. d, P8 ^& ~* y, v6) A' q- l* w$ ^* r& g  M# ^7 \/ f
    75 U+ |, d, Q/ `/ U+ g5 `7 k5 B
    8  F: d+ g, a3 g3 P% K/ D
    9# R3 k& D& L# Y5 X: b5 W& D
    df['Temperature'].resample('6H',offset='3H').mean().head()
    8 U+ C( O4 ~7 F$ i4 {5 ^# v, u/ j# ]2 K5 `' i4 G/ g
    Data
    ) Y2 `4 v5 R2 w' B" |) T% q0 U" q2016-08-31 21:00:00    51.2187504 `3 D% `6 O8 Z& L, c, [7 I
    2016-09-01 03:00:00    50.0333336 J9 m! J0 D) a; k4 i
    2016-09-01 09:00:00    59.379310
    & b3 g" D1 }4 |! H: C' x2016-09-01 15:00:00    57.984375
    . i, V1 F4 Y; a7 R2016-09-01 21:00:00    51.393939# y. C- x, }- o* J5 n; {
    Freq: 6H, Name: Temperature, dtype: float641 `; d+ _6 g4 G1 V
    1
    3 q4 a' s/ R3 h. ]! {4 q% b2
      P0 a+ `# j1 g' U* k36 [  A. _, T8 d) ]8 m
    4
    - R# _+ I7 C! ?' v( o% T  f! M5: V$ ~: W9 z  `6 I. A
    6$ r/ @' z% O$ Z" n, D9 h: k2 n
    7
    6 h, p: X0 @+ ]0 x0 ~2 u) a8
    " g' g6 H/ S6 _: V' K0 m% R* ]9; N" l7 i% K2 O2 q
    最后一题参考答案:
    / A* ?( Q5 X: z) J
    % s: Z7 {6 Z6 ]0 |# 非常慢
    . [  [: u7 Y5 o  G9 n) cmy_dt = df.index.shift(freq='-6H')0 D/ {0 V1 ]; \7 j' J  q; e- [
    int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]& k, F5 m6 N- m. h- ~
    int_loc = np.array(int_loc).reshape(-1)
    - ^3 X1 X2 o7 T. G" g3 lres = df.Radiation.iloc[int_loc]: b' o4 u% B: r- j
    res.index = df.index
    1 j1 ^9 B, ~4 ]0 m  n5 W4 R2 n, Yres.tail(3)
    * y# e, C0 U$ J! Q- i1
    3 k9 D1 }8 r" c& f6 m2. f+ m8 V  Y* a- s* r; k4 i
    3+ U6 u$ M' U9 M; n4 W
    45 H) [& i* v; ]; K. D/ x
    5' M$ C  s$ H9 _! f
    6$ J! S( }- K$ g# @7 J! ^0 S* J
    7
    0 T( ~& f7 i6 G# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    % W9 ^; [8 I  h8 Ctarget = pd.DataFrame(6 J0 y: J' q- z8 m
        {3 j+ I5 }  E' V8 ?5 t
            "Time": df.index.shift(freq='-6H'),
    % b% P( G3 ^' x  t5 w& g        "Datetime": df.index,* H0 }" ?* d9 r
        }
    ; e# q. B" X/ N1 }, Z( v$ h): ]& i5 h. ]# V+ y

    . A) c+ V1 L/ c$ r$ ], C, Gres = pd.merge_asof(
      w- `) t9 }! o; B/ ^; ^& S    target,: a( z; \9 e7 H8 X! C% f+ l8 N% u
        df.reset_index().rename(columns={"Datetime": "Time"}),
    . z7 h% m4 [1 K- ~" h. o; }    left_on="Time",
    " P0 A5 }4 _" J    right_on="Time",6 Z! `2 f, q# w5 b8 p8 p  K
        direction="nearest"! m' m0 f% y; ~& d: J; \
    ).set_index("Datetime").Radiation+ [; F" e6 r0 F0 o$ y
    * z# O0 C$ P* G* c: s8 g6 v4 {
    res.tail(3)& D7 U- y8 z1 @& P9 U8 _
    Out[224]:
    / |, d! e6 ~3 EDatetime
    4 t1 m" [& h9 z* q2 w0 m1 w2016-12-31 23:45:04    9.33
    , V# I+ J& J+ x, M" y- U2016-12-31 23:50:03    8.49
    - a4 T- u2 E' o' q% a+ I2016-12-31 23:55:01    5.84
    : A* r# H/ M, t' ~' x% w% C1 RName: Radiation, dtype: float64
    / }6 Z2 Z- T# @% O" u2 T* T( J* X' c' N7 J' K
    16 y" _9 Y6 W- F3 j. s7 Z
    26 q5 _6 H. l$ S
    3
    - F- S6 o- l4 W% {44 R% H3 P: ^& ^6 m3 U
    55 y3 a- R; w9 N5 M! |  ^
    68 b: F3 D( N& f( \5 n9 {/ @; V3 r6 t
    7
    ) w! T/ a1 i6 [  y; U7 E) r8
    0 r: O4 I: L! n- f+ f9
    ! D6 b  b# |8 \, M( R/ r. L7 ~; @/ F# T# |10
    3 I1 T7 Y" ?- H' G, F( e% X119 V5 L/ A# x8 ~( ?
    12
    ' x  Z) E8 {! V13
    4 G  r3 m; {9 E14
    6 }1 N( e9 m) _' v0 U+ N15
      _, F& m6 v9 F: o; t3 {160 T' e: h' j2 t  A
    17
    & J8 S* O  B. n- S$ c1 h18
    * n) y% @& y1 ~4 b$ I! G19
    ; ]' S# L7 @& U. o* T2 u8 e3 G& C, ]20* w' y- P5 S) c1 N- l. ^
    215 e( t" _. Q8 q  G
    22) P. U& E* p/ o7 {
    23
    9 C& j- l  ^7 u# [6 m5 cEx2:水果销量数据集" @7 q* l5 @  I! y2 ^/ d6 |& H; m
    现有一份2019年每日水果销量记录表:
    * ?; f4 F( G4 G& {0 A; P! s- E+ u4 `. r
    df = pd.read_csv('../data/fruit.csv')
    % ]) a7 D5 Q! K0 F! i; P8 |$ Ldf.head(3)
    1 o6 Z% |; e! i% x
    / h5 ]5 \, x9 NOut[131]:
    6 Q9 ]! ~  @3 t, r/ U' `4 a         Date  Fruit  Sale
    7 G" Q& e+ |7 H* H: D0  2019-04-18  Peach    15
    : E9 ~; O) ~4 K& g8 {5 f- P1  2019-12-29  Peach    15( ~# q( E( e  ?+ g
    2  2019-06-05  Peach    19
    & W# \+ L* w( _3 f* j) O1 b1
    * k7 j: D% {/ J+ \+ D; o2  f; o; W( A" d8 G
    36 F  D3 I* x2 |1 `
    4
    & m1 W) M# v. U5) Y$ f# t+ I8 F
    6
    3 j4 A9 \1 h) W) K8 p! t5 q1 Q% P7
    9 z2 |: t8 j; J. X5 y8+ `/ w1 Y4 n9 K9 d1 j% }
    统计如下指标:
    4 E4 X, {, h1 L9 [* w5 M2 Q每月上半月(15号及之前)与下半月葡萄销量的比值
    # `3 }/ G  b& c- Z. {每月最后一天的生梨销量总和1 |0 e8 z! y3 Q8 \
    每月最后一天工作日的生梨销量总和& R8 }- x8 J/ N) q6 ^
    每月最后五天的苹果销量均值
    * d! l- E/ x' R! \$ g* K8 L# z按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    1 ^. X. [5 \) l9 @" S. ^* h, N按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    5 ^; F! y- r/ |5 a' j7 limport numpy as np. n9 }0 a! Z+ n
    import pandas as pd
    6 T8 N6 t) S( k9 U0 o1
    % y) \. z* e' [; G3 w2
    ! q: u$ W# e: X3 L统计如下指标:
    0 F7 I1 a! l6 d. o, N每月上半月(15号及之前)与下半月葡萄销量的比值1 C. J; p( o( d3 R! x
    每月最后一天的生梨销量总和
    : h9 [/ u! p' R$ J. L6 h& ?每月最后一天工作日的生梨销量总和
      x3 _2 z3 e$ v3 Z每月最后五天的苹果销量均值& Q/ ]- B! s# Z# l9 p. n
    # 每月上半月(15号及之前)与下半月葡萄销量的比值$ B6 Z. I2 X; S, ^4 C9 n
    df.Date=pd.to_datetime(df.Date)
    ) }' k0 _, A! q, }2 u2 N* c8 _sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()! u: T, A! w- l% ?
    sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    - A  m- S" R/ B: Q- F9 Vsale=pd.DataFrame(sale)0 ]2 z+ X( D4 D6 x6 |* G9 b6 q  k$ J
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
    / S0 W$ \, u# R9 |' i                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
    ; [4 Q- w( j1 {0 B9 asale.head() # 每个月上下半月的销量
    ) d" R! w1 s: Q- ~5 B) c( p$ o; o- O( X  N  a
      Month        15Days        Sale+ A$ e) `9 F  S
    0        1        False        10503: g! e. y! @: f# P1 s4 h" i
    1        1        True        12341
    1 C( e- y" l, G3 R- I4 e2        2        False        10001* J0 G6 D# i% x4 M
    3        2        True        101067 R7 z; P+ e* R- V4 a
    4        3        False        12814! W/ L8 y+ m7 Q
    $ }2 S# o' f. I& X( n! V: J5 \
    # 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
    3 `; `& N5 R6 A- i2 n* Esale.groupby(sale['Month'])['Sale'].agg(' P, W$ H7 N6 ]) D( j" o! y8 {
                    lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    5 G  h+ F- v, l) G! h3 L7 e& S8 Z( z7 ~9 G9 B1 J- _/ ]; N9 C
    Month. c$ w* F# U* ], B! _% Z* _
    1     1.174998. w6 O) Y7 X+ T) T$ u5 F- \, o9 T% Z$ T
    2     1.010499
    ! [6 |) \% [( B/ k- ]4 b7 ^3     0.776338
    ( F; V: S0 F3 i4 {3 I5 H( C4     1.026345
    + w% ~4 J. X$ G" O+ W4 i' c# H5     0.900534
    + X5 W9 g2 K6 `3 T% }; a6     0.980136
      w% X8 f" J+ y1 p6 D7     1.350960
    9 e. W. y7 q9 X) l7 U+ R! M8     1.091584
    4 {* f8 t3 D8 G2 L& d  W9     1.116508
    5 S& X$ }9 b+ Z10    1.0207841 n, {, ]4 v: e2 @8 z
    11    1.2759112 E/ Q$ H) k' J7 [# {0 l3 f
    12    0.989662
    , V( h! c6 R  b7 WName: Sale, dtype: float646 w9 ?. N: {& Y

    ) |* H; L4 l3 N5 S1
    1 N9 @! m, c4 R" l& n2( Y$ ~. V' _+ |, }
    3
    ! v, p0 R! R4 N4) W: J7 l( t1 ~: j; u" R+ [
    5
    , q7 G6 k# ], T, l: [6 i( a% A6
    ( {! P% V8 B8 j7" ?! l; g. J3 r/ X
    8
    - M+ a4 v9 s, z' ?  d9
    ' J& R1 Z$ I# C10
    ! n$ O# ?# J; Y- q! B1 q116 m. S  x: h7 D" y6 e1 {
    12- v6 m& ~& x3 O# C6 J$ I; L
    13
    + k- X8 }8 L' N9 W" e14
    6 L& |% y6 _5 m1 F+ N15
    * m6 @* Z0 N) G* _  }8 J166 F" [4 M$ }! j- z! i' [
    176 x: N8 P4 q' |. `& ^, s
    18
    9 D; a9 w. d1 E% q  w19
    3 Q- ?9 q. u! O6 y& s3 I1 ]204 M5 I* _8 U4 t+ I! e1 X1 i
    21
    , \; g( |8 l1 x+ p/ H22
    4 S9 j/ F4 S: q$ o4 y. z% M# P* ^: G23, g/ X0 f% |) _7 y' n% ]$ C! ]
    24# c$ G1 p9 P' y( p
    25% e! q& a* k7 r
    26
    , F9 R/ L/ Q' C. L6 x27" ~  b. \  f# K$ a0 F! j* J
    28
    3 P/ \' M4 x# V. R, I( L296 e9 n$ W8 _" O7 h5 W
    30
    4 c% |* \* d3 }2 k  Q5 K8 t31+ J! f" u" p& [/ H. u1 g7 Z
    32
    + j3 M5 u' B+ f9 k' n33
    - Q9 @. ?3 A  {7 }34$ c7 R( I0 k8 U2 B
    # 每月最后一天的生梨销量总和7 B0 O# W! D% S! z9 N
    df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    % t2 p) A7 v# t# c! c* C# e9 c) h
    , h3 Z+ ?  b0 n) |2 Z+ Z( xDate
    $ ^& }! I6 r! o2019-01-31    847
    / \# z' f- E" n1 i2019-02-28    774
    ( M2 v# L; b! c( Z0 r2019-03-31    761& l0 r/ J$ `; N& G
    2019-04-30    648
    # `% L5 T) c2 m: P2019-05-31    616
    ( A% E) F5 P  s4 G# O% x11 o" }4 j8 _; O8 a) @$ E& x
    27 g! K1 K6 t4 m* Y7 i8 T7 a6 Z5 y
    3/ Z% F" O1 A# L" L9 w/ `. J
    4
    : C+ r: H& d/ K7 L- }5
    , @$ Z3 q& Y% k0 f! F! I3 ~6) o( {6 K' |. T8 [9 r
    7
    - ~- F( ?$ Z  s0 R0 J. M( \8
    9 T* _7 E5 }0 n; x4 A1 s9
    & h- I; E" [3 ?$ h. U: o0 l5 Y# 每月最后一天工作日的生梨销量总和
    % t8 U0 j/ B4 F8 T$ Y$ vls=df.Date+pd.offsets.BMonthEnd()+ A  h1 p+ m, e/ }
    my_filter=pd.to_datetime(ls.unique())  k7 L' `; w0 A, J
    df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    1 [4 z  p' ]# H0 J! c  S$ r. H4 d: z4 [8 B* p- g% V* q  f5 \) q
    Date5 n" t2 G8 g2 q3 D8 e- D
    2019-01-31     847  n. k; b$ p8 {3 O# C" Z! z0 {
    2019-02-28     774* v5 }) W2 Y8 J' s9 @- W
    2019-03-29     510# J" Z# N  q9 i3 i6 }4 s
    2019-04-30     648
    2 ]6 ]3 Z4 M$ ^2 v3 P( O2019-05-31     616
    7 @' B9 x4 h$ O1
    + R4 ^+ ?; W. S0 j- L: z5 I$ j6 l26 ~5 q0 K+ R; ~3 t
    37 ]; v7 i" L/ t! [& g
    44 m; `# k* P2 ^) Q5 h" `
    5# m6 x2 B9 |8 l, s% ]+ A# T1 Q) W
    67 h# F" i* m0 W; g; {: [) V
    70 @* A4 w  t" i% H( g
    82 Z5 d7 M. ^' ^4 s$ S- Q8 G
    9
    " k9 z$ e8 g& p10
    8 V0 N' `3 w9 v1 \) o& r7 M4 x: E; Q11
    1 W  U7 ~! M, V0 ~# 每月最后五天的苹果销量均值
    * h4 H2 D" A/ o: C3 R2 W& R' n6 Fstart, end = '2019-01-01', '2019-12-31'  A" I, Q: b) ^& J1 [2 K! b+ E
    end = pd.date_range(start, end, freq='M')" @9 K% ]* V2 |4 W
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
    3 B1 P7 b, l! q/ c) z5 V2 I; S" M  G2 U' [' k
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    + S, A" U4 ~4 b2 k3 x+ Wtd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
    5 U2 U$ I  ]) m3 Wend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    # X# V$ }6 D( B' q! r$ @
    7 K  e! |6 D! A0 G& s! v! tapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    " K6 C% b/ k8 w& Oapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
    7 a' p7 A7 Z! O5 ]7 {0 s+ j
    0 F3 n- W/ l' mDate# Q1 f) l) i3 Q* {
    1     65.313725
    4 b( Q  U( z0 }* W3 ~) Z2     54.061538* g: N5 j; X% D! k+ G; b& Q+ j
    3     59.325581
    4 V% K+ c7 V  p8 U  J4     65.795455& p) Y- t2 S6 e
    5     57.465116
    # D9 S: f' s% J( L% |! e/ Z& {* b# }0 s8 \( j. O, J# ?
    1
    , Z: L/ S0 ]! X8 s2
    ; I' v& j! G% i5 Z  S38 @: T" ~/ T) c: u# H) U
    4
    7 c; F" q/ ?: \2 P5 c3 `5
    ! n3 P$ y# q6 Q# a6; ~- |% v1 L% u* }" X+ W3 Y
    7
    ) C& n3 o, J- D. f8- q# s% F) ]$ ~' f/ R0 |  }
    9
    ( i9 c, F7 _7 b" J" y10/ _/ N+ e6 P2 e, w6 }* l
    11
    " U# i0 O! x1 d8 K  r12
    / M, @, U  {& Q1 S  G4 c' _13) C  E  W: Q5 U) X; K- t/ m
    14
      o3 Y1 D3 F: @15) R! w8 d, w0 }
    165 l8 Z% l. V. |" I! M
    17
    ; N6 X# k9 m! g18
    " G+ x$ z' v: O4 n# 参考答案:7 a4 W5 V8 S7 d2 V
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
    " ~2 G9 X* Z1 Y+ }" T            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
    " I# H+ E2 B' k7 q# \0 S; T2 S5 U" ]' e/ b% g, W! \
    res = df.set_index('Date').loc[target_dt].reset_index(
    7 i7 [+ x% D6 |) L+ K$ Q8 p            ).query("Fruit == 'Apple'")
    $ `' G# U3 k/ K0 Q* h1 L) M3 O4 B3 C3 f$ ~! C* S
    res = res.groupby(res.Date.dt.month)['Sale'].mean(
    0 `  r: P4 v  F6 q& o) e' h% ?            ).rename_axis('Month')$ _* x3 }& n) e/ ~# E6 U
    : ~2 k6 f& f) V5 p3 E, P: ^4 D6 p

    ) h1 F# p* M5 s& u$ ires.head()
    & b; Y  E; D! L5 P  S" E9 kOut[236]: 2 m+ E" v  y2 r' d- \* O0 O9 Z
    Month
    ( H+ b' Y2 e/ `, i5 |! R1    65.313725
    2 U" R2 [$ X- K3 S3 E2    54.061538: ?( ~$ Y! @, q1 O
    3    59.325581
    - l2 t, _( U2 @/ {% S4    65.795455
    4 ]- y+ g5 p$ m- z/ @5    57.465116( p4 J. u% F4 i, T  l8 i
    Name: Sale, dtype: float64# p' W# c# i4 s0 d: K

    * k, y3 E# [/ ^1
    $ _. Y5 \5 |3 q9 v- h24 Z+ j4 v2 @/ R
    33 |% s: N4 R% B! W
    4" `9 o# S, d% Y
    5
    2 B0 f+ m  G0 E: M: }. p- o. w6' _7 G) B. t3 p% x1 |
    7
    & J, z$ ]6 a" X, N83 R6 J# i4 b  e" z
    9
    7 p; ^* C* q9 y; o  s10
    , I1 P2 b7 R8 i11
    , t# W% D, Z  h+ N* q- z' ~5 r12" ~2 r2 W, P2 R  ^' N( P  y# Y
    13
    # S; V  Q$ p2 }  u/ ?9 U% J14( s1 G# u$ y2 b5 w/ e7 Y
    15
    2 D/ Z& l2 [% n7 z16
    . [0 F; ?: N% c3 l$ c* o9 P17
    # l- \4 R3 _# t: K( r" y3 D18
    5 y' Q5 N, V& U, \2 l: K190 s/ v8 T( i' h, i
    20( M, s* @, F" W+ W  G
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    2 s' F8 h$ X% T( S  Y- Tresult=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
    % d' o. q# G  X& d& [) Z6 }9 z                                        dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 * ?, ~9 Z( K6 h/ Z
                                           
    " n) C9 V1 C0 X/ Hresult=result.unstack(1).rename_axis(index={'Date':'Month'},( d0 Q5 R4 ], N
                     columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.& R+ R! c0 l! G  f2 Q
    result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)' y/ ^: r$ r- c. f' H4 q8 I( @
    result.head() # 索引名有空再改吧- k6 ^( j$ J( N

    ! H! ^& x. B/ G) ]- y8 H* b* X$ w          Week        0        1        2        3        4        5        60 V" z- D! k8 P) F1 ?" e) j
    Fruit Month                                                        4 ^3 |7 c+ O$ Q; k' X* p* w# G! q; y
    Apple        1        46        50        50        45        32        42        23# l: k4 y% h4 h6 A+ N- w4 g/ N7 p
    Banana        1        27        29        24        42        36        24        35% e0 i$ Q1 K. X- E% t6 d& L, d
    Grape        1        42        75        53        63        36        57        46
    . x* t9 D7 b# |- _Peach        1        67        78        73        88        59        49        723 i) v* O8 s( y6 ^) Y9 N8 g
    Pear        1        39        69        51        54        48        36        40
    9 I8 v' d- ]# K- Q8 u! S1
    $ t! E& \5 O' ?$ B% E2
      Z* D, `. E2 |+ }  R3
    3 w4 J$ k, s6 j  J4
    # P0 {3 q. C; {8 i. L5
    / ]+ h0 e* H' E, |& F1 |( Z62 s  @: L$ q; R0 N0 T- m( R1 F# H' @
    7
    6 r7 D0 H+ e# v( N1 `$ Y6 a8
    . J3 H) @& B5 J/ q99 ]* Z, o5 K5 u3 |0 H
    10
    $ D  ?/ L. l2 X% ^11
    " l2 i! Z  E) s9 p7 W0 Y- C12
    4 ?2 y6 N4 r6 ?- }& `2 r' Y0 b  a" l( ]13# N4 c: ~0 j* M/ \1 u1 n4 C
    14. Z  ~1 l0 f" c/ {7 y
    158 ]8 `0 f/ o8 u/ z0 B7 S( j
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。5 I" ?: z# {, u
    # 工作日苹果销量按日期排序) m- F, D5 y! C  H6 w' d
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index(): b8 k( l% T8 h. ~( [. U0 K% |
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总2 y3 C1 p$ z" H' |* F" r( ~" [9 i
    select_bday.head()
    4 \$ o' o3 G( [) |& K8 w4 X! r5 ?8 Z) {! [/ e2 k& d7 s
    Date
    ' a% d' b* d1 F* Y) O2 _2019-01-01    189
    : ]) y. I6 |2 l$ }; }2 ~5 p$ `" X2019-01-02    482
    2 V5 \0 C: ]' g# b  K2019-01-03    890
    ) u8 i4 x) c# N0 d4 `. _# V2019-01-04    550
    7 \. ~( P4 n' Q. B/ a2019-01-07    494: J* \- C" e! Y4 T

    * G: C6 w$ x- T# `2 n# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。* _( D: D: t# W
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()# J' S! x) C, [0 i5 X
    ) [) N: e1 e: ^& Q3 B- i
    Date
    : B5 T% Z9 A7 u; _7 H# ]2019-01-01    189.000000' ^' Y5 o8 R9 f
    2019-01-02    335.500000
    5 h+ j1 q1 A' l8 D5 L2019-01-03    520.333333, f6 `+ d( x: m) l. G& N6 ]
    2019-01-04    527.750000' X2 w  }  q% {; S$ O, q9 x, H/ z
    2019-01-05    527.750000* U5 w6 M/ V  l+ n% j
    9 b. o3 b) Q/ i+ f" \7 j
    ————————————————
    ( S/ q( n" v8 X. k版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ' k. ?9 p! b, V. h% w* S原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    9 q( Y, |' X7 \+ K  O9 O' u1 h+ X5 b1 [
    4 z8 O: Z* N4 ^6 a. v
    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-6-14 16:57 , Processed in 0.585337 second(s), 51 queries .

    回顶部