QQ登录

只需要一步,快速开始

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

    / f1 A. o  G, G/ |0 O4 @; b( I: }5 |( ]( M7 [8 \0 h: P. g  d
    1 Q' z3 h$ {! X. K' i; [4 n
    文章目录; [2 W+ k6 i  V# Q( v, V/ b8 B
    第八章 文本数据  M1 B5 z  ?6 z
    8.1 str对象; U" E& M9 e  E1 N( f! g/ A2 Q* U: T3 D
    8.1.1 str对象的设计意图* a  r0 W# J$ x8 d6 I! `
    8.1.3 string类型0 a3 X% u( p. l
    8.2 正则表达式基础2 U5 Q/ j8 K  h# P7 l' T+ c0 x
    8.2.1 . 一般字符的匹配
    6 Q! Q. e+ W  \( y" Z3 c, r8.2.2 元字符基础! }; V0 Q! z( A' N
    8.2.3 简写字符集, i/ Z& P: W1 H- b; O1 i
    8.3 文本处理的五类操作
    " ~! C& s. r0 D& i! ~" r- e1 ]7 ?8.3.1 `str.split `拆分
    , l# `, {, ]2 f; K8.3.2 `str.join` 或 `str.cat `合并( n3 A5 Q3 c3 ^* w1 U: e# x
    8.3.3 匹配
    0 L: ?7 s2 C% V$ Y8.3.5 提取0 F9 z" ^# A+ Q! K
    8.4、常用字符串函数; i" z- Q, h0 g8 A- u; c
    8.4.1 字母型函数
    ' E) x5 G) A; {8.4.2 数值型函数
    * i  u+ ~. J: q1 s) V8.4.3 统计型函数( c) `" Q! }- R$ c
    8.4.4 格式型函数
    0 m( v) `0 c" ^/ m- m8.5 练习( {9 Z/ q. u$ I' b
    Ex1:房屋信息数据集
    ) u  |* ~2 v  k) s- O* F4 YEx2:《权力的游戏》剧本数据集3 S6 H. _, ^# C" ~4 X% u
    第九章 分类数据
    # W+ t9 r! y/ `9.1 cat对象
    " y% B4 N* |5 e, n1 L  ]9.1.1 cat对象的属性
    , U: ]+ [3 k+ S9.1.2 类别的增加、删除和修改, U& {8 V- M3 W
    9.2 有序分类
    % h$ r  r* q" W7 J7 V9.2.1 序的建立
    2 z: V( ?2 ]& L5 m* x( D/ A0 {9.2.2 排序和比较( B3 j$ V) J! ~: P: y; P
    9.3 区间类别
    0 r  A8 [  Z, \+ S9.3.1 利用cut和qcut进行区间构造
    9 L3 r, a4 ?- Q9.3.2 一般区间的构造) J5 Z; ]0 Q+ l' x0 e/ w
    9.3.3 区间的属性与方法
    7 ?* A8 T7 K2 y9.4 练习* d6 l" t. {' \8 C& F) p8 j
    Ex1: 统计未出现的类别
    ( I2 D, _: M" oEx2: 钻石数据集
    " N: I  a6 Q1 V  ?- M) {第十章 时序数据# U4 R. |) P; Z8 [3 d
    10.1 时序中的基本对象+ ^& _2 Q. a# ^9 Z
    10.2 时间戳7 B7 P/ t& z  t5 m! x7 G+ Z- K
    10.2.1 Timestamp的构造与属性
    % X9 b- W* O* u: S9 \10.2.2 Datetime序列的生成, w( H% x- @6 C
    10.2.3 dt对象2 R4 \! h$ y2 i9 i4 `5 A
    10.2.4 时间戳的切片与索引
    6 c1 H/ w2 X# E3 y5 X# N% T3 _10.3 时间差* a/ l: K" S* ~" S
    10.3.1 Timedelta的生成
    & K* U$ ^7 s+ ?8 |10.2.2 Timedelta的运算
    , Y$ l+ K9 W( V8 Z. D10.4 日期偏置
    " S* E( H; O8 T10.4.1 Offset对象! u3 m7 a' u0 V1 I
    10.4.2 偏置字符串* I* Y; B: Z* S! O1 _; M1 @+ o  y# e
    10.5、时序中的滑窗与分组5 \& v3 K+ T+ S
    10.5.1 滑动窗口% D/ h/ [' Q# z' G5 b* }; e
    10.5.2 重采样
    6 ^  M, O0 L$ b' ]& U4 Q) R10.6 练习
    " ^: s, [# H% _* m- [Ex1:太阳辐射数据集
    2 _1 W! w# r) n, h; x6 b5 UEx2:水果销量数据集( u# ]  g# ^) U
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网9 x5 M+ {% A6 n& g+ R- |8 @
    传送门:
    4 j/ q* f( t3 p. J) b' q
    5 t% Q/ K: S; J! m3 a9 I, r& F# A. edatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
    - u7 H% W/ n/ E2 ^# wdatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)1 ^8 h8 |; h0 l4 w( y2 a& V6 \, i
    第八章 文本数据
    ; j6 }' o: W: {8 c8.1 str对象& @2 E* z$ H( J# s* G7 k/ h- O
    8.1.1 str对象的设计意图& J$ W7 Z3 d& \* F! E: p
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:6 [8 `; E" u+ G2 X2 T* S
    & `" b+ v1 G  e% Z$ O
    var = 'abcd'
    / _% k/ _7 l' lstr.upper(var) # Python内置str模块
    + R! g* L% x/ S! dOut[4]: 'ABCD'
    0 l3 P8 v0 A) i* j6 D8 w5 {# U& R) f
    s = pd.Series(['abcd', 'efg', 'hi'])
    1 C+ m& g2 |7 B6 O6 T: G
    ! W6 s6 {; `+ G- C8 Fs.str
    : i' e8 q! P/ i; ^8 NOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    ( u7 {# [3 i# Y
    & r' P% c) V8 Y# d7 As.str.upper() # pandas中str对象上的upper方法
    9 l1 T% p5 v/ R5 z; l9 ~% [/ u+ c! ]Out[7]:
      ^) b2 c. |3 j% }+ R0    ABCD& a7 r0 W/ g; w: {+ o% h! i
    1     EFG" h2 f) o8 Z; V
    2      HI; J" i2 ]6 k0 r" ?3 n/ G
    dtype: object
    + Q2 o- B2 ?! x/ A1 |) B1
    : n. y1 L( t, G% V! O2" p( s4 c" Y: h. ^( x
    3: K* h! S7 e4 M; V* O! B
    4- _6 c: O6 n; @# b' R. x9 r
    5* N6 y) w6 @+ U' m/ g$ Q, |
    6
    2 w" w+ c$ J( @4 R" A, d7
    . M) _& y( Y4 [0 B5 u89 H. j) y4 d: R4 m
    9" F  O1 [9 K# F0 l1 N8 }* q
    107 B4 f; M2 {( E: Z7 ^: i
    11
    6 q: X5 G. L( R6 l2 e. @0 O126 y( Q5 J8 J; R
    13
    3 {( N3 c% F& J$ J1 E: P0 v9 @14. N$ |4 _7 ^) D, h, q/ Y5 s
    15
    8 L; X1 V8 U3 G! ^* D5 X: {8.1.2 []索引器
    # H. F+ P* P8 F4 g! v  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
    ' U" `& g1 W, [1 Y  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:# i+ M$ G- z, {; [$ ~, d; w' ]9 ~

    . W4 ^  i+ q+ G" v1 rs.str[0]
    8 Y7 i% k2 @7 g) _Out[10]:
    ! R6 w+ q, S6 F- u( W$ D) C0    a
    # ?) `& Z: W. l6 f& e) K1    e) I. I( t# V( J- D( |3 v
    2    h
    1 j6 L0 R' D7 @$ Ldtype: object+ J& w' X: T5 j/ j! h: z$ ]" H5 O) m
    " b" d  }3 z% k, D% b7 A
    s.str[-1: 0: -2]8 E7 L3 G4 y9 Z7 z( \* O( c( `1 _
    Out[11]:
      [1 _& {0 J! j: s) R$ L0    db
    4 O+ v5 }/ S' w; C* h( v1 e0 I  D1     g8 _1 G! t/ h% ^: H
    2     i
    " s5 ?+ @: E, _dtype: object+ s% H' m& `# J% R, J- P$ A2 h. o9 C& ~

    7 g- _& k# }% m3 ]3 v# _! }s.str[2]
    4 S$ t4 E; o& e# OOut[12]:
    0 {  _/ j. M- b) S& v$ J* u0      c  j* `, v% d  p/ ]6 s7 e- ^
    1      g7 F, }! P/ a4 w0 X' R
    2    NaN
    + _/ c! ~6 I+ vdtype: object
    " ^+ X" m, `4 n2 `" O) M' f3 ]: s: x- s6 @; C" O: q  l2 D
    1/ Z7 a& k6 Y) n: j* ]+ h
    2; ~  N# r# A: e$ q
    3. e( u* l3 ]+ ^0 X# @, o' X: x- A
    4
    9 ?) @1 O5 `2 U* h. R, Y5% x2 b; `" B8 E, ?
    6
    : Y2 ]1 ?" L4 }1 o2 s( Y7" @$ G# X' }7 l
    8
    6 [- `+ s  ^0 F7 b95 Q- _2 {: i, q6 a2 \& i
    10- S+ \, s9 n1 d
    11
    1 Y  r& S2 I) ^6 p  _12. ~4 I2 k2 [+ i# o
    13: b. g! v+ k8 M5 X  ~  j
    14$ ^7 s* K6 S' K: V- e1 v: n
    15
    5 p6 V' H% I$ L16
    ) z; \2 G) b0 s) F4 b17
    9 Y$ R1 N) |4 o( {& `18
      |$ K2 ?& ^  G, W192 k/ K8 ]- j% T6 `$ p. k# Y
    20
    1 R+ s% T; W. Aimport numpy as np/ U# u2 G  E* V
    import pandas as pd) l( }' T% b0 _3 ]
    % X8 V: `+ A2 e# x$ t
    s = pd.Series(['abcd', 'efg', 'hi'])5 V4 a- X+ S/ G/ P- q7 t& o. F
    s.str[0]# L4 @5 K8 t6 G
    11 D9 n2 k+ T) q, E5 h
    20 d3 a6 N9 Z' L
    31 G! b% r9 G% T* q0 L
    4
    0 z) S$ o' _* {5: X  F" c9 K. {. Q: Y0 x! H# H
    0    a
      y/ {2 f8 Z# W9 t7 ]2 }, c4 T1    e
    ; c: s8 [* ?0 d2    h
    8 p8 `. k. A- L) `# ?9 b' J" V0 [0 {dtype: object1 l+ o  w' F: r) Z  ^0 E. w8 A
    1  Z& U4 V" G9 d- U% q  U
    2/ r5 G' [( T' }
    3
    & G6 j6 t! S2 X  A9 q7 l4, \/ o, ]4 L3 s' D% @) p
    8.1.3 string类型3 K- T! A$ M  t3 b' ]' U2 H  m
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。
    % b% }  t" r* ~8 r! q  总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
    $ V) z7 s1 n* O6 l4 R
    0 o, {" ~6 S& f% F5 r二者对于某些对象的 str 序列化方法不同。/ X+ n! |, [) A4 a6 Y9 t3 W
    可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
    # e3 c& E' O' B5 h, p; P) \s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])* Y% J2 R/ \5 D
    s+ D1 I2 {5 J4 }# C* k9 k
    1
      F$ O3 I2 J  P6 t' N  H5 [9 g2$ |5 j( R3 }* s
    0    {1: 'temp_1', 2: 'temp_2'}
    " b% e' W+ ~5 f. `  {  t5 Q( ]" I$ q1                        [a, b]8 }2 n; c1 Z0 p
    2                           0.5
    2 G) O" j7 x) {. j4 C0 C3                     my_string$ [. }- m  ]/ b* r3 ]" l2 x
    dtype: object: P/ f- p( }2 Q/ }1 C
    1: D, J8 C. h# c
    22 T; b, M; ^" z1 u4 k
    3& G1 @! n! c0 f$ K
    4- l; Y# o' u1 ~" c+ Q( a7 h0 y
    5
    ! h+ W( Y/ X% g) \2 q1 Gs.str[1] # 对每个元素取[1]的操作
    % z9 q& ^( I0 ]8 N8 T* j% ]& H1
    ) h$ M1 {3 c( _  F/ l4 p, s0    temp_1
    1 @2 y# Y) c6 @7 K" }1         b3 r3 h' S* C9 N# ]8 U! n) V
    2       NaN
    # h% ~2 h# r% F5 e3         y
    5 Q: s! M( B5 X) t4 ]dtype: object% T6 P5 Y! q& L; Y- R3 \
    1
    & i% r9 K  `) e. ^7 U7 @' Y4 O2* D, u: \" R5 j) v% q
    3
    3 G2 v+ X8 h# X; m5 T) T8 H2 \45 S2 a  k& ?$ V+ i, O8 @& W' S
    5  m, J+ _0 o; M1 ]0 i9 F2 J
    s.astype('string').str[1]7 z* |- `, [, A6 N# H: d+ J
    1
    ( M2 \6 u6 Y' O$ l0    1
    3 K* g" `% p" q2 y6 Y1    '
    + e. H( q4 ?1 s. e; v2    .
      A% j# I! x  l% v8 T: S- N3    y9 [4 X" ~& \7 J  V1 b
    dtype: string! H% g: \. U* T' l/ B
    1
    " C- O3 s  C1 F2 c! D  s2  r0 e+ ^% i4 L1 e
    3
    2 D2 b8 @, G! p4& E8 u& w- b8 a) e/ S; \: Y
    5
    ' b1 Z, n7 T8 e) l除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:) v7 V& i5 a- n) E" x% r; t
    2 f% a- f- E9 r6 r0 r
    当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。( i  ~- Q6 X- @9 E' {  t" \( t0 l
    string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。+ ^0 y/ M! Z+ n: Z# G- H
    string 类型是 Nullable 类型,但 object 不是
    % o1 U" R2 `8 s' `4 j" O1 f  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。$ C, g+ X$ q/ p8 t9 d8 ~
      同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。' f7 s: Y5 Z( S; |& `" O- K* R
    s = pd.Series(['a'])4 V7 c0 q5 N+ m3 f
    0 W2 l0 d; f1 @4 `- E
    s.str.len()
    5 K; a  J+ A6 W8 R4 IOut[17]:
    ( M% h& Z' i. ^+ h+ N0    1
    7 i# Q2 w& ]) @- p* x& a, ldtype: int64# H) E0 S, u2 y1 J

    1 F; A( i" h5 @1 T6 As.astype('string').str.len()
    + b3 e; ]' ]& \" \Out[18]:
    ' X; v7 g+ ]4 R; c; c$ R0    1
      f2 V, w8 G7 p$ D, F3 Cdtype: Int640 L# w6 W6 Q, v& ^+ N/ K
    : K$ X5 m$ O# r2 {; D
    s == 'a'
    1 t. V* A, `/ n3 R' X8 IOut[19]: 3 W( d+ Y" s( l' Y4 v. j$ X  _
    0    True
    , B! x8 J: A6 T; F2 xdtype: bool
    9 b6 ]3 \. B4 A3 T8 P7 B2 p
    8 ]. M) p' E6 f  T& Qs.astype('string') == 'a'
    + H4 b6 }8 z5 V! H( j$ `Out[20]:
    $ l/ l+ F. k! |' u. }0    True
    ' P4 v5 D$ H0 H8 R4 s+ _dtype: boolean
    9 O0 |  f# B$ D  ~- A. V% |
      x6 i3 n# }$ ~! i$ ]/ t/ Y/ A% S6 Ls = pd.Series(['a', np.nan]) # 带有缺失值) N+ U  H8 L4 y# B  S

    : [4 L- D# ?" N7 K0 W/ ~s.str.len()9 W" f) C1 M" u9 K) T- b
    Out[22]:
      E4 L7 @& Q8 x$ }0    1.0# `3 r: Q9 P6 @7 P. R
    1    NaN/ m9 M5 ?" M: _
    dtype: float64; J7 z" R# X) L* B1 {" r
    / ?+ U6 |6 u1 x3 K: X6 @: V) Z
    s.astype('string').str.len()
    1 c2 p/ Q7 S1 n# s4 d$ Q* X- c9 `Out[23]: ! t* c% K  B9 m5 x! r$ L, n
    0       1
    & @. k& ^! y* k2 w" m: P6 s1 c1    <NA>4 m1 M2 H# q7 Z$ h6 V
    dtype: Int647 k5 B# @) f  T; H* C

    . f: X9 U0 D7 ?s == 'a'5 n( z  F4 ?4 }" Z
    Out[24]: , M7 r& x! a; M: ^' O
    0     True
    6 n4 X" \9 j, E6 r) W0 d& ?: _, {1    False
    3 \2 N8 D9 |+ D4 o7 |# Fdtype: bool
    $ U- S5 K( {  F9 b: C5 G0 V$ ?4 l( C; B
    s.astype('string') == 'a', g! r5 j; R$ D$ i
    Out[25]: - v  s$ \. {5 g
    0    True
    : j7 x' E. T4 |% F% o6 G' a1    <NA>* D! F* O. j1 `" c8 \$ q( {
    dtype: boolean( M( q' \% ^! a2 k6 d) D

    5 c# P) d7 n, I/ U1
    0 P) B8 @+ y" k3 `* b2
    9 z, n* K8 X1 m3* j9 k& [) s% ~+ t! {% c
    4
    * ^3 _2 v2 h% W! J4 g8 ~5
      Z5 K' }& R& R$ ~6
    ! T+ O; l) {+ y7
    3 Y% `$ x+ g2 b9 E8 n* o4 A2 o84 [# p  \# `% U8 T: M! y
    9
    ( ^; Z5 Q! i- Z: r) O10
    , x6 W1 G0 S$ T2 [11
    4 }6 q5 m' @0 y- p) A' D12
    2 A7 G( r+ K& ]; ~- {! G13
      @& u0 y8 [: n' I% ?14) ]& C0 V2 j" q/ B/ ]) v- v
    15
    2 _- E; ^; J  s0 ]$ a* ]( c) G16" B% J& B! W& }$ a5 r0 k7 J. e' p
    17; J% |# I: |5 H
    185 ^& A; t8 V% ]; }, Z  D) c
    19/ W# p6 d* q: H5 F( C; B% w
    20! O$ @0 J. X8 B) Y6 w* O
    21
    + O1 N  ~7 E5 m) ^& z& t- b228 ?6 N( j% [9 Q3 F
    23
    & q' d- y; H/ g. n% x! Q24
    1 N: i3 S5 N4 \6 R# r1 V/ I. q25# v# F9 H8 y" `3 j
    26
    2 K3 g& e# A+ i$ c# s1 U) n, W+ w27
    - i' ?/ f) A8 ^+ X! X- S8 R, s- t28
    , ]7 i' p" t3 Z2 Y29
    8 r9 I6 `+ L; g# q301 s) d* s# {( S% D
    31; P4 h1 m1 }1 h7 ?# K% k/ g
    329 p8 c" y) g1 j
    33, d5 g% H5 ?; }* ?! B
    34
    " [8 L. [# l4 W  |8 i$ _. F35
    1 R% [" I; X8 }) f36% r, J! y9 V1 U, T' A: T' L+ @
    37
    4 \7 {5 T' M* v38
    3 }  y0 q8 m% n" `1 F9 E$ S39
    4 i+ O+ J* T2 `. ]3 T403 n/ G. i, f- ~" @' a7 i3 `4 D& L
    41
    5 d# l2 r8 f! e3 f% W42
    : p' X. N# k! D7 ^0 u7 n43
    5 W, T  h- k# \# w! N: Y( t$ P44$ o5 ?+ a# h" W: _/ a+ \* [
    454 X, u1 K/ f1 b/ d$ n
    46& t/ K- a; p; T' B- u: E- X) D
    47# U- T) I* a/ g4 j- a
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :% F3 Y& v. ^6 ~) v  x

    0 C, x+ s3 ?0 ]s = pd.Series([12, 345, 6789])
    : K  v. Q. h4 C3 ^) X* A8 l
    7 k) _: i$ R+ r: P+ {s.astype('string').str[1]
    9 f+ E& b1 w# p) l4 tOut[27]: % K1 h5 s5 j+ _$ I6 ?
    0    2: y, W) c4 v6 F+ N! R5 t5 H, o: m
    1    4
    & ~; d1 a6 u% G& \2    73 C7 O3 \9 y. Y/ o
    dtype: string
    * u& Z, S, ]- J+ G6 r0 K1
    , l( G* O& P" X0 W" u7 Z6 J( a/ V2
    ; G+ u+ ]6 Z/ @+ ]3
    5 M5 H, a' u: F( C3 K4, H) \. X( ?8 \/ [2 s# Y' c
    57 a* i+ T  f; J1 h* I% J& D
    64 H$ @- O" G- g! `+ |
    7
    " h7 `0 I& M+ u1 a1 X! I" i8" \8 D- K0 A/ F2 W! T+ a7 l
    8.2 正则表达式基础
    8 n2 Y2 ?& d- u0 L7 I0 f这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
    6 z* I& z6 }  Y, f/ L6 |5 [
    + e' w' F( j: h$ w- f5 m; e5 x8.2.1 . 一般字符的匹配1 z. @' ]" r  k4 m
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    3 D% `4 A  K, p( F6 j
    & t7 D- V0 t$ r" j) _import re, x0 J2 w4 i9 ?4 t/ \1 u! L4 m6 z
    - o! n& X* W3 k+ g; ?, a0 k1 s
    re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配. ]+ _$ @' K& ?& |* @2 k, |9 r1 q, \
    Out[29]: ['Apple', 'Apple']5 y: _; k$ q7 T9 m
    1  A3 i1 H7 f0 Z+ a
    2; e! j5 ~$ s2 f. ]# }' s
    35 g  V) H1 W. y, ]# e2 J: @6 L) k
    4
    , L* U3 J4 F2 u6 v3 B3 _% f8.2.2 元字符基础
    ! F7 }7 _  e' O, R: z元字符        描述5 w8 ?! h6 v; @1 {  ], b
    .        匹配除换行符以外的任意字符$ T8 A# d* e5 z! `0 d: j# [
    [ ]        字符类,匹配方括号中包含的任意字符( n+ j: a, J6 Y7 W/ ~
    [^ ]        否定字符类,匹配方括号中不包含的任意字符
    $ o. ]9 C3 |# ?5 ^*        匹配前面的子表达式零次或多次
      T# T8 k, h6 ^( c+        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字! m. H4 e( u+ H
    ?        匹配前面的子表达式零次或一次,非贪婪方式, T5 \: K; \0 e- e7 `% v. @8 Y: P+ I
    {n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式( X$ }. \! i% f2 Z2 q& R
    (xyz)        字符组,按照确切的顺序匹配字符xyz
    8 ?; X: ~* y$ }0 G6 A|        分支结构,匹配符号之前的字符或后面的字符
    4 ^. l0 y; m' E\        转义符,它可以还原元字符原来的含义  d6 T, g2 i1 Q$ q5 b# q
    ^        匹配行的开始
    . Z+ y! r  G. A* E) S9 q" t" A$        匹配行的结束. X6 z! O1 O+ |; i, T
    import re0 s3 S, h1 q/ J) s: Z0 w6 g& W2 A
    re.findall(r'.', 'abc')) i4 j# Y8 r% Q0 j$ |- {
    Out[30]: ['a', 'b', 'c']4 n! H1 A  r( X0 Q0 y' {

    " z. o* w) K. Y! m! m2 W9 M, X  Gre.findall(r'[ac]', 'abc') # []中有的子串都匹配
    , r3 M  n# M/ E: m8 OOut[31]: ['a', 'c']
    ' l% L% O+ ^3 f4 u/ O' i( ~, p, D6 ?; y: H
    re.findall(r'[^ac]', 'abc')
    ; }9 t& U7 p% n# j) C5 `& f5 a/ `Out[32]: ['b']. h1 Z; g! k2 W% u7 S

    4 z3 N0 a! y" R- H* d8 n7 ure.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次% U0 L( ?6 m0 G& R. {# T
    Out[33]: ['aa', 'aa', 'bb', 'bb']
    2 p. s% ~& _9 Y' Y
    5 a6 T! M( x- R7 R" Q) U: pre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串/ Z+ c1 o3 J2 P4 w- O
    Out[34]: ['ca', 'bbc', 'bbc']
    + a' R) U& a- d1 ?! c/ G. m3 w* ]- T( j5 p" Y
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。. l: O8 e% ^0 r/ z& f8 V: E
    """
    ( O+ L* y2 K/ r% f3 h8 k1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。; j/ ~( P8 h3 v7 t; `: R% }0 O
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边. n6 s6 X5 ~- j- Z2 b# r
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
    # t- q6 a, n  l/ x! x但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    0 a0 `9 ^9 C1 ~, l0 I. x9 V"""2 X2 i0 v# d% I+ S3 w+ M& M2 V5 M

    # U7 h" }* W7 Y7 Nre.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。
    5 `; e3 i+ z8 a. C3 [( I# DOut[35]: ['a', 'a', 'a', 'a']  @/ D- |3 m% P, ?- M. T4 u3 c

    ! n; M  a4 S3 A# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。3 z8 V( s& B' [
    # 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
    2 [( b9 X+ S  X2 Qre.findall(r'\\', 'aa\a*a') , S( L, H  d3 V2 k% l6 v! A
    []
    3 u, i# n0 g- `3 C( K4 ]0 D6 Q0 }+ d
    re.findall(r'a?.', 'abaacadaae')! }+ [+ ^) A+ m2 Q/ |6 \4 L
    Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    : {# }: x+ r( V. `( r& b* t, I' T
    6 X; d: Z. M3 {% e& d; N( C4 ]re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表! s1 g3 H; e7 ~, @% ]
    [('width', '20'), ('height', '10')]- P% y+ `4 i; J
    ' B2 x& @; f/ I% t& i
    1
    . d$ L. p( u& h$ p" o$ I2
    $ w; U6 c; \2 L; s) F3
    9 ]+ F% Y; V; `2 W7 }4 D# g4: M) G* O- Q+ z& \
    5/ h! s8 ^, f4 l6 D* a0 O
    6: G5 R- H1 m  x/ e! P  ]/ x. O; H
    7; m% H3 i0 }4 V! N# W' z3 C4 r
    8
    + s/ s3 Z, m2 j9: \" e  s0 ^! J* J$ R3 Z
    10% @2 H1 F2 e  ~: K1 R2 `8 T
    11
    2 b. I6 s- k  I12# @" X8 r  n7 h; x
    13
    6 a& F9 V5 U) y$ G! w14# n" y' P5 m/ O4 m( h
    15
    4 P* S* n" E/ W- D9 {% J162 @$ `7 m) e, G2 t
    17
    6 [) ^+ _4 X* y. s2 O18
    4 C8 L3 P+ W' y, _# ~194 h8 |$ l- ]2 q1 ]& G9 S* @; D
    20
    4 p% D0 r  U3 n" N21
    % a. }$ [9 o( i$ s0 Q# k22
    6 U+ q, u+ V4 E" i23
    8 i2 E9 T+ j: R$ A( G4 x( R24
    " Z3 J, O4 T8 @8 h( R254 S# A$ a: H  q: o- @( t2 N3 i7 d
    26, F% h! U; W" K5 A5 [) I" M+ p
    27
    ( q2 Z% A% \1 i28
    ) I' M' t6 \" d+ r29
    ' s. Q' @; s" C$ p/ ^306 C5 x" r; u- c6 s9 u2 Z+ o
    31
      x& A3 U, j' b- h; c" I% P321 h3 z$ S: V) V" e. I0 D
    33
    ; V7 V* \4 ~4 {( W0 x$ }& b' E347 r& v9 \/ M5 K% J& w
    35
    ' Z9 ]. \3 _  n: S) ?& V36
    % n2 z( g  s# Z) j* P% B378 h; P% y" s$ b* \
    8.2.3 简写字符集6 |, g, s3 \$ V0 H2 P, {( {' I/ {- n
    则表达式中还有一类简写字符集,其等价于一组字符的集合:
    1 ~9 P/ h# S$ A9 S# M) S5 l4 g% R4 \) z' Q3 O6 D4 T
    简写        描述
    . D, i, n, [0 Q& p\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
    6 U: b# F& L5 c5 Z4 Q& r\W        匹配非字母和数字的字符: [^\w]
    ( v2 U6 F- B  u( _8 m% {\d        匹配数字: [0-9]
    # I: v0 S7 n) p5 r\D        匹配非数字: [^\d]6 M1 T7 t$ T# M
    \s        匹配空格符: [\t\n\f\r\p{Z}]( [% x, k+ k9 o, d9 e" n
    \S        匹配非空格符: [^\s]
    3 ?$ ^/ j6 E6 R9 W0 P\B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。- E* p: p1 K4 ]: Y! ]
    re.findall(r'.s', 'Apple! This Is an Apple!')# W: r' Y, d$ S( t8 o/ s/ s3 s) h
    Out[37]: ['is', 'Is']
    * k( t  I- M  w; `' z; \8 w7 p: d, c
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次* J" i/ R4 q/ {7 O
    Out[38]: ['09', '7w', 'c_', '9q']
    8 B3 n, b! C( L- D! {" @/ H1 b0 P, i1 `& ]0 g  t- t: B
    re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)/ ?9 d( h4 C/ G( m5 @9 S0 _7 J
    Out[39]: ['8?', 'p@']
    - U6 {; E- U  m" o6 T4 \7 v
    / r! T+ p3 w/ r: Y* R$ ]: v. s7 |, Kre.findall(r'.\s.', 'Constant dropping wears the stone.')& {/ n- t, u$ K& D* C
    Out[40]: ['t d', 'g w', 's t', 'e s']1 ^7 X0 n3 z- ~8 B6 l1 e6 N/ [" i
    % _( @1 ~0 u  R$ r, D+ V
    re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',0 Y5 d- r" ?5 v$ H" D6 @7 Q; f% R$ c
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')4 o: Z, t/ j/ o% m7 }7 q
    2 s$ g* r  o8 `+ ^7 O* c
    Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
    ! u, `: Z' P$ y; M: P( I
    0 z3 J3 N6 P' P" r+ \1
    1 Y) t0 f2 \% O0 Q3 N# K2. D" @0 {; U# O  }, v' m6 \# @: e
    3& g5 W8 ^0 m" A; ]2 K4 w9 _
    4) X- Z0 S* z- p$ `: X, z
    5" f/ r5 S( r( R' t0 G
    62 Y5 R8 R, ~) |4 R/ ?9 ?
    70 j" Y6 i( u( Y! ]2 T# u' F
    8& R8 Q' I+ e* Q- K% M# k
    9
    / K. \9 C% K. u10+ @. x' w  [9 V6 {- j; _; g
    11
    4 X4 K2 H5 n2 c/ X- \12
    9 w/ O1 D, J6 O! T. a3 L13
    % I$ f1 a) F. L, }; B( G14
    & K6 s+ W0 B- Q! U15
    / b- n+ J7 d+ p16: f# B6 H0 K  W0 E1 n" v
    8.3 文本处理的五类操作
    , b. A2 y5 [0 ?+ ?( A8 y8.3.1 str.split 拆分
      O. K- M# O' r( [  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
    6 q+ l$ w2 K4 Y/ @( j" Y4 a# w- b( C4 R) X3 n
    s = pd.Series(['上海市黄浦区方浜中路249号',
    ' c' Y7 C! z/ W8 N" g+ A* L& b9 i            '上海市宝山区密山路5号'])$ L9 j5 Z3 R* j$ ]
    * ~9 _' F- w) }! w9 b# e6 a; ^# }

    , {( ]+ o  Z" `1 L4 `; es.str.split('[市区路]') # 每条结果为一行,相当于Series
    , \9 A3 z( J1 _  D  kOut[43]: ' T1 O0 o1 W: m5 P2 a8 e5 ^
    0    [上海, 黄浦, 方浜中, 249号]
    / W% @  W/ L6 }6 ]+ m8 n% I3 Z1       [上海, 宝山, 密山, 5号]3 t/ V; W' o/ S* p8 L$ U
    dtype: object. h" l& i) d2 \# l8 Q' p1 N

    ( W" z. l# L& Ns.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame9 a' w# j$ M; C6 w
    Out[44]: 3 C  \: ]/ H& W/ K. q
        0   1         25 g1 ~/ P  K' \( ^5 H3 ^
    0  上海  黄浦  方浜中路249号
    ) b; p3 ~' O2 `+ M" M1  上海  宝山     密山路5号7 d4 y( C% x( U1 {* E+ e8 X: q
    1
    % s! A3 ^" |, O) c9 B2
    ' h9 X# w0 R) L$ S9 I5 _" e, t3
    ! E9 O6 i" N6 f  j4
    ( c. t1 W! @$ P- o6 j. c9 ]3 G- n4 U5
    % V& E0 k/ A& l1 o! M; @* Y64 P% A0 ?5 o; U
    7
    ( e9 Z- n7 Y5 q1 W84 }9 P5 e* Y0 N2 w" R  v/ p) }# r# I' ^
    9
    7 m% n+ E2 |5 S  M$ b- C) h# P4 i10
    2 V8 T3 t9 V4 h8 A( B7 ^- h+ x11
    " h+ u6 @# K" E/ x12
    ; r' a( X1 Z, J) p8 ]  {13! l: M' C7 @6 j/ ^& N& ]
    14
    0 R& l# d, Y  {3 |, Y; R2 R1 B15/ W0 A$ G" n0 n" i
      类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
    * M9 j) e/ M# O& C# v1 v- _9 G* Z" y) V5 M* k  T0 v. ^9 ~. N
    s.str.rsplit('[市区路]', n=2, expand=True)& o# a/ |* |5 ]. N
    Out[45]:
    8 O0 V! C6 e, J* C                0: V2 b% c& N; d
    0  上海市黄浦区方浜中路249号
    $ l# v7 _2 s: w: _: ~  {5 l& c1     上海市宝山区密山路5号9 ?( d  W, H, Q& V) n  M* r. K; t
    1
    # Z' ]8 Y# g. k& s2/ X7 i! e0 m9 r% {6 G, |
    3, Q8 b, X9 V1 o; J0 t7 q
    4
    ) G% u1 j( a: _. f: Q9 j1 L5
    6 {9 k  H- i' d7 b  y% u8.3.2 str.join 或 str.cat 合并# z9 a; Q7 _+ N
    str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。( s) S2 \% h; m
    str.cat 用于合并两个序列,主要参数为:
    : Y+ h. D- B/ b" x9 Nsep:连接符、  ~% R( M# f! J0 R& J/ H: A7 f$ R/ \7 U
    join:连接形式默认为以索引为键的左连接$ i; h( V3 r# f
    na_rep:缺失值替代符号2 i- b- D+ d' X- T8 {, G9 p8 L4 y0 ~
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])3 x9 g1 f5 \- w' p2 o4 o; Y
    s.str.join('-')8 d4 r+ n. G5 Z# g
    Out[47]: ' W% J8 z' C4 H9 f
    0    a-b6 V+ l% T4 ^( I3 m. M& e
    1    NaN, Q5 e8 D2 J8 L* n: D) [/ D+ g) j
    2    NaN; {% A$ o4 Z0 p/ X
    dtype: object
    + L' K: }, `( O) X4 N, B/ e% J; M1+ h5 S2 W2 p+ h5 C' o7 n
    2
    ' E% ?4 [& a* i1 g; w32 J/ v* V+ I/ {9 [) g8 ^$ c4 f) k$ Q
    42 x4 h$ i* G) E0 \
    54 J/ y# L; M7 p3 _7 H& X
    6, f- z0 T9 g9 v' A
    70 N  W7 O: |5 o/ y
    s1 = pd.Series(['a','b'])
    1 `, B* F7 Z( U/ c" ts2 = pd.Series(['cat','dog'])# g3 z6 s& k1 x
    s1.str.cat(s2,sep='-')1 T+ p# w. G% |+ F8 D' I( a8 C1 T
    Out[50]: 4 j6 x2 _: N) [) L6 `0 }% G
    0    a-cat
    $ z7 {: O  D8 I4 ^3 I  b2 T1    b-dog
    + r; b' A, ^1 a3 v( t" Pdtype: object$ q* ?! B( V* ~) t, |/ F9 z9 i

    ; }: w. s. f9 g% J- a! y) @6 bs2.index = [1, 2]2 `# H9 {% W6 v" `) }! L
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')
    - m/ P0 ~9 r/ c% A6 @  d+ zOut[52]:
      N+ e: D; Q5 l0      a-?
    ; d% x2 ~0 g( ]- T: J1    b-cat
    ' p/ ?2 d+ o- d+ |, e" Y2    ?-dog
    $ ^4 c# s5 e6 r  |dtype: object
    7 n( l0 z+ |- v( h1+ e* X, W3 a7 r3 T
    2
      A! H$ \# R. ?4 O7 I* V3* c* @7 `" ~0 l. E% b2 T
    4
    , A8 w0 D1 j* w! w  z5/ O1 Z& N" h* D" S" J
    6) W! W4 {% J$ j, P) D. ]. }
    7$ y; j+ O5 l9 y" U9 \
    8
    + x" J1 {1 {; q8 K95 A6 m& o9 c! h1 F
    10( Z, G9 H8 ^0 e, }, e- |2 Y5 \
    11, k  u5 ~- x3 w& W
    12% h  q9 a  r  b6 D1 S4 L5 i
    13
    8 [( V6 e$ J! w! {! K14
    & L9 R" l9 G8 f3 X0 n$ @, f: g15: }6 X0 ]2 q6 m9 s' m3 c
    8.3.3 匹配" Q4 s5 a! n$ M2 [( R( q" k
    str.contains返回了每个字符串是否包含正则模式的布尔序列:
    3 q# [1 ?; g0 Q" e' O/ P) g% ]s = pd.Series(['my cat', 'he is fat', 'railway station'])
    ; J  `- d: h3 c+ Q/ K+ [s.str.contains('\s\wat')! w# `. C; U/ t3 H
    5 r# e* D5 a: c2 L
    0     True
    1 Z5 s5 v9 O" |5 F9 I6 v9 Y1     True) `$ \, k0 \3 F- M2 j
    2    False
    " n( l9 t" J- G  T3 o) bdtype: bool3 w. N& x) M% y, h  C) X( g' H
    14 z( w4 I& V+ ?8 c* y
    2; @* w+ s. ~  A7 a/ r" [3 r
    3
    / x. S' X3 Z# e, ?: W4 O5 `: T4
    3 D1 P& Y# |* M3 {" H5
    : A- K' H  r5 k4 {6) J1 f! P' m+ ~6 G0 N- i
    7
    & c) r* H5 m9 X0 e7 p2 J7 Estr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    / h& K# A' ^- F0 d/ W9 _" ms.str.startswith('my')+ i  X) x% U+ l, b6 ^. |
    - u5 e1 j7 {4 B+ |$ m. i. `) {
    0     True
    ( I& D1 }" S( w4 t1    False
    3 g2 p- r1 q& @+ [2 \% E2    False6 g" h1 T8 s' z4 F# M2 q
    dtype: bool
    8 j) ~) n" U; u8 K% O1 M8 J1
    3 ]& ^) c: M( Q! k- G5 J% ^2
    1 Y- q- O, }3 V/ Y: n$ [$ _! w% E3
    3 X, T, ]4 i" z( U2 {$ J$ o4' b& t. `4 M; i& t. b5 d& C
    5
    ' F3 x. z/ z5 }1 N; a6' ^* R7 N; r  f4 l6 h3 [+ W9 m
    s.str.endswith('t')
    1 g$ }7 N2 T# v: d$ E
    : P0 X/ i% H6 {; r+ K0     True
    ( V2 G' j( D2 l' g4 V4 {1     True; X! n2 ?+ }6 f5 Z; g
    2    False9 ?. H& i" q6 U8 v5 q) a4 v
    dtype: bool
    # G! g" f/ C% n( {" S15 h5 k8 U0 R7 c
    22 S2 c6 \& M2 J
    3
    7 v; t" Z& X9 c7 |# B4  r( `- Z8 M3 t4 j. W( d6 g& ]0 O
    5
    6 s# F5 n; }, R/ r/ l: }& C69 V5 j6 \$ R3 V3 b
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    0 V3 e* g4 B) f1 t' Ds.str.match('m|h')" r% K) R. V0 N0 ]2 e
    s.str.contains('^[m|h]') # 二者等价
    * x# c% r( k8 h- W" W0 j' f/ m; z% l; e
    0     True( ]( @. U. ?  ]" _! E9 j2 Q. e) A
    1     True- R! X- F# b$ B1 R" {3 n0 Y
    2    False
    # t# X% H  m6 G5 }# x3 w% mdtype: bool/ _# P) l; R. s  `
    15 v- T, H5 u/ P5 r% G) Q
    2' B9 }3 w3 t4 C; A
    3
    * t! {# r- e' v& b9 Q) E4# B5 P6 y% E* ]0 ~4 K% x
    5
    ' ~7 `5 ^8 f9 q, a; t9 Z. D6
    ; f1 _4 Y/ y8 L% c3 @4 W7  l8 n2 l; I$ `# ^
    s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配
    * k0 h, `3 l5 Q  l8 H, x6 Z" \* Ks.str.contains('[f|g]at|n$')       # 二者等价
    9 N8 e0 ^4 X9 c! r9 {- C6 S% N* P- I* M9 k) R
    0    False
      z% e/ |2 r! d( k4 ?1     True" X1 U0 X' `6 `& b$ ]& w# f0 F$ Q
    2     True+ Q0 {/ E5 ^- m* W7 Q
    dtype: bool
    ) Z8 R6 p  S! J6 ]. M; F1
    0 ]" t; z9 J; [, D9 @2  Y# _0 l/ l3 G) P# d
    35 ~' s2 D% l  s5 U# F4 W# w
    4
    : j* }$ t' L- A0 Q9 o: c7 {52 X/ ^9 c) x; U. |
    65 ]% L  J: Y. T) ~0 h8 L' `8 p9 b
    7& Y; {; m6 {1 C0 p4 O
    str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
    1 G6 n; _6 @. \, ^- ^( Rs = pd.Series(['This is an apple. That is not an apple.'])
    1 c+ ^/ T- }0 P1 s6 M1 e* i- ~
    0 J* M' u1 b+ k1 \1 b8 z6 g( Qs.str.find('apple')
    3 L0 M* q) ~. d( `! a9 HOut[62]:
    5 j3 D; ], ^. e: v. R  y0    11) ~" E2 N* j5 y0 S" F  |  Z" K
    dtype: int64
    8 o8 f  N8 d9 ~! v# x8 @/ H; [- @# t9 \5 b1 H" W
    s.str.rfind('apple')
    ' B0 l0 B! H2 ]0 j' H: c9 ?' n0 c  p$ KOut[63]: # a; Z5 J' ~0 i7 V
    0    33
    ; ?: M' _9 m8 |dtype: int64
    $ q$ ~- s0 t$ v: i: j, h1
    4 t- }, {: C! }) |7 Q23 M' }' J. U6 `1 I) u0 ^1 c
    3
    3 e; U7 E- Y9 t$ H3 U" q# e0 c' P* R7 z4' C) B1 r# J* F8 a+ _2 m5 \
    5$ h/ D0 a; X# k- R0 G
    6
      p6 C" ~, r7 _5 {% G0 e6 b78 F; `2 e: y5 X6 G# d/ h- Y
    8
    # k0 Q* B; L7 H, t- F96 i) s. q, A9 b1 Y! U# {
    10. p8 b  u3 L, U# V# E
    11" M( z9 D3 k6 G# I+ a
    替换
    9 w' z0 E. d6 D- v: cstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    ( W7 C' u4 P4 h- fs = pd.Series(['a_1_b','c_?'])1 x) Z3 \; S, b& E  I
    # regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?0 A7 ]! l, y! ?: r* W+ V: C: L* F
    s.str.replace('\d|\?', 'new', regex=True)
    9 c) _* Z+ c# @+ |: f0 K* i8 `: X$ S1 E1 E" p# V
    0    a_new_b+ _( Z/ @, x4 C9 T
    1      c_new# D" c* A* v4 C" j
    dtype: object4 d  @% u, [+ B* A# x
    1' o$ P8 p1 j) e& \/ c
    2
    0 A: f9 b" k' b) R3. O; U3 A/ c9 n7 T9 o6 h! O
    4. B& j. O8 y) w. h. U& k
    5
    3 A8 i, C; j4 O+ K! m6
    3 w2 t8 S, W  M" v# Y2 L& {7
    7 O; y6 o9 Y' j- x! m+ k  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):& D+ M! O  e6 s5 a3 g
    ) n# Z0 {1 x- g! c3 H# |5 @
    s = pd.Series(['上海市黄浦区方浜中路249号',5 x4 v6 o( j, s) `
                    '上海市宝山区密山路5号',
    # Q0 k) Q3 J* x) h, w* u                '北京市昌平区北农路2号'])8 W% C- W1 b8 o' f( Z
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    ; ]0 |  n* E' y1 j* ucity = {'上海市': 'Shanghai', '北京市': 'Beijing'}, n; Y# k+ f6 u# ^5 R, `- f( ]
    district = {'昌平区': 'CP District',8 J. b1 @( ]) }- L. X- R. H
                '黄浦区': 'HP District',
    ' M9 x; E$ k0 N/ W4 ^            '宝山区': 'BS District'}7 L' e! B& P/ n6 j
    road = {'方浜中路': 'Mid Fangbin Road',
    0 y- e" M4 V; ?: G$ h* S* c        '密山路': 'Mishan Road',! b$ M* F2 d8 Y0 L
            '北农路': 'Beinong Road'}( [1 P# ?3 o! j5 z5 Q
    def my_func(m):% @# ?  a  }0 g* V
        str_city = city[m.group(1)]
    ; }( h+ m. g! k    str_district = district[m.group(2)]0 f/ N" O5 E: A. _+ e! j0 z  O2 A
        str_road = road[m.group(3)]2 S  K, x& d. {' k- S+ h6 N
        str_no = 'No. ' + m.group(4)[:-1]
    6 u  C2 t1 X  p1 f    return ' '.join([str_city,
      M  D" z6 u" |5 ^3 H                     str_district,/ @7 R& ]- V. q- e2 g
                         str_road,+ v" M- p' A' p- ]2 I# ~
                         str_no])' A9 i0 n5 `7 H: x. P# F; \
    s.str.replace(pat, my_func, regex=True)+ ^! S  ]: u) r6 y9 p& I
    ) `3 |7 @$ m$ c0 Q' @
    1
    : h1 v1 a0 K- y. {, L2
    7 {. O' q6 e! U35 o2 ]! K9 N$ Y7 n
    4
    6 ]+ f6 }8 P9 L2 C5  \9 @0 w0 P* M, p* I% R
    6
    8 d( \* a! G; `0 f' e; w) Q1 q7
    / W6 G7 a; h. l) C8 D& `8
    # p) |2 p! a5 y& f3 x9" R, k: e" F* b
    104 z  C- t. G2 W, s/ W
    11
    # ]. l2 |+ T5 r, H12" K* I( V3 C3 z6 c* F
    13
    ( v4 [. b6 `8 ~8 a6 c146 [$ e# i5 Q( a+ B/ T
    159 |6 Z( P5 f/ _& M; b5 i+ @
    16
    3 m2 Y* V/ Z3 S; C17
    ) [9 Y4 [3 q' ~8 F- \- `18
    : R, b; U6 S; J& z8 [4 s199 m$ w& i0 B! |  O2 u9 l
    20, P% E9 Y. ~( m- K0 ?1 f$ y. s0 m
    21
    1 _$ [8 N5 k; f% a0    Shanghai HP District Mid Fangbin Road No. 2493 c- I: ], B& ~: F8 _7 z
    1           Shanghai BS District Mishan Road No. 55 P2 M2 j* K: ^' s" }7 Y+ r$ f
    2           Beijing CP District Beinong Road No. 2
    $ c1 [9 q8 |# idtype: object" ~4 R, \% e7 E% }2 [
    1
    ' ?% ]. y6 p# _9 H# G2 v2
    4 e9 D( s2 [- L! P9 B2 y34 J& _8 b( q0 a4 r
    47 K7 {4 s$ g1 K% U; I/ I) N
    这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:, E6 I2 n# \2 O- l% P! J" [  I, B+ ]

    . g  r4 E4 Y) H" U' B7 S, w# 将各个子组进行命名
    ) W  s$ `5 l* D* B4 Apat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    : Q' R& F8 X9 {2 U6 @% H/ Qdef my_func(m):& k: s0 j1 }% R! }, m
        str_city = city[m.group('市名')]
    # F# @4 z9 @* A. b6 {4 G    str_district = district[m.group('区名')]
    % G0 D5 m$ G4 d+ G5 N, H    str_road = road[m.group('路名')]
    . `9 r5 c) o; ]    str_no = 'No. ' + m.group('编号')[:-1]
    9 U- g  m, K! _5 X5 v( |; ]2 t6 g    return ' '.join([str_city,
    . r3 W  E7 [/ `                     str_district,
      T( k" S& _" v+ b1 P; e+ L                     str_road,
    8 V: v  K, A% D+ [2 l                     str_no]); G+ q" N  ?0 g6 E' V: [! i
    s.str.replace(pat, my_func, regex=True)( `9 H5 m5 f( y
    1
    6 E: [* X  S, e/ L3 \; R8 L1 D2
    ( w( r$ l% H, r- `( z# a. B' u3: a& ^5 D% |' n/ O' P
    4
    ( }! {* [8 {" F; N, b: L58 Y% J/ l1 u" s; E  l  Y* Y# g
    6+ v  w9 O+ r" N, G1 H& Y9 a% x
    7
    7 `" ^1 y3 L: k. w/ v% _" f! v8
    ) ]5 @. W) d$ [$ M1 }$ N9
    1 Q/ S/ C2 P, t! v. ?! @10
    1 \/ E) U& b4 I1 n8 A0 e, ]11- d' w' e( W% B! t4 A
    12
    + ~: C/ Q5 v( @/ {0    Shanghai HP District Mid Fangbin Road No. 249
    ( C' E1 Y# k6 `3 @+ m7 f1 G1           Shanghai BS District Mishan Road No. 5
    5 h1 s0 W5 V2 ?" y, p2           Beijing CP District Beinong Road No. 21 \& v: o) E% Z( T
    dtype: object
    ; t, I0 b+ s! X. t1# f! H# V+ C( z1 `+ `( a9 `+ ^& {- T
    2
    8 F9 s: G* S. N4 v9 U8 f" M+ D8 u7 ?3( Z. ]3 f6 L6 ]/ s1 v) l
    4- q" x3 E6 H& ^
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。" d2 E% E7 S  I) N, @. c' C: h6 M- N" ~
    / R% w" r" I! o" P5 T+ O
    8.3.5 提取
    ! |& d# U0 l2 Istr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:$ r! ^8 n% f& V! d
    s.str.split('[市区路]')
    , i. X0 E- u& K* m: QOut[43]: * N+ U" H! c$ B5 f
    0    [上海, 黄浦, 方浜中, 249号]
    % f; F0 P& {! ^0 l% @1       [上海, 宝山, 密山, 5号]
    ) Z/ |- ^& {- G2 R" g. y; `1 ?dtype: object
    0 V+ ^/ s+ {8 m% t2 `4 ~, j
    # o+ w, g+ }7 l5 D+ ^pat = '(\w+市)(\w+区)(\w+路)(\d+号)'4 A0 k3 S. h4 l4 M1 g# Y9 J
    s.str.extract(pat)
    $ X# m7 J7 ^2 q; }5 COut[78]:
    4 ?) m0 U( D7 P. }% |1 d    0    1     2     3
    " _% S) I  o8 v1 v8 d0  上海市  黄浦区  方浜中路  249号
    . Y! C6 U  t, v/ r, z" n1  上海市  宝山区   密山路    5号& [6 d8 S4 @- d) y
    2  北京市  昌平区   北农路    2号
    - g9 B4 ^5 V9 a& z% i- A' x1
    6 w- _# @( Y. k. Z" M1 ~! z: Z- ]2$ u* N% P& }) i- v. o8 f7 {# h& K
    3
    ; C0 C/ A( F  B' B/ P- r4& C) C, ?: s4 e
    52 M, @7 U& e6 T9 v
    6
    ' ^5 V$ ~  w  A7
    : B' |7 A! N. S% @+ g* n8
    . p8 S2 }, W7 }4 C" \/ ?: `- B94 t  o' N+ V1 ?$ y: U% C
    10
    % V: h; L& J4 G115 x2 a* D; R) @6 l
    12+ w& w4 I/ ^) r3 ~1 K7 g
    139 {9 R, E  R7 C' N5 f, d: W6 D  D' b6 }
    通过子组的命名,可以直接对新生成DataFrame的列命名:  T  m. v' V3 Y( U7 _% u
    9 O8 }5 n& }* b: b$ X7 C# J& n
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    " G, c- i, \6 d- u  s' Zs.str.extract(pat)
    ( C% `4 F! {  }3 X4 b8 SOut[79]: - Q3 o$ b' T) Q
        市名   区名    路名    编号! n) {% L& a! o- D1 j
    0  上海市  黄浦区  方浜中路  249号5 S9 t) Q' O" p2 d0 U+ E; r1 ^5 I9 o! N% t
    1  上海市  宝山区   密山路    5号
    % d- S6 x7 S2 D! i+ l+ s6 e4 V. Y# t2  北京市  昌平区   北农路    2号
    & k, e% l! B) M6 r3 P! W& X1# D9 q. {. N( Y- V
    28 R( T# x! R; w+ J0 |
    37 a! V" p  w- U4 E1 l
    4
    ) m. P, ^6 _: m5
    # L1 C9 Z6 x* d. b4 m" _! d6( r5 Q& d1 P7 D9 T  K$ m# \0 R
    76 G. S& P$ `4 V: p" Y
    str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
    / [9 m  F# n/ `s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
    # Y9 o1 }) M4 G3 z. Npat = '[A|B](\d+)[T|S](\d+)'
    3 y' @& D. q' L. i- |( a* Y0 ^s.str.extractall(pat)
    " @- R6 _* y, g; {Out[83]:
    , Q. I* }3 q! c5 @) z       0   1
      ]0 x: e0 X$ @8 }     match         & j7 f1 \! |/ n, }$ [9 X( i
    my_A 0      135  15
    2 Y) k$ x- @8 w     1       26   5
    ) @( R) I7 s( D4 |. }, u5 @) qmy_B 0      674   2
    4 i. s8 q+ f3 {$ V( v8 W7 p     1       25   6
    - ^3 C# ^: x  S4 u; O! U% p10 d2 ^, W: P# q( R
    24 R3 R2 f8 K# e
    35 y7 V8 w! B5 R" L! O0 l' T0 c
    4* B3 n. c. |3 ~( N+ Y; v9 N9 B
    5
    7 R1 c! F. o4 b" p( f2 {2 V* V) i6
    - E  o: x( [( w: [7) T- j) R) G' H. ~8 n
    8' o  u3 P& b( ?5 |
    9
    : F0 V, H5 f9 @1 m" o* ^! Z10
    $ L  @9 {; {2 L. o/ tpat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'1 _& z: x% Z6 h4 ~  R, y7 E# ^
    s.str.extractall(pat_with_name); ?8 C, k5 j  t# [, ^' y
    Out[84]: 7 W1 S: V' E) ^( ~! a
               name1 name2
    / ]7 `3 D. F; Y* H) ^2 s     match            
    # u" |/ K. r, i1 Bmy_A 0       135    150 g; \5 ^. x" E
         1        26     5
    ' f& p* C& y- N% N+ P1 q. amy_B 0       674     2
    % X$ ~& c+ j* @     1        25     6
    % n2 s' }/ `/ D( ^7 k, x0 V1( \2 x% t  @' t. ~
    2  h( C: `! |# r$ O. c- Y7 v
    3
    $ a8 R8 b# P6 @7 c0 p9 t/ K9 D4
    # d2 B6 _- D" W% m# L5 ~2 J55 e/ D5 g/ A+ u( W3 i
    65 ~8 y/ P8 C$ K; c
    7
    3 I( z( ]- t8 T0 V' @88 u7 o0 r( p7 @6 J- N/ A- B! [
    9
    & t; q2 [/ n7 n2 p1 x/ Ystr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。4 M4 e# _  q& b& [4 r( G, X) P
    s.str.findall(pat)! G& @* A1 h$ {9 ^( y9 a0 {; B3 v
    1
    4 F/ C: L+ e- mmy_A    [(135, 15), (26, 5)]
    # ]" Y+ K5 a- a- d* b! D! Jmy_B     [(674, 2), (25, 6)]
    / }) a7 s  H9 s) y; j2 @dtype: object6 _! X9 b" N, R7 ~! b
    1/ Y5 u0 W( w% L
    2$ i# @1 z' N( O, q0 V4 x
    3
    , g! R$ |" {! R- C& z6 I6 D8.4、常用字符串函数
    ! ~) c  d# n" p6 u; d) |  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    2 N& q. g+ f# b# f3 r- Q
    5 e# p% Y% M* u5 n8 Z, m5 E7 b+ `2 X8.4.1 字母型函数
    $ r, N8 @* s! d1 C8 @  S/ o- i  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:+ D7 H9 r5 o2 n

    5 D0 `2 }! ]9 ks = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])( A/ D- g( d7 H; d6 m' O* c
    . V% {1 W: _" A2 J& f: {, C
    s.str.upper()5 ~% N$ H/ e! R3 i/ c0 H
    Out[87]: , A1 ^# @" c- j% v5 s; T
    0                 LOWER
    ! q) J6 D* h8 P; k. D( i1              CAPITALS" _% q! O8 X1 B* q
    2    THIS IS A SENTENCE
    9 s9 _) e3 y1 [% _) s# U: H3              SWAPCASE
    4 z4 d0 C" U* D9 K$ idtype: object
    9 f1 Z/ d. o" [7 C/ q! }+ |% |4 X* S" n$ S( ?! v( Y7 D  a+ d
    s.str.lower()
    7 ?4 p9 j! a) g$ ^Out[88]:
    5 V/ R8 Q: r& v, d$ t8 n8 m0                 lower
    1 F6 `3 H) l( L1              capitals$ n$ {& x' Z( j' b) C
    2    this is a sentence# {3 G* Z5 r  G! X
    3              swapcase0 ~2 @1 n0 Y! A) G
    dtype: object& @. w' r1 i  |* g$ O# O' X' V2 ~
    - f) l. V3 a$ g. H, \
    s.str.title()  # 首字母大写! G8 U, `) c) Q
    Out[89]: ) C" O. D6 D- t1 t- r; o8 l3 H/ ~: {
    0                 Lower
    - k7 I3 j. M$ c) z1              Capitals5 k$ w  s  e. W' k# b8 h
    2    This Is A Sentence
    ! ~4 }* w1 a1 u- O3              Swapcase
    ' e* P" R: }/ Y! y& {! C+ ~dtype: object8 [  d  ]& l$ X! b$ c( N+ ~

    & n3 ^) [+ o& E7 F' is.str.capitalize()  # 句首大写
    8 i* \# K5 C  ^; U& x. rOut[90]: + P7 D. r# K/ U" s+ W- V" N4 t' d
    0                 Lower
    % e! h7 f1 j7 u" s8 s% F  z- B! N  J1              Capitals
    - `8 E5 _( ^6 }5 \1 O2    This is a sentence
    3 j7 G& _6 L$ y5 Z, o; Q3              Swapcase+ f% ^+ u5 O3 }1 j6 v* s
    dtype: object
    $ Z7 h. Y1 A4 a
    " n% V1 D( \' T: V" s8 L: \% `s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
    7 }7 a- z8 h. b* Z; `2 c' g% ]Out[91]: ( m) w9 a( w0 k) `& `, c0 p3 i
    0                 LOWER
    8 X+ w, l$ p) W+ k! l3 d$ o9 H1              capitals( ]1 V# ?2 N1 N! @0 B1 i
    2    THIS IS A SENTENCE
    / T# |+ c5 d( q3              sWaPcAsE9 P; o' `: S- |
    dtype: object
    & {! q3 r8 [# v( v) m+ ~# h" l0 m' c
    s.str.casefold()  # 去除字符串中所有大小写区别% z) z% L, t8 Q) \& k) F! v
    ' H" S% k/ \5 f/ p" N
    0                 lower
    : U* K2 y5 u. U- ]- {1              capitals; l7 ~+ o2 o( i& d7 E: f
    2    this is a sentence
    4 B3 |0 g" u& ]3              swapcase7 S; X) p( f4 `- A, N& N/ {

    - x; P9 p! i  s* G$ L1- a7 Y/ S- s/ [3 h  P8 ~3 N
    2% X7 C- W2 {% b! r
    3
    - n6 Q+ T& V( X$ v, _4
    & w/ r0 I0 u$ X5 b5
    & ~# S0 s+ }5 _' V- e, m& S6( L7 O5 q0 A$ L. e, ?4 F( a
    73 |% G2 M- B% `
    8
    " A  |% U8 o1 D+ D# P9* u5 |2 f" I( m9 O! K
    100 V/ i' ~8 m. ~' @# [  @
    11
    ! q6 B$ I( }8 C. ]& I' L9 Y12) V$ u' U: q: M7 T
    13
    * w4 b8 ~$ N$ }2 ]14
    0 b6 F# U0 f0 P: w* s15' m/ V  f: K" X8 Z( s
    16* _+ A7 b" m9 P0 b* Y/ E
    17
    " t* G+ N, o- z) c18
    ! O1 ~$ x* D7 R& O  S" z1 D+ \" d198 k( e" k/ {* D9 v, N+ h8 ]
    20
    " C0 y& j6 E$ e/ r: @21; ~# Z2 f* u$ `+ j% h5 }
    22
    9 |. T  h7 g$ p5 h: O3 f23( c% e- F1 J3 w' n7 n: A
    24) H3 C% E. U+ b( q
    25" ]) W" y; c* m5 q' D8 V$ Y" G
    26" s6 X3 Q$ G4 O/ J5 a2 L
    275 G; S) \- m: s7 m. `6 ?
    28
    3 _8 e: T( f+ c+ S29
    ) k# o+ I4 v- ~* o; q; t$ r30+ [& M2 q8 r) O/ B
    31' O" o- w% \0 b9 r
    32
    ; }3 `) `. u# X7 z33
    + W9 U8 _1 |$ i  C  e  Z34$ S( i5 E+ X4 F2 V2 r2 c0 A8 }
    354 W+ b/ {: n2 e7 e
    361 `9 W4 z8 U& Y, @" k
    37
    $ j4 t7 H' E6 M+ P( G% B38- l2 g/ o/ F5 Q
    39
    " I; b5 g) V7 e3 ^% S40
    8 r$ B: D" Z1 I41
    1 W& B& L( C. p- i42
    ; P3 {  B3 ]9 `43
    ; {' f* T' O; \1 k44
    ! R: f* i' `$ E; Q1 ^8 I: X45) i0 f" C# @1 W) W% N( L$ A. V- o
    46
    : [6 ~! {( J1 ]$ I  z: w47
    3 ^' `% x. W5 |  H' A, m48
    0 e$ v# x* a  R6 o! ]" R  J8 N+ X8.4.2 数值型函数, N  S. {$ w; \7 [8 s
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
    ( S4 Q6 o+ h) d# c% \, _
    ) S8 r2 z; H4 i2 Cerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:
      d" t) k9 q: W- v& C2 Z7 Fraise:直接报错,默认选项* Z( e* q8 ?  @* o& S( i2 M6 y
    coerce:设为缺失值
    5 s* g3 m+ B9 H5 h$ Kignore:保持原来的字符串。
    , G; O) X7 Z( N/ cdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。% `6 {+ ~+ m1 Y7 ^' q
    s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])8 S$ M: p5 Y1 A
    # x6 q! J; ~6 d  H
    pd.to_numeric(s, errors='ignore')  o) |( A/ @) q1 r3 D5 I
    Out[93]:
    " ]* s5 N6 n" H0       1
    0 _5 N5 d! [3 {% l  B- ?6 @1     2.28 S7 f0 H- k0 L( K0 o# r3 f
    2      2e
    & B+ h5 t0 w9 M1 J  M' R3      ??) I9 `5 a& \  H! b
    4    -2.1
    : F8 F' N2 v  F5       05 z5 |+ Z* ~  Q5 g. R; Q/ I6 w
    dtype: object
    * [2 A9 T2 R; T+ ~. v- ^% F! v: Q3 W* b. F. K, T/ X
    pd.to_numeric(s, errors='coerce')
    , j2 o2 y4 g3 O: ^  D& I" ^# AOut[94]: 2 `2 t& P5 W4 W( v
    0    1.02 P0 p: {+ E. k6 c/ s/ d* v2 _
    1    2.2$ `) g1 \8 k1 E$ k
    2    NaN  G( t& Y7 t/ k) ?
    3    NaN9 G! F+ V, F/ z% }
    4   -2.1
    . C+ T7 x" T# m5    0.0
    9 j  _8 P7 ^3 Q* M+ h% zdtype: float64
    $ r. x. u- k5 G" G9 D* a
    3 a0 A: W% l0 E, \1& e# }4 `4 j0 u9 Z* S6 b7 h: V% L4 ~" H
    2
    - |& v) }" H: B2 B5 O9 v- f0 y3
    ; j: W0 H( V. R: A" f7 B. D45 i0 H. x6 e$ [. I+ o
    5
    % e# x3 c  a" e6
    ' ~) r; v6 Q) b$ J75 a  s+ x+ i) \; H4 ^. ]# t
    82 Y$ k9 m/ @6 ]; R; K8 M3 V5 P
    9
    9 k# h8 Y9 U( D6 Q7 B; F' r10$ _7 Z# E, k4 _6 H
    11
    ; o0 v1 W: z- w% }12
    8 a7 [/ t4 f* m; J1 F* d4 Y13
    . f% o+ H$ ^% X: v14+ D7 V2 `5 a& b; E* b7 C# ~
    15
    2 \" o. Y: X6 Z1 h- r2 \16
    5 Q6 `; R" [1 R6 O) w17* f5 ?: z: ~% `! v: ?
    186 K" f& q' i1 ]3 a2 _+ w
    19
    3 Y$ A# A9 n1 {" `' s20$ L1 Y9 r  `* _  q- i: z3 s
    21
    7 @9 Z, ~# `4 v: m) ?. h  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
    2 G. T+ v: j  P6 h. [+ t+ e4 T. e2 u/ a. f' p7 n1 `5 ~# Y7 Y
    s[pd.to_numeric(s, errors='coerce').isna()]
    6 S- ^- u- s7 ^1 w( [0 MOut[95]:
      Z9 X: k; L, X) Q4 G: O, N5 t2    2e; Z# a2 i/ D) I# a: S8 F( c
    3    ??: o* N3 s* k) @- m2 l
    dtype: object
    ' z. K3 E# O1 V  T8 d! m. x1 x1
      m  l0 Y: w0 G2, z$ h2 `8 V( Z" U- U1 O& `# P# M
    3
    2 N: j/ i$ @# N8 O4
    6 r- h6 R& d1 _6 {5# j$ [2 |5 P* g; v
    8.4.3 统计型函数# l% i0 }" a  m8 j3 h
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:
    ! l8 T2 c+ `( |8 e3 {0 Q
    6 Z$ R  ?- N: S% S5 n0 P( ^s = pd.Series(['cat rat fat at', 'get feed sheet heat'])
    8 G7 E; b5 C0 S" p0 v
    : c: |8 f3 r8 p; Bs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次
    - u* |6 G! Y+ fOut[97]:
    8 F- V# R" i# d% Y; n2 F0    2
    0 ]: Z* Q* r8 E8 S1    2
    , p; f9 ?6 o1 d- ~4 |7 w% }dtype: int64
    8 M* J* K( F6 e& g0 Z
    9 S5 D. B- i1 ~( ns.str.len()
    : p+ c2 x* S' @3 c& \Out[98]:
    , D" A# s1 W: C0 D  x! @! A0    14
    & {/ k& Z3 F3 ]9 m$ B$ v7 {1    194 K5 N6 a- }- U9 H. Z1 z& L* H
    dtype: int64( w( C) D! [: l$ v  F
    1
      ]' }  i; |% ]2
    . S0 Z  j* ?2 \, W$ h9 A0 n* _  U33 U5 ]5 L0 V' b* L/ j" |
    4& W* s2 e& x8 i# y& V
    5" k5 E6 k, s; g7 O" ?
    6
    % f. K2 k5 q( {6 ]" [& A7* A0 F+ m3 d5 ^( N' B. V  v& T
    85 b2 V- s8 C# C. A" ]* r
    9, ^  [6 r& T! L+ i, y. s
    10
    / [  p9 J* x& v  N/ E114 ]! B$ l! A; P5 ^* b
    12
    " Y7 c$ b) I- N9 ]$ q0 m0 `13
    2 W7 x* W! x: T2 D: s. Y$ p; a8.4.4 格式型函数- h4 r3 u- d* K" T& t
      格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。4 x8 F1 D$ ^# [; g8 Z; v% W9 _
    4 U& P! M) J) r* u) v* I! `# q( G
    my_index = pd.Index([' col1', 'col2 ', ' col3 '])
    ) O: J* n5 y8 g7 j! {+ k2 |9 y, Y7 ~2 M" f2 |) i: {
    my_index.str.strip().str.len()
    1 U: B. K0 L. y5 V  b3 K5 {Out[100]: Int64Index([4, 4, 4], dtype='int64')
    / E, f+ G% t) R  o  F+ s* z# i! j$ H' c0 K! n
    my_index.str.rstrip().str.len()7 d- g, K/ }6 g. d9 i  e
    Out[101]: Int64Index([5, 4, 5], dtype='int64')/ \5 I1 D3 O+ Y9 _2 m3 Y# F

    5 J+ {$ |# @) ?) C8 H& W9 f! n; rmy_index.str.lstrip().str.len()
    1 B, I/ z; V# M3 h' c: KOut[102]: Int64Index([4, 5, 5], dtype='int64')
    " s6 \* E& f; L' D( H" |1# @1 k; l" K" f: b" U
    2
    " C  D6 K0 e- w5 W* ^7 I  O2 F38 a5 |* d% S7 B/ Q, o
    42 S3 D; m9 ~" O
    5
    ( r7 c" ~$ N/ w69 i# c' N: ?( ^2 ]! D. f" Z. H( f
    7
      a0 e* z& ~* Z- q- T% i  c8! G$ A' w$ p! ]5 [7 F, ?/ D
    9
      k: o6 A2 f4 u; t5 u10, I0 g( a8 V3 C5 F
      对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:4 w( f6 i: [$ x6 M- D6 J
    9 f* I, L+ j) q2 m* A; G
    s = pd.Series(['a','b','c'])
    ' ?2 E! i; n' @6 |: z% }
    7 J& J; u0 E; m5 W9 S' es.str.pad(5,'left','*')3 O7 I, A: L" m  d
    Out[104]: 1 n" \6 p* y) w, M
    0    ****a4 {/ z* t4 I* Q# }+ C
    1    ****b& [4 G$ t4 c: \( z
    2    ****c3 ]3 k  P' R8 z0 [0 u! a
    dtype: object5 ?5 n. e7 ]0 a& Q% s& \
      F3 @" v/ v6 P+ ~) p9 C! x  ?
    s.str.pad(5,'right','*')
    0 _$ P0 K; n8 K  Z8 Y* U) c0 pOut[105]:
    - o7 h1 {1 p. k* q0    a****9 ^# F+ g2 M1 j( R
    1    b****/ A. e$ i' |% w0 d1 H8 w( i
    2    c****
    9 L, ^4 X' Y! b6 @& X# ~dtype: object
    : T+ ]( u& ^3 V& B' A+ k, X5 H
    s.str.pad(5,'both','*')
    9 E, I+ O3 o, @% o! {7 nOut[106]: * O# x" b/ E. S# X6 D1 T3 M
    0    **a**
    4 N+ L. h2 ^0 T7 w8 k1    **b**: j0 R2 l! S# f, H
    2    **c**
    . D0 s  R+ W9 U3 D4 Xdtype: object
    " r& c$ b% k/ L1 o( O. `/ q* r/ _( b1 Y5 B
    1
    6 l6 F( R! i4 r! L25 b& U# h0 ], f3 e2 R! w
    3: D# f/ a1 \1 h- O% u
    4
    2 Z* D8 b8 P$ i# N" J3 X4 e57 L  i) @0 j2 E" D4 Z/ g. i
    6
    0 T: b. S: F( a3 K2 h7* }5 S) A2 X% v& Z8 e' G( I
    8
    ( ?+ O2 r- d4 |5 f( I9 ]6 z' n$ ]9. k$ {. m. a5 M3 f+ _
    10
    # \9 C# M9 G2 g) @+ R11
    " b0 r) v: s2 b/ O4 H: a12( V4 T1 o9 ?/ b" Y" E
    138 Z& r- i! E$ r2 X) E' P
    14
    - j5 f* D: A, \# J15" K; X/ U1 ~$ A, Z/ L1 }( Q
    160 h1 n( I6 ]8 T" i
    17
    9 T7 B: a, E: F- {5 A% h18' _' y9 d+ [& g% }8 F4 _
    19
    ; k/ |& A  s! |20$ d2 x& b$ }8 Q$ Y$ j
    212 u2 ~. B5 O9 o( X
    227 [1 E' T8 U1 c1 G# G7 e2 `
      上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:* k$ \! a9 ]  B% h) t: C1 V
    ) A. P' p! s1 d1 L9 X9 H
    s.str.rjust(5, '*')2 j: z6 F2 ]2 [4 H, G
    Out[107]:
    % u. R  Z5 N5 c- l0    ****a
    8 T- q$ v' c  H$ g1 h1    ****b9 L% y5 M* V' y! z
    2    ****c
    $ k, c$ O1 S9 M. b7 Adtype: object7 ]0 g+ c1 n; c" [( H3 f9 Q3 N4 ^
    ) K# z1 f& f+ A! }$ m1 R3 P
    s.str.ljust(5, '*')4 M, G. i& ]! K0 v1 ?
    Out[108]: 6 ]: O( L8 G. S; H; r0 G' b" a4 v
    0    a****
    ' c! o' d# o. M3 {1    b****$ X- B5 f( ^5 l4 B9 x5 g# E
    2    c****7 y' R. |: c$ k8 q6 w
    dtype: object$ ~9 b& l( [* {$ G

    / n! J7 U$ v" I' N) D4 U/ i* ds.str.center(5, '*')  f3 l; _' H6 U
    Out[109]: 9 d8 `3 @3 U8 f& \) F
    0    **a**
    5 h7 G6 D  h  _1 R: c7 |3 n1    **b**
    2 n; f2 V, J  t& K# {2 g$ k' a$ ^. A2    **c**
    2 X' C9 s. m* U" }$ E! \/ Pdtype: object
    3 i/ P! y) |# K' o6 W0 y0 O* j( }0 e0 @  N! Y2 S3 E6 ]
    16 E/ B1 B! S( D" N! {# n' ?
    23 Y( r" t. d& W% z$ C
    35 J9 _# A' C7 W- L: X3 ^- r' D0 E5 L
    45 h' x9 A0 n! a- ]' ^( `( ^, W7 F
    5& R, k5 T4 p7 K5 K. K6 A
    69 h9 R. ^( k2 H6 }8 }' A
    7
    5 \9 R$ K' ]3 l- T6 Y8
    & f  ?6 p' M0 c( B7 M! N9
    2 }& z: J! [: y) j10
    0 n+ m% V7 M8 O: D& z11$ D3 O3 D  h- p- g0 q% q1 Q+ Q% q
    12
    / f. \+ [" g- I5 n# [. q  \7 f; o: g131 M5 d+ p1 l7 I4 i
    14
    6 H0 ^; x! G; f) y159 U/ Z0 x! k2 u; y
    16
    $ z' c8 K* S, i4 n17
    1 S( ?8 e  }/ c1 w: h. B18
    % P% m6 I. A7 u; u" T; |19+ ]/ o9 u' h+ s6 i6 R1 s" F; q7 N
    20
    , `# `) {- m' r5 W  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
    2 w8 H" c) B' b1 l) P6 Z( x& C3 `' J& X
    s = pd.Series([7, 155, 303000]).astype('string')+ U2 z8 w0 ?+ i, Q

    . y1 ?6 O: w7 o, l0 U: z# i& p& Ds.str.pad(6,'left','0')
    # J) Z8 H9 J% k6 J$ MOut[111]:
    7 m8 J9 D- p  s' S& L0 {0    000007
      R9 o( D! R, ^; \# G( n2 T6 C2 m8 |1    000155
    - z! A+ K( i+ B2    3030009 t* X. c2 g, [6 ?+ J  x5 R  x& s
    dtype: string! Y3 t2 R. g+ [' R+ n
    % {4 k3 E$ A! |% I
    s.str.rjust(6,'0')
    ! T, |3 y! B0 s3 X- zOut[112]:
    " d. N; L9 X7 G0    000007
    . q9 O! c+ c* S: M, |% t1    000155
    . e0 X' @$ Y0 V+ F2    303000! u% D' T& o6 j) F
    dtype: string/ N4 L- k) _* ^/ _. w
    " a6 w2 j0 U. m* G3 f/ j) ?" c: J% d! [4 s
    s.str.zfill(6)" Y3 s2 z/ r% F2 M; P
    Out[113]:
    * r9 M8 K3 l2 L: f: z- j0    000007# f5 _7 ]; T" d& B4 I7 {
    1    0001559 d1 b+ y* w/ ^% ]6 I
    2    303000, v6 R6 z6 E# {) @; g- N
    dtype: string
    7 h+ S: F$ r* S
    . v0 J* h2 w5 Y! W0 k10 ?0 F5 r' x4 Q5 c% m0 @
    2
    ! n* ?) Y5 P- n" D8 ^8 R3 ?3
    1 B/ l% v, ?/ q5 w' K46 `4 W' {; E  q8 R- `! [$ I
    5
    ' n8 D6 u( r3 d& E; V6 l62 w, X$ [8 B7 q* z7 ~
    7
    " Z! _# O2 D8 {8
    7 t4 s* I2 j3 A! o5 o. ?3 C9
    / X6 [0 @3 q* \5 ^( U5 ]: K, s102 [) Q/ \* A" t2 ^+ F% G
    112 F3 d# s: g1 q& x9 ?
    12. `) u9 x" l3 F/ M( U( \4 F. S
    13
    0 Y* J; A5 G$ B+ S14
    ' _! C3 k. f2 M# B0 u+ \15
    8 [( ~; k3 w/ |+ e" S, B16: j- U# T8 q' w- H- m
    170 X' T) q) r% z! P- X- ^
    18! F: I* t# |! f  b" e) s
    19
    / Y( v9 C* C: x/ Z# H20
    ; D* P$ A5 ^6 g$ I2 t, C211 S/ E  D7 N+ K4 J& {
    222 P0 P8 y( D0 `4 \
    8.5 练习; G( Y9 K3 T8 V  s
    Ex1:房屋信息数据集! C& |/ w! ]! Q; ~7 ]. k5 t4 T+ E
    现有一份房屋信息数据集如下:
    7 m: V% w9 I7 L$ |! y* @/ e  |" e( N
    4 w4 V3 x, b. pdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
    . m5 Q2 Q$ c5 q, {4 m8 x6 Xdf.head(3)  m$ k/ x) N( a& ~/ a
    Out[115]: % P# K; R; k$ h8 S* s
          floor    year    area price# E1 ~9 [4 C) c4 z7 w
    0   高层(共6层)  1986年建  58.23㎡  155万" [1 o" r8 h$ D4 z6 S, n$ B
    1  中层(共20层)  2020年建     88㎡  155万* `6 g9 j+ x4 p# ]1 A) _9 Y8 ~
    2  低层(共28层)  2010年建  89.33㎡  365万
    5 R2 V4 J4 y* {( D0 }- I1
    ; }/ S  W$ F2 u& m3 p22 b$ |! u- y8 I( u
    3, w/ D6 s$ H" G6 u6 d% Q2 I: x
    4
    1 b2 q# ~: U) P! j! F51 Q( ^3 d( p/ Q, [
    6
    ' z6 @- _/ v. U3 `' \8 w% U. Q7$ x$ f) r; l9 d) v* z: w8 v0 j
    将year列改为整数年份存储。
    # T9 ~) p& p8 t  \: w& v1 B6 R将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。, o$ J5 p* u$ B; h4 w
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数: e" D2 D; b( U. _& ^  f' W
    将year列改为整数年份存储。2 w7 @" |0 j' p( o: r
    """# q5 ~4 u) C  f# Z. Q+ B# P
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
    1 ]: p5 g1 {0 A: A注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,) T+ N6 c* @3 w/ e
    转成int后,序列还有缺失值所以,还是变成了object。+ \( d  S4 L. T: u/ E' a
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
    - f# @& K( u6 Q" S4 `- N0 F" u$ H"""
    : l  `( ]; m6 P, [' P$ mdf = df.convert_dtypes()
    - T) r# t0 M" x8 O: C4 Gdf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')4 Q5 b6 x, Y; e& S* ~: ]
    df.loc[df.year.notna()]['year'].head()4 t3 m4 P) t5 \' A% P( @8 T
    9 ?: T% n6 Z- ?1 Y! W* q2 w
    0        1986
    9 @5 l8 k8 x& S1        2020
    ) ]& M- `1 I' k0 x1 ]2        2010
    6 [( Q- i! k: b3        2014
    " W' L( ^" v; ~4        2015$ S1 o4 w1 k8 c" M2 p
    Name: year, Length: 12850, dtype: Int64
    ' }$ H! N9 \; ^+ M7 c! n0 o, \+ q& L3 o
    1. [2 Q7 Y1 N" h/ p
    2
    . i3 h" T/ z1 z0 j8 C* W, E3. i, q& S1 V' s- Z
    4
    6 i+ D# x9 K8 \/ `' M58 Q' @5 I1 _' I& X4 [% b
    6
    8 U. c1 w8 m1 f, q8 e" a7- h7 r$ a1 X. }" t1 {( A
    8
    - ^' _# }& ^* H- @  B# @: O9
    9 a: i" k1 N! L4 z( b+ `4 U10
    7 {* \% `; o% z$ V( f11  w( M9 r3 n% C+ v. u5 G4 L
    120 K* e7 h4 G+ N; X1 w3 Q& F
    13
    ; }9 z' d! [5 _) u7 c' e14
    / ^6 ?7 s4 S0 A! R150 ^6 E) h7 N, m1 N* L& j
    16
    : e3 y3 }2 G! V9 E' |, ~# \- O! E' x参考答案:
    ' r: B& \4 u& h, v' q- D$ [
    + @9 O& d" G* V# w不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
    * N2 K. \1 z2 q$ p3 j
    + J  d# t% [. w- T2 E% e* d% Tdf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    ! ^$ w& R" O; c( Y/ Ldf.loc[df.year.notna()]['year']
    ( l9 x5 g! S3 R7 E# v# e0 u0 a; X4 O1
    % K' \( q3 X) s- \$ K: ~6 o2
    1 \- t$ D. h0 e. K- O: ~6 Q3 C将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。9 ~& e% }9 b, e5 Y! \1 V
    pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
    / V: c, a$ K( f/ ~0 s( q/ Gdf2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
    * n6 C+ ?1 E. m' x  e  Qdf=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型8 y6 S. [6 u6 \* Y# k
    df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
    " |+ Z" V* _4 @1 F* x5 G/ adf=df[['Level','Highest','year','area','price']]
    5 a! [8 c0 \! k, H" T  ]6 ?) udf.head()2 D* n( f8 S, v. ]& q% J1 V4 c

      M7 N2 v" j; `/ h+ P/ i   Level  Highest        year        area        price
    1 @4 U0 ~6 \2 j0        高层                6                1986        58.23㎡        155万1 b6 i7 d. W6 T: m
    1        中层                20                2020        88㎡        155万  M& r. ~3 u( a8 T: f7 Z" M9 P9 j
    2        低层                28                2010        89.33㎡        365万
    7 U8 d  M6 \4 c. ?7 A3        低层                20                2014        82㎡        308万
    ) o) @* @5 F+ S9 D3 G9 u4        高层                1                2015        98㎡        117万
    ( u& y' ^7 X' N. d; U7 m1
    5 g; Y  T& P4 F& ~, B$ n# M1 K- C" j2
    7 k4 i& G9 Q, S2 n5 V% d' Q36 l8 w  Q/ X- j  F/ X' d' L. l# c
    4+ R& \. U3 U% N% e" b3 @
    50 m3 w6 }6 H, j& B6 q
    62 Q6 c9 ~: o- X( R6 |- |" r
    7+ l" Z& _# U, S
    86 U& A1 ]( d9 d
    98 i* ~6 j/ v6 f9 n" U8 x* C
    10" r* c( l1 N% [4 g. j
    11
    $ A  Z$ T0 c9 Q' m/ d0 L12
    " e% q' W) T. J1 x: s2 x/ D* T13& ?& [* Y" f$ N4 t- W" {& r0 l
    # 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了2 N$ M- x1 |5 ]" Y
    pat = '(\w层)(共(\d+)层)'
    ) @! k8 T0 l7 m9 e9 ~new_cols = df.floor.str.extract(pat).rename(4 H) [6 N: a% Q, d) Y8 R7 n( r3 Q
                        columns={0:'Level', 1:'Highest'})
    1 [+ F/ u  R4 d8 y% A/ z3 T+ k" y8 i0 [
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)- S9 L. y$ x$ z: ~6 A. b: o+ _
    df.head(3)
    : |% D) ]6 J2 B" T. @
    + h, U. P/ W+ Q  R4 d/ jOut[163]: / L& k" w. v4 G7 L, D  \2 g% k
       year    area price    Level Highest
    ; X* n9 T) w: s  c0  1986  58.23㎡  155万    高层       6! V/ F3 \. t, b3 p0 p+ }
    1  2020     88㎡  155万    中层      20$ e( N& w7 `3 d7 }+ \
    2  2010  89.33㎡  365万    低层      281 r7 H" q4 L+ `- J+ U* x
    11 g) u, b. G4 n. E% `6 V
    26 j+ ]$ L+ R& C$ O6 u) t
    33 H; M! ^! U8 T" d+ X- \
    4
    * a" ?* U$ [# N; j! c9 o5) @; H+ `! d/ w
    6
    5 G1 ]$ H2 n+ Q& T4 Y) w75 O, R$ r, q  G7 s' x
    84 J+ u. A; v( P; r0 E9 h
    9* E9 p3 C# _/ G' b4 i2 j
    10% e) u2 f( t- ~: |% q1 L; e9 m
    11+ E+ ~1 D4 c2 }& u9 k2 |
    12
    4 J+ a$ s4 [* B( p2 }. M13
    3 w+ R3 \. b" z$ u) B' U/ `计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。1 S  A8 a% a: M
    """
    4 {. Z6 ^& G" f; F. j0 P3 Nstr.findall返回的结果都是列表,只能用apply取值去掉列表形式$ }3 U4 v6 v4 _  P; d
    参考答案用pd.to_numeric(df.area.str[:-1])更简洁# S9 O. A; D$ P+ h, |; L8 A
    由于area和price都没有缺失值,所以可以直接转类型+ O( J7 L0 d4 v5 P7 y' L- _" o
    """8 q# ]* ]) M* Q- Q
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
      N: _  D6 F2 g4 j' bdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')% A. o, e$ a$ n6 J( S+ L, ~
    df.eval('avg_price=10000*new_price/new_area',inplace=True)
    1 n1 ]. j; m% C" j$ T$ Z% q. S+ F# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))0 S; c: v+ o" \' O
    # 最后数字+元/平米写法更简单
    * d+ ?' o! T5 ~8 f' u  N! M$ Zdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
    , o& X5 w# u( ?: v; hdel df['new_area'],df['new_price']
    + }' u# J* [. q2 S( tdf.head()
      N. K( ]7 K# W7 q0 z- B5 y
    9 N, ~+ E9 c' E6 i   Level        Highest        year        area        price        avg_price  v+ p/ D) b% R1 h1 D* U
    0        高层                        6        1986        58.23㎡        155万        26618元/平米( m, O6 }8 R1 ?* ?! b
    1        中层                        20        2020        88㎡        155万        17613元/平米
    & \6 H0 R0 [1 D7 p2 _/ l2        低层                        28        2010        89.33㎡        365万        40859元/平米4 R$ p' F( U- |% j! \6 r
    3        低层                        20        2014        82㎡        308万        37560元/平米
    3 s. \7 X4 W, y4 O( O. l& b7 r7 Z4        高层                        1        2015        98㎡        117万        11938元/平米8 }! @" K! ?' _" T) A1 `1 `3 _
    ' f8 @- `6 _: F5 O5 y* j0 T+ ?
    1
    7 z4 D+ ~6 b" f7 K( f* b8 ?2
    , ?! T9 ]+ l4 q2 a) |3$ M. h2 ]$ B7 n
    4/ U8 b6 P: `, G8 H3 @
    59 p; |) n0 n% Z+ d
    6
    ( C- b) Y5 X; P% J% k7
    ! v3 v; O2 h2 a9 }( M6 {8 V8
    ' [5 x3 `) N8 |; o* Z, I91 [. v* k& q! |( \
    10
    # e( @0 v  {! A* j114 p6 e9 y! w) H6 F/ p$ z1 H: h/ m# ~0 T
    126 r* ?! p8 `* o6 D8 \8 `
    13% {0 T- ~" V1 N
    145 P+ @$ p( s2 f
    15& m8 L# c" l& ]  u# D7 u) E4 E
    16! q' j) i: c: O% O/ ~9 i
    17- r7 y0 g7 x" z8 j  N0 A% f
    18
    5 j' X/ r) O1 O19) s2 O; S+ u7 D& h
    20
    9 L3 s7 ?. h' v- \# x  E# 参考答案
    7 k( @( r% ?! `+ Rs_area = pd.to_numeric(df.area.str[:-1])+ G& v/ S, k% e2 D& h- ~2 z3 I
    s_price = pd.to_numeric(df.price.str[:-1])
    ( `, s' i% r4 E6 S, \df['avg_price'] = ((s_price/s_area)*10000).astype(
    5 h% S7 h8 y4 q7 l4 @# t                    'int').astype('string') + '元/平米'4 y: p0 h' A) X
    ( [- E$ h: V8 b# G1 ^
    df.head(3)1 ]$ U1 i) l) b1 A6 X7 w7 A
    Out[167]:
    & T9 s5 w0 [4 s' h4 U0 w  p- Q" i+ J7 I   year    area   price   Level Highest  avg_price
    # O5 h4 K8 ?1 h- g- {$ K0  1986  58.23㎡  155万    高层     6          26618元/平米
    ) [) E& M% n) J# n- ~1  2020     88㎡  155万    中层     20          17613元/平米
    9 T9 u2 R0 s" D, u/ I' y2  2010  89.33㎡  365万    低层     28          40859元/平米" S. u: \: O# ~5 \% C, O
    15 z9 Q! c4 f, M
    2
    * I9 O' v3 L3 e& \; C+ e8 m31 w  \& y- E* h2 s
    4
    $ S* u& }' R- a6 k- z5
    . u9 B7 r- o; |% l4 }1 Z6) ~! ]4 T0 u: y& V3 a6 R' J# c# a
    7
    2 [( p2 ^- p0 R) S5 v; l* F! S8
    " {" H- g; c+ e/ U, @0 N9# j, T$ K- B9 j' G, ~3 r8 R
    105 V2 y. p. u; T( X
    118 V7 F) v: I/ L. z7 A
    12
    7 y* o3 X  x, c& r/ ?Ex2:《权力的游戏》剧本数据集. J! Y7 ^! t, h3 a
    现有一份权力的游戏剧本数据集如下:
    8 S  e" _( E( q& M0 I  c5 e6 c' R8 H, ~# g/ \" i7 f' u- ~
    df = pd.read_csv('../data/script.csv')
    1 P$ ^+ u, Q: ]9 p6 r7 o1 gdf.head(3)! Y$ v2 P" F9 J- B7 {7 J% |& d

    & l9 l! ~+ i5 Y( e; A. @. dOut[115]:
    " F/ [9 N6 c/ ^8 P  COut[117]: ' o$ j- e3 h; u# o
      Release Date    Season   Episode      Episode Title          Name                                           Sentence7 b. P$ ?( w5 _
    0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    2 n  ]' o/ p, D! H1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...
    5 @8 S! X/ j( n3 E. Z! l- ^; O. x2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce 9 X$ \; {: A, Y9 M
    1
      \* h( W  Q5 b, v! W! {27 Q5 h+ @2 P" T  D6 }
    3
    5 L- U6 h$ l% U4# A4 R2 z6 h( L: x( o* ?
    5
    & c/ m% z  A! C5 o62 ?4 S, \7 m+ f9 |
    7+ x9 E- v& F' z. g- j
    84 {! O% z# H4 O& A# i
    9
    $ C# \5 X" ^/ O% T1 q计算每一个Episode的台词条数。
      E2 X0 F; t! z& J( B2 a# Y- A7 @8 r以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。! e- R/ Z# `2 w3 s4 k8 u. I/ Y
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    4 A* \! K, f5 ?6 z2 ^7 J' u计算每一个Episode的台词条数。+ ~! `  n1 z$ }6 z2 g, |6 z; `3 u
    df.columns =df.columns.str.strip() #  列名中有空格! _! u( _. w1 ^% }( C; ]! ]1 J: d7 \
    df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
    7 s# P" ]8 S5 o+ Q, k) a
    9 X, c  b6 b$ L; |8 D( w. ^- ?season    Episode  
    # V8 J" ]+ n% C2 a; eSeason 7  Episode 5    505* A: a2 ]) Z1 P% w9 w1 |2 N
    Season 3  Episode 2    480& H: H4 C( y1 a7 {" E
    Season 4  Episode 1    475
    0 e+ A3 O" g3 ]5 x' l1 vSeason 3  Episode 5    440
    5 ~' l4 B+ S7 G" W( CSeason 2  Episode 2    432
    ) |7 n' }# b4 ~1 l5 E/ `" n1
    & A$ p8 y% W# [' ~2% w3 \1 ^3 K- W4 R
    3
    2 X0 X4 ?+ C- U( Y% H% N( R4 L4
    " ~" u+ B! @, |9 ]57 {# b; }3 M( C4 H. Z
    67 Z, q) U2 Q  J1 l: z0 {: \( w( k
    7
    0 w  V# o. j9 h' G' f7 X6 W2 [8
    ! _* }5 P% K# E  n! z* R9
    % L  i9 {% h# E7 o以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。% Z1 [) T+ `6 u4 w' d0 }2 _1 {
    # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
    ) [5 w, l% ^2 w# j! edf['len_words']=df['Sentence'].str.count(r' ')+11 I" k( c8 }& _" e
    df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head(); g% G/ o. `# K$ S/ }  u, a8 m

    $ B8 n0 ]- T0 |& Q) g7 x3 s# ~Name
    , g$ K6 j3 i% ~6 {8 Nmale singer          109.0000007 ]9 N  j! {- }6 T4 t8 \
    slave owner           77.000000$ `7 o# d5 \( v; O* @
    manderly              62.000000( @; f0 d4 B; |: K% c+ Q
    lollys stokeworth     62.000000
    , N# F" i$ D1 jdothraki matron       56.666667
    1 Y8 Z1 r7 R2 E# D6 J2 A& u  mName: len_words, dtype: float64
    , d/ E7 Z8 \4 I, C1 I4 S1( I9 P4 x: G  k
    29 l/ J6 z  i& A  k
    3
    3 |+ l' K, @: d) _3 J# [6 s3 a4, z) L. ?# v0 b5 t6 ?5 [8 ]
    5
    5 Z- G* N; A$ l/ X7 I+ t: |5 ?6
    ) l* t1 `5 O, s: A, ]5 ^  j# n7+ |4 R9 m; Q6 f8 }& ~; U# n' W2 x/ w
    8
    2 i. n- E/ U! Y8 T6 k/ F9/ n) R* b8 R* e3 h5 n: o  Z# p" |  \
    10
    # }9 x4 ?. h! ^3 N11
    4 Z9 a/ j0 M, h- l2 A# V若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。0 i1 y( c4 o  s) b
    df['Sentence'].str.count(r'\?') #  计算每人提问数$ b- ?: o7 S% R7 Z: `7 z7 `* k& y
    ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0, C; Q$ F! y9 M$ M; Z
    del ls[23911] # 末行删去
    ; ?0 Z. M5 m1 h6 U# X9 Ddf['len_questions']=ls" t3 C% S3 n( h: x1 F) H% v
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()# z4 v: f9 }6 A$ D" C: e
    4 R# C* K$ }. o) s
    Name
    6 f7 j! a" g/ {tyrion lannister    527
    3 n! C3 V5 z/ X) a$ b9 L# l4 rjon snow            374
    : Q2 ]% v1 x& Q2 E/ w$ \# Y- ^3 ^jaime lannister     283
    2 Z2 t2 L& p7 y! ]( Xarya stark          265
    * {0 ]" i; [. N) E7 G1 X- e2 Jcersei lannister    246
    7 k6 m' ~/ w# }0 K$ ^- pName: len_questions, dtype: int64* X: Q1 H. J4 O; u) U9 u, q0 }$ g

    9 }4 [: ~  x- z; q8 [3 q7 C+ j+ b3 k9 [# 参考答案
    + ?4 |6 O9 f4 U  C2 {3 fs = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
    1 G# W' t: i# e/ J4 R" P. a* Ss.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
    6 b: e9 F) d3 R. \+ T+ x1 X1 M4 M2 `! H
    10 _* Q& u2 A0 E! N/ g2 h: d
    2
    1 e3 T" i4 E/ j1 R" K: e32 a7 X; q. A$ s! ~, t# ?6 Q* R
    4
    ) h6 ?% u: }+ M; |5
    3 M) E' z1 {  o0 n: u$ w9 y6& m- F; h0 b2 j1 P7 `/ X' x
    7: d4 I3 x# r7 w9 I# U& p# u- H
    8
    % v' c8 Z3 f  {1 c9
    + f! k" V0 p# z& J2 a  _4 H108 z5 }4 r  r( i  C, w1 Z* w: E
    11
    5 ~0 s- C# [9 k+ m5 o12& n, `+ \, H7 }  L
    13
    * i+ N- \4 k9 o2 D14
    * n( V1 Z8 o& a" l0 C# K15+ k8 l0 f9 A* ^+ w& e
    16
    " t0 K- s8 n: Z4 `) F17' C  v0 N8 ?. Q9 o2 F( j% U3 }
    第九章 分类数据
    4 |+ Y* @: p8 W" \, t; fimport numpy as np, V' J( \1 v5 J: L5 g. r. Z
    import pandas as pd
    / N7 E* ]( _4 f6 F1" U" ?& Y6 E, h" @! y7 r& w
    2, C& ?: f' r9 S
    9.1 cat对象
    5 M( C& r& v% R% i  ]; ~  s2 S. U9.1.1 cat对象的属性
    + o* R+ v2 z' d$ C+ f  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。( c5 G; U+ n; Y1 [6 W' x

    4 D# j4 I3 ?4 E, Odf = pd.read_csv('data/learn_pandas.csv',1 u2 C# Q; H* T6 Q' A# j
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    , J" c/ G1 q4 H2 N  U2 Ts = df.Grade.astype('category'), M& L8 q0 c+ z0 E9 c2 v# X5 w
    ' L" P3 x) |) [) Y7 P' U/ R$ p
    s.head()4 H& f+ X4 e) U! s  k' v* V
    Out[5]:
    / B1 Z" C  a2 q, F) m0     Freshman8 j6 b6 |- K, X7 n) n( f
    1     Freshman
    , [9 l1 `+ K+ W, x! ?  _4 h  _* n( _2       Senior2 |! ?1 ?9 V# T; i4 `9 v6 D
    3    Sophomore
    ) N5 q* l& s  g  [) Y* Q# g- l4    Sophomore: X# j" ~. R) s
    Name: Grade, dtype: category
    . _8 z1 e2 C4 t) d8 L7 Q! a; @Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']; V; i) U; n" O! i
    1
    ; ~; B9 |" S' M8 }) O2
    ! d1 m! _2 i* |. N; c& I1 N3
    2 w- F+ @6 t& C* Y7 a40 Y! n: I6 I/ x( S# U7 j3 ]/ W& t
    5
    ! ^/ @. |, L2 s8 \: D! v0 _! T6. I  h7 K! E* f' S, Z, E
    7& O/ U6 `! O' s6 z( f9 a, D3 i
    8
    4 s0 R/ L( T( C; }4 o1 b  H, o; [97 P; G" Q: ?) @
    108 X. h+ a* q" T" ?0 z/ C9 j2 w
    11
    ) k4 D) a  @# N, C0 j12
    2 L0 J# D4 }( P+ E# G9 z1 ]' r! ?* U, l134 C- k$ H$ |) a/ X* p/ p
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
    ! H% S3 g- y3 J
    4 A0 g- Q6 k. O8 ss.cat
    / S" [/ v: t6 S) G6 E! g. oOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>) z' n, y* m: F/ Z
    1* ^1 h# s6 R  a: T+ n9 R
    2' h) F4 M* h+ y& a9 F
    cat的属性:
    5 Z/ D: ~* n. |9 V
    4 O' c2 r" _. i  I$ ecat.categories:查看类别的本身,它以Index类型存储
    6 f" d9 s, X0 ]5 G* _- Ccat.ordered:类别是否有序" f, t3 Z  }' s, r
    cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序% K& G/ F4 K9 Y- f+ n
    s.cat.categories
      v/ B* m& y: `, MOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
    2 D' e) W+ \( i: X8 s# ]7 u- O" s" t& p! J, L$ u) m6 Y/ p7 O' Q1 ]
    s.cat.ordered
    ' S3 _# F' ]/ n5 r; `1 `Out[8]: False
    % Z# t" D8 O1 l" ^" b1 g0 ]# x* t
    s.cat.codes.head()/ q2 B! q2 |+ [! C% K
    Out[9]:
    : o1 d9 j+ z3 |6 H. X0    0
    3 |$ q8 T/ w3 z/ P/ `. e1    0
      o  y+ T" I* r8 w/ ~0 Q' b4 l2    2
    2 p. e, r" A' o; b' d% L. F3    3- W, e8 N) [3 |, K) O2 U: H5 I
    4    3" [6 _7 n* ^4 H) s
    dtype: int80 T( Q, A7 V* i0 q- M" ?8 @; G
    1
    9 |6 {& w7 o3 n2
    # Q) h! Y# S& W: \/ C3
      Q+ Y# V8 `0 V6 ]1 @5 M9 @0 I* q4; m  ]( g( }8 Q% ?* g
    5
    : h5 F9 u: B5 u6
    1 `& P0 ?2 h$ t0 Q3 f3 j4 d76 h& p/ h9 V# K# s5 g8 O9 V6 {# H
    8  ~" Q5 I$ m5 P
    97 B8 Z/ j( d# d- u& @6 C, Q$ o
    10
    7 t) K5 [$ d; a; S11
    + P# Z4 u  H( K12
    . |% |6 V: h1 |! z% z. c9 q13
    & ?5 r# w/ j# x14
    3 H- P7 h2 c* ~" r. d- f+ h9.1.2 类别的增加、删除和修改. p* m" h0 W; [7 g" ~
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
    0 c4 r, L7 w' `+ Z# L1 l; u4 u- B; a* W
    【NOTE】类别不得直接修改
    : O  u) ~6 f3 ^2 [在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
    + ~* d* Q- z2 [1 l- L( v" p, k$ J, U7 G
    add_categories:增加类别% Y4 c' R6 K; i6 F8 }7 c7 v
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
    ( K% x/ {6 j& Y/ Js.cat.categories. Y; C# F. {; }- `

      r9 R9 F  `  E3 l' A  hIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    * T, k( B7 f- q6 F! M1
    # W. s' p% |" ]4 j2 a; T2
    % r# y! j+ _: Z( ?6 l; D4 ~" p3" b( X0 ~* F% t$ v
    4
      X8 ]8 d6 e9 o3 \) [, h8 ^remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
    ) ^. Z: H' v% j; A. ]% [s = s.cat.remove_categories('Freshman')7 ^) p0 l7 S* _9 P

    8 E: d0 i- P  ~% Ps.cat.categories$ p2 }- H/ M- W4 w# n+ B
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')( j: f. p4 [8 m" q( n  Y4 [, {1 m

    5 t; q$ j/ V1 H& M) U0 [9 Qs.head()
    : _# t# z% C1 w. POut[14]:
    9 Y% ^3 s8 r6 g) J# ]0          NaN4 A% t3 {, d/ E' ]- e4 d" c
    1          NaN
    ' u( i+ O+ I1 z0 ^- z2       Senior; ]! d7 K* K! U
    3    Sophomore
    2 J% ^+ [, {* `$ C4    Sophomore
    * l) }9 j1 A0 IName: Grade, dtype: category  ]+ y) x2 M+ V7 N# j2 L6 u
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']+ L/ [. N; N6 z6 `& ]7 J
    1! m/ C- s$ E* E# y+ ]
    2+ Y: ^! w7 _3 S' _5 Y" d- \
    3
    9 z) q) A$ i2 y9 i' p7 Q5 x3 |! }4 h4
    1 a1 t5 ]! l3 m  G5
    3 M$ F) w- k/ ^/ f/ w64 `9 I- D% ]2 m) f: E' G5 y4 s& |+ R
    7$ O. i# ?5 X5 _3 \' Y+ x; W
    8
    , I) C8 W2 Z+ `% M; m7 o9
    : G( f7 s: j% P9 o5 g- g108 K5 ?' a7 G$ ^& v+ [& h; w
    11
    & R! M3 |- |$ y4 E5 B& j1 B126 k. v# g9 Q$ H
    133 o1 u# }7 @/ {2 M/ b
    14: s* }5 Y, F0 n3 z
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
    % ?% D) K* i+ R2 P2 J4 E( F6 `. Es = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
    * `4 w7 W8 f* h  c: fs.cat.categories) f6 s) I( j8 b0 w4 _! t
    Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
    2 z1 J; [- h# C' K0 a. w. u& j8 n- k# x: K; U; {2 r+ ?
    s.head()3 U/ R4 w  @3 g9 b
    Out[17]:
    " X% @+ _+ J& r0          NaN6 Q- |. T4 U2 Y. U5 D* ?) J
    1          NaN
    4 D: R/ z# a5 {0 w- {2          NaN. _7 g$ W: I' B
    3    Sophomore
    + Z5 V2 p$ d( H0 X4    Sophomore5 y- ^! v7 `, F* f- }
    Name: Grade, dtype: category
    " s( r+ E( T! s" h6 w( fCategories (2, object): ['Sophomore', 'PhD']( h# |9 E; f, T! y" g: C# t, |
    16 q7 T$ e3 i3 _0 E( J1 \
    2% x0 U& T$ C/ z5 ^  d
    3
    4 h  O5 [9 n/ j% z0 }3 v42 x& s; L9 i0 t! [# e
    53 h! K7 r; G0 D
    6
    9 b  y# X3 C; Y* {4 I76 j" U9 y$ P, [# q: w# |
    87 R- z$ P1 ~9 j
    96 f2 v: c7 I/ ^  o; }, c* N7 n9 ^' r
    108 F$ ?: D- c7 N% A; C
    11- R5 h' {: N- P! d7 ~0 K
    12
    - j/ |! B0 h6 S2 y. ?) O13: P8 _0 F: [! S; m% l1 c: X
    remove_unused_categories:删除未出现在序列中的类别
    ; B1 n: ^2 T; n* B/ u0 P6 w  _s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别2 X, [& U% A' o) |
    s.cat.categories
    ; |9 C& t; G% t
    0 N" e; g, l1 f5 A3 b' R* DIndex(['Sophomore'], dtype='object')& Q: a1 \2 \1 f; \) k& ]
    1
      v1 A% T# b' S: P) A& A& g* a. B, T2
    ; A, t" d& ~7 P- s& z0 G3 e36 ]# Q: v. U; ?4 _: C( d
    4( f9 j! ]0 |$ y9 U( t9 ~
    rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
    / ?- _3 o) ?1 X! T/ N! Is = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
    $ U3 I* ~9 ?% o8 {s.head()4 ~( R  Q$ Q% t% G/ O5 s5 d

    ' W# @# Z% V" L9 `( j& W0        NaN
    7 E9 e1 W% I! @0 @' X1        NaN/ m- \1 j- U1 n" @. p) t% x
    2        NaN
    ( Z9 C3 u- J2 g" F0 F+ T! T- }1 o& C4 R3    本科二年级学生
    4 Y' U/ a5 _2 b+ _' F4 M% \4    本科二年级学生) i; ?  ?. d  c
    Name: Grade, dtype: category1 E, X/ _1 }! D
    Categories (1, object): ['本科二年级学生']" l3 Z7 v3 ^/ j+ x: v2 H3 Q
    1
    5 N. t( g. S' o; ?4 m' U; s9 Y2- U4 [4 ]9 L5 g* [1 ^  y9 l* p
    3
    8 y6 p0 j0 o( Q) V% s' C49 W& h4 p% f+ D6 B1 p1 a
    5$ M$ Z6 C4 f+ M- C3 j
    6
    - R+ G- T9 J; m, d0 w/ M" `7$ k% z0 ^8 e' j- u
    8* Z1 P$ ~. Z3 U% r
    9( T' C: v4 y: v8 ]. \
    10
    2 p( E% I8 |3 Q+ n, v* m& A9.2 有序分类
    : c/ F* I/ M& q  c9 m9.2.1 序的建立
    5 X0 T$ @) k0 F; V' h  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:# @* z$ Y! Q" y8 e

    2 Y% Q; `. n, Zs = df.Grade.astype('category')& I/ E/ `4 W, [$ r, J( d, W8 w2 J
    s = s.cat.reorder_categories(['Freshman', 'Sophomore',2 e; h- K* c0 R" y
                                  'Junior', 'Senior'],ordered=True)
    ' r1 x* q, y, o& I/ j8 m$ Cs.head()0 C4 _. P% f& V3 k7 F1 [# B
    Out[24]:
    ; u$ `' X( _9 G1 ]0     Freshman9 W; J0 j" t" {
    1     Freshman0 B  c4 `. Z& ~4 b3 z' h
    2       Senior8 q% W* W+ @2 S7 H  @: W0 N
    3    Sophomore1 n6 Q0 }# V' v& k5 J
    4    Sophomore( x! @* r, w8 [) f" U+ W7 p. Y
    Name: Grade, dtype: category
    2 e7 z  B9 X+ h; z; e& vCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']; y% q! q1 \! T4 N3 L5 T! c: M

    , L, ~( P0 W/ Z$ \  J& Ls.cat.as_unordered().head()( g9 E# u; h0 Y6 ^1 z
    Out[25]: # z7 x. P, s+ R* y9 U' C4 W
    0     Freshman
    ; y0 W! a4 k8 a  i8 w1     Freshman2 O, ^0 I; R" V9 k
    2       Senior8 }5 c1 c9 a0 w* O
    3    Sophomore. w3 j  Q# K/ S% `
    4    Sophomore/ q& i; J0 e  W: F' t
    Name: Grade, dtype: category
    $ R0 k. ?0 W* ?7 V& g; J. C! L: vCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']& i) l7 F3 R. Y5 ~

    ) S  N) i. G$ X: v1
    & n+ P; E: i$ B# }1 k2
    : o& t4 F5 t9 I0 H/ ~' m2 {" T3
    - D9 M4 @  i; Q2 [& h6 V4. {) H! A) g! Q$ W/ c
    53 m7 w8 C* Y8 R; L) k8 n4 |) b1 p! R
    6
    ) l) j; K+ B. l) C2 q7
    ' M3 O5 c9 G: F9 d5 {8/ [" Y( @/ ~# y/ D: [) P
    98 M4 b9 ~+ T$ T, Y
    107 u6 a/ W/ v: L( z) B4 i
    114 e: X: d, Y; O4 A9 b! u, @$ x! n
    12. @; o5 b6 F: r! ?1 Q
    13
      O2 o6 e6 ?' W: M3 h14
    9 ^5 R& r# X5 K' @0 T15
    ' t- p8 P" k, k) q$ I" o, h16, @: z; l- |3 I! G( R2 w
    178 z) a$ e( O* \% X2 R% Z: u
    18
    2 X- ~( g8 H: O- T1 k9 C( _, x$ r19/ P% I  G, p% ?! u& N* i
    20; r2 ]; D  L  T, I! H1 i& H
    21
    ) r, \' `9 V) v# L% y22
    , g0 \, Y/ c. L3 i4 s1 y% X0 P  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。. a/ y3 w' k( S
    . N. ^5 v9 |% B4 ~! g& }$ j
    9.2.2 排序和比较
    / c) K1 ]7 K  h1 I7 Q在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    : s" c- y  i# a
    ' L' V. {. [' B3 z  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:$ n( }. E5 ^% V, H. a
    4 r* n4 V: Q& p+ d8 w' @
    df.Grade = df.Grade.astype('category')
    ) f1 R; X/ \9 f8 J/ Mdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    + |; A1 W  E+ \  y7 H$ Rdf.sort_values('Grade').head() # 值排序  \, f/ @- f7 |5 b4 Y- b, N
    Out[28]:
    2 _% {2 D0 a' I. `$ E        Grade           Name  Gender  Height  Weight
    ( x2 O1 ^5 \" P3 @# s, j% e0    Freshman   Gaopeng Yang  Female   158.9    46.0
    0 H+ w6 i! S: V1 q- w$ q/ [105  Freshman      Qiang Shi  Female   164.5    52.07 m5 t5 c+ K  h0 n0 z5 }) d
    96   Freshman  Changmei Feng  Female   163.8    56.08 X5 _% z2 s# R3 s5 M6 ?0 U/ \
    88   Freshman   Xiaopeng Han  Female   164.1    53.0
    ( O& K, E- T# ], o8 G% f* C! l81   Freshman    Yanli Zhang  Female   165.1    52.0
    # s9 |, Z+ j% R" W% A6 t" H4 a! i/ {7 c# d
    df.set_index('Grade').sort_index().head() # 索引排序) D$ e4 {3 J- \7 S
    Out[29]:
    * k: e, ?1 ^+ i# n                   Name  Gender  Height  Weight  d0 D7 D  I5 M+ N9 B( V) g  n
    Grade                                          $ T0 S2 e( A( o* M% Y
    Freshman   Gaopeng Yang  Female   158.9    46.0
    % T. y( y$ M- t% b1 [1 rFreshman      Qiang Shi  Female   164.5    52.09 x# q' f6 N" @( Q
    Freshman  Changmei Feng  Female   163.8    56.0
    7 p2 }" L) j# _Freshman   Xiaopeng Han  Female   164.1    53.02 j% f6 v+ r  H2 s6 h3 y0 x5 k* B5 ], d
    Freshman    Yanli Zhang  Female   165.1    52.0
    " R* |* \& f/ P' g4 G0 z& O# h5 j+ a- f0 k0 T' S5 `5 W
    15 q# z+ N- }8 F% ^- z
    2
    ! `. C2 w1 K0 l3- T4 u1 E) ~. [+ @6 j
    4
    4 N; d) j5 e3 `0 F5
    ; t" I" D$ D, g: J/ \3 ?6) \  M. x/ O( a% J* K1 N& {
    7
    0 Q7 V3 b. h7 T& y) F' D8 B- r/ Q8
    % g4 n2 v! ?7 Q9
    & G6 G  o' c' t$ T8 X10
    8 s/ i+ ^9 z; l* }5 m11
    . a! V& a  i& I( o' p, G  \129 \) o2 }- g: E2 V2 {3 M
    13
    5 z5 b' q! m  z$ d& i/ I! A- N, ^7 b% v14# W1 |4 P  G/ n, `, Y
    15
    6 X& f8 [7 ]$ v. l" k& j161 m1 n0 b- \* |, Z8 F; l
    17) f$ O: O, j( T4 B
    18
    0 c9 C- Z) X6 S+ W$ B( e4 ~+ V196 h6 E0 \% Z0 k: [( n
    20
    4 ^# c& A$ d# a6 F. b  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:. O) K  |, U! M$ A6 E% e

    & _6 E3 W- K; D( ]2 h- F==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)/ ~  a& X# f2 z  V+ }8 t
    >,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    6 v+ X% N4 q  R  a, X) vres1 = df.Grade == 'Sophomore'( h0 m, m: n4 R$ h2 I

    1 o# b6 Z: `+ I8 u5 |. Rres1.head()
    1 r2 K$ K5 k1 x" x, R8 f5 IOut[31]:
    0 c- p. m6 E$ P4 n; k; L$ U0    False0 _$ J8 Q$ j: O* i5 f: W
    1    False
    ( Z, @& r; |6 @) @) q2    False+ W1 W8 J) R3 S
    3     True
    - c! y. m. n" Z# d* S# u5 B4     True% J/ @7 o% l1 f* L) A
    Name: Grade, dtype: bool
    ; J  H" t& Z1 _2 b: d
      w* p+ O- d7 qres2 = df.Grade == ['PhD']*df.shape[0]7 E, F! f  f9 F$ u5 _

    6 a- I3 R9 v/ O! T) ]res2.head()2 K- I+ \) o. s9 P. U
    Out[33]: & m* ?4 Q. u. B7 {  Q
    0    False  k* a  i0 }- m# ?" f5 b" p5 G
    1    False. q! ?6 g/ l( L4 `
    2    False
    3 E1 X+ D1 v  F) D" S) Q7 g0 a3    False& ^; B* J+ ~% i
    4    False
    * E% C8 Z) L9 h* W; E4 K2 c! eName: Grade, dtype: bool/ p0 F: d. e$ L/ f5 F
    + O8 _: T) W$ g$ E' b6 }
    res3 = df.Grade <= 'Sophomore'
      q2 P  ^1 t* K5 V0 D) o: u, r& h% @5 m
    res3.head()
    8 T; b' t6 o: N$ r( S$ cOut[35]:
    4 z% O  p$ ^1 m4 Q7 W1 m  p0     True
    " C* b" E$ y  M0 s8 z7 ~1     True) y" `/ Y& P+ b- I$ H, {
    2    False" C4 N7 F( T9 z- J0 m: H, R" H4 i
    3     True
    8 C" C  L. d2 v! [3 i& S4     True+ b7 K5 [* k$ |, y; N) E1 f% \' _* X
    Name: Grade, dtype: bool
    7 N7 q1 H0 a1 a. y/ }, f$ r( j7 |; L1 m5 y5 `7 ?1 @
    # sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
    + E' \6 x' G% }  \' j" z9 hres4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True)
    $ X. m( ?3 B: i9 }
      z7 U; ~1 p/ T4 q1 F9 jres4.head()3 z* C! n$ \0 h" X% H
    Out[37]:
    : Y4 M: N2 c: \1 a9 t4 }) G* Y0     True0 y) o4 x) h8 V5 O
    1     True
    ; R' `$ C; |: V( `! x( Y* k# h2    False
    9 o: u3 h+ }. s# m; r5 K3     True# W4 \; p+ A0 a
    4     True1 M; ~8 w4 j- |+ J5 e
    Name: Grade, dtype: bool
    - }' d2 b1 Y% z) H0 x
    3 w: q# S- j& h& O3 z5 n1; t# i9 H, r7 `$ F1 i2 B9 b
    2
    1 [7 l9 R' R( x* g+ W2 k' e3
    . i: l1 Y. |  c3 I. J( t. l4/ Z2 S  c/ o5 |; G, W: r
    5
    2 v! k& J; t( l1 v4 k2 J3 K5 R) J6, S+ i$ \/ ~9 f) h; h
    7
    / y& y' M1 @5 N# A6 Z8 s8% S' X7 s$ k  k4 G, X. G) X
    9) E/ H, s0 _# z( X& t$ F
    10
    2 o) c9 h! l5 D11: m5 v7 ~1 W6 E2 t
    12. d! {: T7 n$ Q# g. ~' K$ v$ ^
    13
    0 R' y( z+ I& O* c14
    ; f7 n) m% R+ m0 C/ h15' L/ Q! S% ?7 @! P# F5 k/ ^. F
    16
    : r& e! u& t; ~/ B, E17; S/ z. e; U/ R8 E4 R
    18( V2 g4 |6 A0 }
    19
    ) \1 S  J) @5 X! S) w0 N206 F% D! z: s9 M# n* X8 _
    21
    ; j- A" I3 {8 q5 G/ W22
    4 D. m: w6 ]* i7 K) b23
    ' _& ?, b5 |( y8 e) |245 K9 k9 d, V3 x. H- s6 {
    254 T8 k. q  o" x1 U8 Z: o: h
    26  m1 e  U$ K( W' S7 \
    27! S2 d( n; T, w4 e; n$ @8 X
    28
    : k% W3 C1 f- i6 N) H- m/ ]' o29' O. S" t2 h0 ]& ?9 M
    30  t3 @* I  A3 g! F# ~
    31
    - O. p6 H2 {# Q32. V6 ]. L1 d4 M' [6 b! f
    33+ P; r* s% d. s9 Y: y! i! ?
    34
    . F0 j) Y, D3 z1 j1 U35# R( p7 e. l5 S& ~  U
    36% \+ u0 U/ @0 r4 m  x
    37' A6 Z. h6 [$ U  B
    38
    ! |- B0 l" H, o& X' [4 L% K39! [4 k5 s6 j  R, g2 r1 l
    408 N1 y2 W9 j  q8 h6 c
    41% O) i, B4 z- X+ M) C) Z
    42% v, B7 h( j; T# g5 w/ K
    43
    * Y( g6 \7 D+ E) M8 P8 s44
    & [7 E" S$ G/ ^4 Z' n7 t/ r9.3 区间类别
    & V: S0 D+ X% O" I9.3.1 利用cut和qcut进行区间构造( }8 J5 ^- Y0 O  {7 I
      区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
    ) w, A" \3 w) J, c" ?
    " P5 {5 `9 n, _' f) l0 K4 N# Kcut函数常用参数有:
    0 r/ n  Z6 g' F$ T" xbins:最重要的参数。' F( N7 J  Y. ]/ {- O1 @
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    + l2 F( Y2 _# z  }( [! j2 x5 _* Q也可以传入列表,表示按指定区间分割点分割。
    0 I# p; ?! C% U8 D2 b  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。7 w  G8 Z( f+ c! ]  o
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。* o# [: U( w0 p; H

    : D* `/ c0 S$ _! H2 us = pd.Series([1,2])
    3 @( }, k# |9 x% X4 \# bin传入整数
    - Q% L) L' k9 f/ H2 b
    ! E# I9 ]& o1 L3 {pd.cut(s, bins=2)
    ; B7 s, X2 W% T: Y% I* x( l1 eOut[39]:
    % i# _% N( |. D+ F. v6 b0    (0.999, 1.5]
    % p7 p0 [. m! V& T$ p1      (1.5, 2.0]! H0 U& n: \- u) |' ?
    dtype: category" Z3 t) m( N& K2 x
    Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]7 Z6 r) ]# g1 a

    ) e8 e! y& c- x' v" x: s) _' ~pd.cut(s, bins=2, right=False)
    $ g9 m5 ~' {2 a2 ?  pOut[40]:
    + i3 P/ s3 E7 ~  X0      [1.0, 1.5)
    1 k) L" |! \2 C1    [1.5, 2.001)! j. {. i( v; E
    dtype: category+ v1 E/ O6 e6 J; o) ~& c, ?- z$ s
    Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]0 x$ }; B. N3 Q  A8 H
    0 ?+ Y" r* N9 A
    * F+ b- N* d7 ]
    # bin传入分割点列表(使用`np.infty`可以表示无穷大):. O0 o# a  |  {8 X! T
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty]). ~$ g3 [6 |6 e2 K5 l+ q
    Out[41]:   R2 X& b6 |1 u1 i0 H. t7 y0 @
    0    (-inf, 1.2]/ A2 r/ t+ H# F9 f8 F! I
    1     (1.8, 2.2]
    + e2 `& K, d1 zdtype: category6 P% J) x+ |5 N/ l6 e/ |2 q
    Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]& B. w" ]' q$ T8 a9 _* o
    ) q: I9 X, L, N  [
    1
    * m/ \# u8 Z, D0 a; \$ X+ n2
    2 u1 j/ t0 ?- {3& d0 z+ N' D! A% f6 {& Y! `
    4
    ' u2 {5 [: E' d' L: E' z$ a0 c3 H2 c0 o54 ?8 s+ H5 d3 {, B" {
    6
    0 O3 J; n/ R: }: g& N: }3 @' i$ N7" m- e0 W: T4 Y
    81 |  {, C$ x: r
    9
    # x4 O% I; M8 y! f+ Z2 k. |10( u: \) u* a1 D, b% J% X8 ^
    11
    : `3 [+ T# |. s) t/ ?12
    8 F1 I, P, Q( u; O* t* s133 Z( G: _) u: N5 s
    14$ [: i$ A3 ^2 Z9 r: s  {7 H- P2 A
    15* K1 P2 X8 c" U1 |9 U0 G) X. r
    164 U" t4 c2 x+ \5 {
    171 r3 y! E4 b2 e2 X' Z" B
    185 f  L3 f9 o! O5 j5 I& H9 O
    19' a  ^7 N4 Z8 |9 W5 n- `* Z; _6 t
    20! p4 I7 J5 r  v& `. h" {
    21
    % \  E9 \2 E3 J, g; x: r' ?22
    6 H  C5 J  A9 Y* b1 h- b23
    3 m2 T* l% X  ]6 H24
    + k  ^) I# s6 E  Y25/ z6 u' [8 a: s: R7 a7 |" {
    labels:区间的名字
    / k- ^. @  b) [3 xretbins:是否返回分割点(默认不返回)
    5 h3 a7 h# g+ p默认retbins=Flase时,返回每个元素所属区间的列表
    * c6 L8 p* Y: u" v; M. x# kretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
    " x+ N& t/ G: R0 ~; s
    3 m, M  L; i2 _' T$ R+ k0 @s = df.Weight- E2 u9 ~2 R' ~  {& V+ y$ _. f
    res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)  R+ ]9 X3 E% k. ^7 \+ Y: M
    res[0][:2]. n/ Y! o' r4 l8 ~% g
      Y$ T  c$ s5 q; L- X. R6 W# E
    Out[44]: 7 A* H' V" m8 ?5 l" v' t6 P4 r  a% B$ c
    0    small0 K, ~1 ]( p0 b' P& x
    1      big. C/ K( c% v( M8 Z; p
    dtype: category
    0 B, ^6 \  }/ _Categories (2, object): ['small' < 'big']
    # U' R/ \9 @- I& c9 e# ~# ]
    8 q4 n7 R  [- }' T' N, `( |res[1] # 该元素为返回的分割点' v1 \! @+ v; B' ]
    Out[45]: array([0.999, 1.5  , 2.   ])- V* M( Y# X  x, a
    19 ~+ A% ]1 h, U' p  T' Z3 ~
    29 M$ a# \/ @1 v/ U1 [
    3
    ! n& H( i: g$ D% {1 u8 P8 w4' b9 k4 Z. q3 D; T; n5 Y
    5
    , x& B! E+ V  B, Z5 w5 x6$ j- T" s" v; x: c- `
    7
    ( m" M$ i3 a* B  u$ ]8/ k6 r1 z) p3 F3 M  i+ Y/ F
    9% M) u4 l1 s7 m" U5 W
    10. @2 [  ]3 A, T1 t" @. f
    11" T7 i; }7 P6 l0 I1 t
    12  G: L/ ?' h: {* ^
    qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。% p( A4 s* \: D8 o$ L! z
    q为整数n时,指按照n等分位数把数据分箱
    * M4 \, o5 X+ S- s" jq为浮点列表时,表示相应的分位数分割点。$ L- c; }  }& c4 y" R' S8 W
    s = df.Weight1 S/ N% j" ^4 q+ l4 L/ @8 @; n0 D5 w
    + V5 U. E# W$ q* ^5 {
    pd.qcut(s, q=3).head()" P" t! H; D6 m, t" N6 Y+ u0 U3 v
    Out[47]:
    $ _# b% k! G9 M& Q$ B" W8 m0    (33.999, 48.0]
    0 ^1 E5 I3 B' h" R# a/ |! Q5 i- q1      (55.0, 89.0]+ Y9 ?& y: D4 s1 K- w) G% M+ N
    2      (55.0, 89.0]
    - {0 t* W7 C7 _' e3    (33.999, 48.0]! `& T  m* x9 i
    4      (55.0, 89.0]
    ' ~7 d; n9 K& lName: Weight, dtype: category9 o4 {9 o- I5 R! r% _9 p
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]- ?! L3 v2 S; f6 ]7 j6 s
    2 A- |# ?" B9 b3 I" `
    pd.qcut(s, q=[0,0.2,0.8,1]).head()0 k9 M; P1 R7 R7 o1 b. ^
    Out[48]: ; C; Y2 m9 V7 g1 K
    0      (44.0, 69.4]# d. r, |/ K; [- e
    1      (69.4, 89.0]
    ! T9 l. o) ]- F2      (69.4, 89.0]
    5 B  m9 p% |3 f7 `+ f! e3    (33.999, 44.0]9 k) S* P3 X$ h' Z
    4      (69.4, 89.0]
    ( u& ?7 a( y; b8 N2 I/ N$ vName: Weight, dtype: category
    % Y: b( Z! O5 Q% N" ACategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]. ]& ?8 {# w- k* R3 T# n6 T

    0 h5 \- [# k. W7 s7 n6 L  i- F& q1$ R4 I) x1 q( i- ?( l0 e1 M& c8 |
    2
    8 \/ g/ L) K8 k# }; l3& U$ H4 M* e" T
    4
    # Q; q& {* L5 F5/ }5 Q% x& X" j! l# |; _
    64 h9 b  @  z. m$ @7 Y; h& Q
    7' W* N0 ~4 W4 O. u/ o6 C
    8/ r8 j2 W  }: P" v1 d5 \# L+ t. O
    9
    0 g6 M: u' o" S$ v, d4 [& d) l10% @9 E% ]; D- M' y
    110 m" U: @. z. m* X+ p' N
    12+ @7 |$ s& x' g: f9 v  O
    13* C4 ^* r7 F/ ]( u3 r
    143 v- A4 y. @- y
    15
    : i" |- P4 q9 [2 _16& [" e/ R6 s  x) D
    17
    6 D2 d* N3 K  u  z; ^3 L8 q7 b18
    4 G3 I& M5 X5 @) x& ]  k* A3 J19: R, t5 q5 [" N4 o
    20
    " H# T+ {# u, L4 M; q21" k6 P- f" D- w1 P( D, P
    9.3.2 一般区间的构造$ y7 U+ R. H9 S$ g. x
      pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。6 y! G6 g& Y) G# h5 k7 A2 r

    % v9 j, B4 u9 r8 \& |1 W开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。( _0 z( F9 ^9 J& ?2 {+ d1 u
    my_interval = pd.Interval(0, 1, 'right'): I  d3 H: T  ?* Z

    / s/ l+ V- Y9 A# ^6 amy_interval& j# |/ w- f+ l5 n1 T, A
    Out[50]: Interval(0, 1, closed='right')
    , I+ v% v' h$ A* @+ M' R! w1
    # x$ M! \! d6 c! }% T$ n& V& Z22 M, y+ S! h3 c3 b- K
    3% d, p; Z  A: h9 X+ U6 z+ L
    4
    5 N9 ?5 ], L2 P" e8 V8 L区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。" f1 [* Z. I8 B
    使用in可以判断元素是否属于区间
    2 |3 K3 j: i) z1 @$ S6 Q用overlaps可以判断两个区间是否有交集:
    ! k' O4 }( x; s- E/ J0.5 in my_interval
    : v/ v- k$ j. X: q# z' p$ \4 R& K( F7 `2 }
    True
    # n8 r+ K! j  r$ e12 ~0 H' F% N5 v0 b( J
    2
    6 _# `1 ^% ~6 L2 B* L3
    . p# F/ D$ H) M1 x9 r4 `; j% E" nmy_interval_2 = pd.Interval(0.5, 1.5, 'left')- f" ^/ P* T0 p, Y, T
    my_interval.overlaps(my_interval_2)
    - W3 k* j/ {) V: q' H9 {& o+ q+ f. }% p
    True
    $ ~) s9 L# ~9 [1 O& l1
    % {& ~( Q  i/ K4 m28 C0 ], T" T# H& c' `
    3
    5 S" S, d# d6 p6 p  `. F! n) u. Z6 W4
    ! b0 r; A2 ?( X  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:9 z2 S1 C3 j+ M$ ]- g! j7 S% z) x

    5 f$ A* H; j8 N" s5 l9 ?from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    $ ~4 D6 c, z+ X. ^pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')8 j/ M# \1 T2 L- T4 X) v
    8 O& A2 n3 g. j
    IntervalIndex([[1, 3], [3, 6], [6, 10]],
    5 P- h" X$ k3 r( x9 D/ b               closed='both',
    ( Z: N$ g" g8 ~1 l) V5 W               dtype='interval[int64]')
    # U; k/ [+ N: K: {10 W9 H/ C  F- r/ v# H6 Q, M" f
    26 X, }0 X5 @- k. R0 P
    3- T; D8 F! L+ n" ?( d  |
    4
    % v( x6 u" s# t) [; g( x5# C  m. J/ m( S- v# u9 t2 Q. Q9 \
    from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
    & y9 _2 C5 C( R! B  apd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
    : L1 J+ k' q$ \5 b! `3 R; t' d* l/ B0 T6 ?/ G8 R# B
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],  c' |* w- s0 I& `0 s( P
                      closed='neither',5 _. y; u; X9 a8 d* I
                      dtype='interval[int64]')
      c- i( M# V( Z) R/ A1
    ( N8 v$ S7 A7 r+ d# j6 G7 ^! D2/ ^; A5 m, p" f9 a
    34 c1 G, z) T) C- h: R
    4
      t- ~  A+ l6 q1 b& n8 R' n4 M5
    0 `; C8 Q/ v& |from_tuples:传入起点和终点元组构成的列表:
    : [0 |+ r$ m( L- Vpd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')! e- K  \8 \# w* F) q0 `0 L

    8 p+ Q5 ~$ s0 k, P% j( u( y: u4 mIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],( m+ K8 j( k0 y; v
                  closed='neither',
    ' `3 @6 C0 o, d" O8 B% r              dtype='interval[int64]')
    5 E& j& J' C9 h# }, {1
    0 [0 K- L- `; ]2( @8 j3 ~! \  t/ J/ [" }% B
    3
    0 f# ~0 S( J. P( C; j4; Y  ~4 O- ]  I; u% o
    5
    $ j/ R/ u4 G0 n1 k/ {interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:& g. w0 ?, X0 S- C
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
    ) d* w* j9 c2 f" l0 Z+ pOut[57]: 2 q8 Y2 f; K% A! R8 C% Q
    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]],- U) p+ ^: a1 G8 T
                  closed='right',9 W- d6 d3 s8 A! p& X+ @
                  dtype='interval[float64]')
      |: x- a( l# U, @) A3 K; R. d- }3 ~
    ( {$ j0 g! g1 {. o9 w  L6 epd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度/ R6 e1 `. U2 D. L* Q. I
    Out[58]: 9 D8 r) q* Y. ?! w
    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]],- {% N! J, {& w( [4 v; s# q
                  closed='right',
    ! O. c9 P1 k: x4 W              dtype='interval[float64]')2 w. b$ G  t/ }
    1# y  Q. v9 ?3 _
    2
    1 W% e+ g! ?7 ]3% ]4 B- P% |% {* d4 ^6 R
    4
    + K# P/ m3 F( ^& A  Z$ `5# ~6 x  z7 h9 i! w( a- w, j
    6  O/ l  }6 d4 O' J
    7
    * k; {" ^) w: \% c2 ]5 U: m, n4 o8
    % Z( Z. A3 x1 f# V; u. u9
    + m% u2 d/ l  w1 z) a" T100 X! Z& O8 t) ^# r8 \
    11
    9 q. }9 x. p- f$ A【练一练】
    0 {5 H9 B. w, s1 Q  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。7 z4 j  Q$ ^  Q% j/ q4 m

    % `) u$ u0 P% w2 n8 i  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。" n: w/ b3 l1 D- ?
    / f8 W8 u$ v7 D, E& Z
    my_interval
    1 m) J' X: @4 [4 sOut[59]: Interval(0, 1, closed='right')
    1 E) u3 {- n# r/ z9 l7 Z# ?7 L) p! @
    my_interval_25 h# r5 n0 ?$ I% r9 d/ g
    Out[60]: Interval(0.5, 1.5, closed='left')
    $ J) N% x% ]* b7 D8 p5 z4 u: ~/ v+ A- X+ Z# A; c
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')$ p+ t0 Y: S+ w- f# D' ^3 W
    Out[61]:
    + Q. p& D% k- X6 G% pIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    " ]+ ^- J- @: G  A              closed='left',# X0 I9 U5 V' H9 _
                  dtype='interval[float64]')
    $ W" I; i6 z5 l1
    5 D# z/ [4 F3 ?$ n24 }; @* ?7 n7 ^. O7 b* h( ?+ L# B
    3  P# Y% C. i/ b4 b
    4. ]5 s: b) m+ L# P' o. A
    5
    5 J+ S  n$ a0 ?6 m" G6
    0 v* o7 ~6 U! d$ H. `0 a7* P# U% K/ g% _- M! q% |9 ?; E# F0 V' Y
    8
    ( N' X) T0 Q8 K& Y3 E  K9
    ' {: e3 G6 `) U- o8 H1 \10) U  B8 n% x( x0 u6 x2 _' o
    11
    1 g( z  V9 b5 B9.3.3 区间的属性与方法
    " ~1 n% e( F% J$ d. c) X; I  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:; P' T' l8 J8 ~5 o. x, g" i  J

    8 W0 r" y- b! v. ~7 e$ Us=df.Weight0 z1 @* I# }5 y+ u* N  ^$ @
    id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
    2 F8 g5 l% q& \0 }* k- jid_interval[:3]
    $ J9 i) D7 s0 Z7 @# C
    + y0 z7 H$ ~3 _1 `IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
    / [0 |9 d* {& Y/ y! n                 closed='right',1 c2 i% k$ ^+ i- C1 k
                     name='Weight',
    3 M( t5 b  S( t6 C4 ~" R1 P                 dtype='interval[float64]')0 T* E: I$ U: y* o8 Z0 `
    1
    3 _9 G  e+ S) w9 T/ v; I2+ P# F0 i8 J$ ~. S' H. v
    3
    + w: s* h2 @2 k, w1 R" x3 B4
    . W; K& B1 B7 M5. N% W9 I" a9 \2 r9 ~% w0 [
    6
    ( q4 |8 ?1 C. x: q( `" B5 |7
    % \: }' S( l6 K: Q3 C8& ]; g9 g% d+ c6 v( ]
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。- _# W0 Q; G9 ^" R  [* u; E
    id_demo = id_interval[:5] # 选出前5个展示' ?- t9 U) Z. \" Z

    : e; E" N- |  f$ aid_demo. q" {7 r% u7 _/ A" s, v
    Out[64]:
    2 e4 N. N6 ^/ Y7 i4 r+ [IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    9 [: @% P) M: _' W2 a% l4 G. |              closed='right',
    6 j% ~! J# V( G8 z7 P; K* |              name='Weight',0 y) F2 A# W5 L- I3 D' r
                  dtype='interval[float64]')
    ' {* Y4 _$ Y( z  t5 b6 l0 w: w7 U2 b+ \( s2 a5 \
    id_demo.left # 获取这五个区间的左端点
      [. z3 I. B: E1 E& N% |" r0 @) ?Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    $ G; c* c8 t9 I4 N9 P
    ! Q+ N5 \# Q. Mid_demo.right # 获取这五个区间的右端点
    ) I1 V8 r. K( q: V, F2 mOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
    & ?" b/ i6 G. a/ m5 y6 S) F8 O, f. K- E' O* Y% Y
    id_demo.mid8 J* s) p. n# l1 l! }2 E
    Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
    2 R$ e* p9 y: n' w7 V$ h
    : D4 z, O+ S$ }id_demo.length, b6 t+ F; L6 K" S% ^
    Out[68]:
    : Z" B, {7 O: I- i- }8 Q0 PFloat64Index([18.387999999999998, 18.334000000000003, 18.333,6 k) U- `2 ?8 O& F6 f! G" T6 L, W
                  18.387999999999998, 18.333],
    3 ?2 v" i. J8 \             dtype='float64')$ ?5 {2 x9 h+ L
    # D0 l! |# u* Y0 r/ t" _3 g) s
    10 y4 C; p, c+ _, o$ U4 a# E& B# P
    2
    & E# U% |3 D8 L5 I0 L3/ D1 \5 Z8 X7 Z3 L$ l' n
    4
    & i. |* f8 d! n2 \' A+ t: t* A53 Z1 `% M- \6 K6 h! A
    64 s1 D/ ]" j+ i- k4 W/ `
    74 d( Z4 p4 h% K/ S$ f9 W. x
    8- H8 C; L6 S2 T) `$ @* Z/ V8 c/ B
    9
    8 e2 \. R3 A$ q0 f$ e- q10( l* K, l5 ]" _
    11
    / z( G# I8 v9 |' X1 ^  ]12& R$ T2 Q+ P3 `% `6 Y; [
    13
    ! t8 Z( s' v4 E% T( }7 z- k+ m14
    ( V/ ]  M/ c. @! q15
    ( c$ @2 G2 ?0 F16
    8 e# ?" G: _1 {" B: e17: R/ m& D7 t8 N- C
    185 H1 D6 i' L) }0 x8 m+ h
    19
    2 M0 n" @$ W2 f( t4 T. {- l20
    0 o) Y/ [1 Q( f4 C. A/ y! M21! o6 ^. c* E* }8 r
    22" k" t, s) |# V
    23
    1 M7 \' I: K5 a' P! W! vIntervalIndex还有两个常用方法:: b. e4 K! ~0 _: y0 l4 ]
    contains:逐个判断每个区间是否包含某元素
    ' U& t& Y0 p; _8 K4 coverlaps:是否和一个pd.Interval对象有交集。
    , K& M% M7 Y: Gid_demo.contains(50)/ _! O* v' n0 ?( j- R* {+ Z; e
    Out[69]: array([ True, False, False,  True, False])4 R8 ?! s5 d( ^* C; s
    / G4 U' z0 S* ?# y& M; `
    id_demo.overlaps(pd.Interval(40,60)); S6 d  S# `: R2 L; {
    Out[70]: array([ True,  True, False,  True, False])5 D$ B, X+ H* M$ \$ l' k
    1
    1 R. Z1 w6 v% d& x* Z7 R# s2
    0 Q; ]$ Q- s. C5 l39 m; ~3 x: p7 C5 u, g8 M* D
    4# T! c+ z8 n  d0 x3 c' k
    5
    5 B" ?) O9 d8 _9.4 练习& C' a  o# q# ]; O8 q
    Ex1: 统计未出现的类别
    * |, J- A% n; P) P# ]  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
    1 b7 A$ S; ^! S7 C) ]6 R+ I+ n
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})" d% g' g6 @* J
    pd.crosstab(df.A, df.B)- V" d3 A. r  n8 e; [
    5 J  p) j" I. G1 `' W7 u! K2 W
    Out[72]: : m0 q1 h. d- \% W* O' \
    B  cat  dog$ I; ~8 F. {) C
    A          - }: ^! s5 ^: K
    a    2    0; z0 }$ l+ n* G4 T
    b    1    0
    / L6 D4 ?# j+ _% Hc    0    1( v- \9 F9 L: I2 g+ L  T
    1  ]/ i: N4 n( `! v4 n+ g  v! a
    2
    " }- M% w+ Q. y' w3
    3 s9 E" k' q2 s" |4
    4 }( T8 ~  F% l& h8 x% `# f5* ^  w' H& \: u. ~
    6
    ' u2 U: @1 k7 B  l$ y. B1 \0 B70 n/ B! T7 A  r7 h1 Q
    8
    $ J, L' ?/ ~7 R& w9
    3 r9 e6 [' h. D" P+ O, d. c2 U  但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:  Y' @0 V4 F1 {4 r: H# U) e
    ; B+ X/ I5 x; |9 L' J# k: \) C0 X( e0 v/ a
    df.B = df.B.astype('category').cat.add_categories('sheep')
    ; j3 g3 d: `2 G7 l  ^% U3 z0 Ppd.crosstab(df.A, df.B, dropna=False)
    $ L& t. u3 g  I2 c# Z
    5 E) }; ?2 \6 r5 z* C' w; bOut[74]: ' T/ N! j) g& X- D
    B  cat  dog  sheep
    ( |( T& S2 G, P" }( {A                 
    7 u5 [  s2 M+ |3 v; p: ~  t9 sa    2    0      0
    - ^6 u: b( f+ B& R1 sb    1    0      0" \; {  A& f. B  ]1 z) X2 p- e
    c    0    1      0
    ; G- [) I+ Q9 R! ^1
    # ^* O2 u3 o9 ^/ m* C2
    , o% S+ w' i: R/ A- L3
    # h! E4 B, x, B3 x49 n* i. Y. Z) F
    5  v& _: |; h/ N* X
    6' K8 H- Q7 A# {4 J/ |1 g; @5 S2 m' E
    7
    " R" B; Q7 A' N4 H0 B$ d/ j. N8 g8- G& J# B- }" i3 T2 C8 l8 g
    9' X" c) a4 I; N! A( @# o8 h6 J: J+ K) D
    请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。8 B: u3 s4 |. V) u* G: m# @

    5 s- `* T% Z5 \7 N7 N. UEx2: 钻石数据集
    ; c8 W& x9 z+ l( B. K7 ?  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:
    ! Y- e/ Q% R& H# U7 y  H6 q0 `; n, n) y% E2 i! v
    df = pd.read_csv('../data/diamonds.csv') 6 B) B, n* V; u. G* ~" M: B
    df.head(3)
    6 e9 I  n0 ]; ]8 x' l" o. A: ~
    4 h. @6 Q0 @* }# _- y1 lOut[76]: # }2 B. q" i! b% q) V9 q
       carat      cut    clarity  price
    / ~0 S1 f  {* r6 |, R6 S5 H5 d0   0.23     Ideal     SI2     326
    ; S' D7 ]( E0 j1 j  [, `+ U1   0.21    Premium    SI1     326
    ! h, j9 @  j: G2   0.23     Good      VS1     327* Q6 Y0 v+ J" b- p' m" U3 P7 T
    1
    ' _" k; }  j# H; R1 b- {1 t2, t; t3 N! i( b
    3
    & [9 A" [5 w9 X4 X% J48 H7 J# H! D2 d/ u' W& O
    5
    + ]4 q6 ~  S+ p6
    * J2 N7 h  _$ V9 E8 u8 V7
    9 _. F( z/ c6 [, Y$ N7 J8: D: }) C5 D0 N+ l3 Y% B3 U
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。/ X/ }( |; @9 f5 N
    钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。0 C2 A( p: A! u- f, m6 k
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    3 M+ V" Y6 G6 S) ~对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    + x9 S) [6 K+ z8 {  w第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    3 K$ r  ^, A4 _" p对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。5 G( P5 X  j7 n+ j: {* o
    先看看数据结构:
    $ u2 Z$ p: m8 G: C+ g/ A  Z. o1 j' [5 B4 a' @3 ?5 A! F0 J3 [
    df.info()
    0 P/ Q' u1 \: xData columns (total 4 columns):
    5 g5 H, _, o& y( i3 T! S$ N #   Column   Non-Null Count  Dtype  - c; O, d4 v! E& Z' n
    ---  ------   --------------  -----  
    # F5 @' ~3 n$ J6 v* ?9 S 0   carat    53940 non-null  float648 d$ O  d8 G4 q/ M% m; s
    1   cut      53940 non-null  object 6 q9 r& r0 P2 I, a  C: o2 e1 B7 e
    2   clarity  53940 non-null  object
    6 \# j5 }0 `. a( ^2 W# B" T) g 3   price    53940 non-null  int64  8 ?# p( U% u5 B: g
    dtypes: float64(1), int64(1), object(2)# r: l( c" B3 n& d/ `7 s9 B
    1
    # ^) i+ J9 g' Z) H2
    : l4 w' `) f4 v! Z9 }, p3
    $ F& k& M  i$ \% t! i1 n$ X4% h% }1 \) B9 {7 L
    5: V, n& s" ]' p# v; I3 @! V; ~
    6, F0 [! o$ c+ w% Q& V# r, S5 c
    7
    + B% ]6 O- w) o- o* w* H3 i, x8
    , _( M! }1 r& R, @9& \1 _$ c6 c; A6 j' |* }* J
    比较两种操作的性能
    " w- ~+ Z) w6 G" C%time df.cut.unique()0 [; n- e% w% Z0 K+ `0 m2 O

    8 Z" S6 u" |+ @# T, d! S" |Wall time: 5.98 ms3 B. f# F9 _+ V. H
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
    / T* \. f9 V5 v8 k" u  Q1
    1 c" z* C! Z( I$ Q# o; i$ S2* C( p, Y) L) v+ z
    3
    ( c: |' G7 p4 E- `' _4
    9 t' r1 V' k+ `5 h8 J% U" w%time df.cut.astype('category').unique()
    5 D& Y  }7 y* P9 C, B- a3 @! c$ v! M, F
    Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms0 r% _9 x% j/ x& y' j- m" r6 {
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']9 o7 h2 X& F( n* q; @; p, t1 H
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    6 p$ p9 b% J1 C9 R8 ?. e7 e3 K1' t, M6 B2 S# s& ?
    2
    $ k% X9 T* }# j$ U% W0 }) w7 i% U- H3# L( e* L9 g! `! \1 X
    4
    / @* B7 j2 D& P5 C$ Z5
    " h2 [! q- A4 j, cdf.cut=df.cut.astype('category')
    " e5 t, @5 Z2 z* i. N) a% `%time df.cut.unique() # 类别属性统计,2ms' d+ Z, w' q2 @3 u# U# O  y
    ; B5 X, e3 l* O" r# o0 S
    Wall time: 2 ms- U$ Q2 y" |. [: q: B- D- n% C6 j
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']0 D5 G0 Y" B. S' c0 u
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    * w9 J! k0 \- ]( p; H1
    + D- x+ z" o# j$ D7 i* w9 I0 `2
    8 I/ R1 p  t+ @+ i/ S: b: Z3
    ' n6 A9 w! A" f3 r- L- G6 l9 ?3 B4
    9 _' [) h9 l7 q; V+ @5
    5 L6 v) j& F+ d0 l7 O' E6
    4 }( U4 ^- {& k% u( A. v对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。, p5 f' c0 a$ ^6 O' |( f
    ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
    ! |5 m, N( o9 _# s! w$ q, Vls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']& I# `( Z+ n- [5 Y' c* E9 Y( \/ g
    df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    " b9 C# Y& ~- ]9 B* i8 m6 S$ J- ?% vdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
    + k; u* e. B4 k" T4 w" A1 \  ^" u, P! y: z5 D2 q
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)6 O. C. a/ \+ Z$ `2 V
    2 ]6 Q% X! i2 f7 j. }
            carat         cut        clarity        price9 l6 ~, V' W& W+ d' _
    315        0.96        Ideal          I1        2801
    / E. E. u( }( k. N8 o. x/ @535        0.96        Ideal          I1        2826
    / g% h7 d# X" h& M8 E. m' `551        0.97        Ideal          I1        2830
    8 K' F3 N6 `% \- Q. X1
    - w( `$ u/ l' _8 f2
    1 M" J4 t, `* m% _# Y7 m  k3; c4 B1 l2 k2 n2 h2 |# ~- @7 q
    4- v/ ?$ Q% ?/ E3 l# Q' t- Z. U, W
    5
    ( V+ ^- [/ c8 ^9 f6" z: n& r7 Q! [$ {8 q
    7' p% M/ q( X# U3 m( i0 Y  A
    8% Z! r1 k$ R0 c% F5 F8 Y
    9
    * U4 D9 u1 P, Y  r  }* J" v, J& C10: k3 n0 ~" I, _/ T
    11' J1 O7 m- B: _  \" X
    分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。2 e$ R7 `; }' y# k
    # 第一种是将类别重命名为整数
    2 ]7 z( |" Y3 idict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
    # Y7 a# X8 A4 ^) ndict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
    / h; ~# _, B3 F! c1 ?% p/ i6 u2 k) b& k$ n& t9 j$ ?
    df.cut=df.cut.cat.rename_categories(dict1)$ Z% [! p7 z) w5 A8 ?
    df.clarity=df.clarity.cat.rename_categories(dict2)( S/ o+ u6 f& w/ e. G
    df.head(3)' I5 B* J( l' Y
    # Q% j4 D% Y* F
            carat        cut        clarity        price  [2 Q" Q& t! n
    0        0.23        0          6                326! C) W5 @- b* e  Y$ i
    1        0.21        1          5                326
    3 X6 Z& w+ K1 c' I7 }2        0.23        3          3                327: u$ |9 V! }+ Y& B+ B9 E
    10 B) |! \. _( W" A
    2
    ! z; m* A9 R5 d0 G! L4 f3
    6 F# f  x; P: b  t( C0 X: l3 ^4) b: ^0 K3 j* @5 B
    5
      k5 N6 |9 _* I6" z5 D) u5 }. X1 }- c6 g' X3 E
    7
    ; a5 }0 V) x7 B( G- I# F. B2 G! O8! H7 n& ?) d! U- ?
    9) D" p6 Q. n8 W" D
    10$ F# t/ |5 P+ `$ a
    11
    7 s+ }" ?1 Z" E, [$ h; `) ]  f12  B7 R% c0 O$ G* i7 A
    # 第二种应该是报错object属性,然后直接进行替换3 c1 N4 X3 {$ U" s" l: j. _
    df = pd.read_csv('data/diamonds.csv')0 R5 N! m! `7 a: W1 j+ e
    for i,j in enumerate(ls_cut[::-1]):
    % {; h1 F# Q. m) p5 v# D    df.loc[df.cut==j,'cut']=i ) `7 l+ K$ }; h$ {' j
    ! S, _  j7 T" h7 P5 j
    for k,l in enumerate(ls_clarity[::-1]):4 T1 |5 Q( U" Z. ]
        df.loc[df.clarity==l,'clarity']=k5 D( w* p# D$ R) C
    df.head(3)! [4 Z. m3 @$ L6 z+ a

    . M7 v$ _" ^& k        carat        cut        clarity        price
      H3 E7 Q: s( D. m0 P* D0        0.23        0          6                3266 z7 @2 l/ H7 J/ V
    1        0.21        1          5                326' U' c3 Q$ I( c% a% W4 i
    2        0.23        3          3                327
    3 f2 w4 Q3 {9 p  z1
      |& B- u1 ?! f4 d2
    1 ~7 s* D9 z: y& P- Z3) j! Q. v$ B# z3 F" V6 Z7 e. o
    4
    9 j4 R# M1 Y6 G5  ?. r( |- w/ ]
    67 D( g( k. l' V7 l3 }1 B4 [3 k
    73 `, z0 C7 h" h4 L3 s
    8
    5 _# q  ?& }0 h! e9/ O2 Z3 n" G6 H1 W! Z. W
    104 ]5 x! [8 w3 B, J
    119 w+ @6 ^' f- o8 O2 ^0 B, E. g' f
    12: ?/ x2 `( w. u
    133 n1 F7 y- m8 C: A) o2 d6 K) f
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    & {- W) |& Z4 k  w1 c# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
    9 I0 G# Y  i: mavg=df.price/df.carat
    # K2 z! S. J+ b  e6 Y
    ! m& d( j: \# Q' ^df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    2 Y$ ]* H; F- m3 z8 ^; ]* V                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]5 H! ^; X: J( @! x4 K$ ^

    * }# H. e# Q* s! X; E/ j! Ndf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
    / P7 e$ ?5 Y" ~/ b' ^) P0 L% X; L                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    ; [5 B* W$ }4 Cdf.head()
    " T3 W2 L+ V4 C1 n: l0 x, ^* }9 I+ }  Z8 J
            carat        cut         clarity        price        price_quantile        price_list
    + h1 F& L' M! B4 U% b- }; _' T& l8 h8 F0        0.23        0                6                326                        Very Low                Low
    5 ]  v" C! L. H7 L1        0.21        1                5                326                        Very Low                Low! l5 Y; p; H, d/ v- Z. d
    2        0.23        3                3                327                        Very Low                Low
    " ]! d0 q; Y9 L' i) g3        0.29        1                4                334                        Very Low                Low
    ( u5 \- l" w) J4        0.31        3                6                335                        Very Low                Low                                       3 I3 E& E) S+ b$ Z6 z( l
    9 q, `( \" J( Z* Z+ p
    1( p) @5 ?& S  N/ W" f; [" I
    2
    % ]: m. M& y( y: J  B, u4 u* B4 d3/ D+ X% l0 v( \
    46 ~" `# _9 ^* O1 k
    58 i& o2 D! g: j  K
    6
    " i( Y3 u  n) i7" o7 e* y" G3 o& x
    8- A/ ]) Y6 A& L5 W/ c( k: l' I
    9
    7 j  @  ?0 Y; M9 l10/ s. H2 ^3 j/ D0 y
    11: W( L) V1 T, z+ D6 C3 E; ^, n
    12% J- a& s4 w& w: r3 h
    13; C3 L1 o& d* r6 V: `
    14% t/ t$ a; x( |
    15- w# H9 `8 S$ N* ?4 y
    16$ h. i5 r6 s6 w1 c
    分割点分别是:
    1 ~, ~, J8 N. p/ f6 C) C! I: f
    0 I. m( c% S2 X, d8 ?# ]array([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])+ G( @- C7 c- m) \- |7 U& R
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    2 p1 i5 {1 F9 e8 k( k1
    * i  q; x9 l5 o2
      @) ]) u) C( m5 U+ u5 P7 V# ?/ u, M第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。- r) W# k- R7 C  f4 Z1 t  P5 ~
    df['price_list'].cat.categories # 原先设定的类别数
    . I! I5 V& F9 r* ~& H3 |Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
    ! R. J7 R, Y$ c$ Z3 b' S3 R
    4 V( _3 P5 T4 p* b; k0 Rdf['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别8 v# ]. [; n$ V% g' W; d
    Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    1 F! Y  A3 M) X1 p1, y: n$ M2 B$ I& i; `
    2" a; i% q# q: L$ f
    3
    ) _' k2 x( C& r8 h- X4 Z41 N9 |: x# o' y- [
    52 l- i9 {+ G/ k, f& k. o8 ?% k
    avg.sort_values() # 可见首尾区间确实是没有的0 F" s5 E" }$ ~* U0 O, J- F
    31962     1051.162791: S/ O$ `6 N: ?
    15        1078.125000
    $ H  \( L8 j1 m$ [* K4         1080.645161: S3 B. L) @) b% o) p# ^/ j
    28285     1109.090909
    * ~! c3 Q" ^8 ]: D+ G1 c13        1109.677419
    9 O; y0 G* z6 y! y             ...     
    2 y* |3 y2 R, l( a26998    16764.705882) y! I0 r9 Z2 r) J
    27457    16928.971963
    2 d" z6 h) Q- F0 O2 M/ A2 d) |/ o0 I3 n27226    17077.669903
    3 l1 b6 ^6 c/ {4 R27530    17083.1775708 K8 }3 ^1 {: @' N3 I' h
    27635    17828.846154
    2 {9 U9 p+ T) z2 K+ q8 [5 j# X( B5 f1
    & Z1 d, e2 a( m2 k: q2
    + u3 X# j! Q0 k. k5 u3
    5 D1 P0 D6 J  Q( B0 F$ }4
    3 j+ Z( F/ ?0 [; h0 G) O5
    & f. L3 i) N9 N: g0 F1 A1 ]/ Z* b6
    7 N5 d8 a0 v  a+ a7
    6 c5 h. Q+ ^6 v  t2 ]& Z# l& G( O: M8
    9 H( u3 S# |/ b7 M1 u! q9
    0 ?) X2 F+ z, f) Q' Y$ ]: G4 E10, O- T6 {* k# @8 B3 e$ M+ @$ L4 l. p
    11
    4 q# s0 G* h: x; q# H- X6 S12
    " N* X% ~+ J, w: f% |9 s, P8 o对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    ; R6 w4 ]( q+ O( D# 分割时区间不能有命名,否则字符串传入错误。
    ) Q; l8 N7 u/ Iid_interval=pd.IntervalIndex(" J. p( O$ f& }5 P, [
        pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]1 P# B; D! G. }" u% Y: @/ t
                                )
    4 n* j. W8 ^$ D1 G, Y2 s% }: j: z3 ]id_interval.left+ b) z' s) J0 P7 u/ h6 B
    id_interval.right
    * b9 P/ B5 v0 s' F" Xid_interval.length                            : C: B% P  a) Y: _4 k; f! s* Q
    1
    - O. m7 `  I% F, P! c5 o2
    * o/ U, S7 t3 v: `+ X3 ?3
    0 t" Q, k# t; W3 H/ x: c4$ p( U" e5 \2 F3 \7 p6 {* @5 o
    50 w5 k: z6 V9 |( x( R( ]
    6; N# m. L, |4 u& j
    7
    ; W5 y. F# f$ l) k! D% j# b8 w第十章 时序数据
    6 C) X) s$ \+ Fimport numpy as np
    ' T' [+ l# c! J% N1 J7 A) simport pandas as pd
    + l5 C& n* U) D7 @" C1 D( w  d19 b& y$ M7 Z- z. J% d+ F
    2
    ! l7 \8 ~8 ]8 x5 b0 }3 O
    2 U* x- `, c- V) M6 d. o, Z$ s3 `: x: p( V$ [  V2 P9 S
    10.1 时序中的基本对象
    + u# x# d/ y( }  时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
    " _, M; b. O- J+ A4 S  `+ v) d7 O/ v, `9 ?. l9 F- s8 |: k
    会出现时间戳(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的简写。
    # w2 U( y* z, G/ A' G' N
    " l4 o$ Y0 c4 }# B& M会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。5 V$ u$ R( N: ?0 b( m1 R) A1 a
    ) M1 y. v! _- W( d7 I; i; A
    会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。' V! [2 o# V1 a" q4 u% l3 K

    7 o6 ]# a* p5 F. M会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    2 U# r' R# _) f4 g% ^1 t0 `
    8 q2 r9 C- U& U9 i* G  }/ o3 ]! {  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:( ?) x; ?! P- Y* g0 e! h
    / F- n9 h4 u" |0 \$ j  p
    概念        单元素类型        数组类型        pandas数据类型( E) e5 z: q: v+ s, m8 X, A
    Date times        Timestamp        DatetimeIndex        datetime64[ns]3 c  g( i( O% _* Q: {; o; ?$ T7 {
    Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    & w" Z, F# y' {& vTime spans        Period        PeriodIndex        period[freq]: _% g: s+ X1 s3 {$ Z2 ^- `; ^. m
    Date offsets        DateOffset        None        None
    # y0 y- v4 j# z: a( ]' r8 l$ a  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    3 Y, y' V' Q7 [) I) {+ n- r% }/ f  s* z) T8 L0 r1 H+ {
    10.2 时间戳
    4 g$ w, o% g8 j10.2.1 Timestamp的构造与属性  U) T  _. M; ?* _
    单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
    ' b, Q! r1 L+ C9 ]: |* k; h+ ~  D5 h
    ts = pd.Timestamp('2020/1/1'), C# X5 E4 o; y1 m
    ( }( k% U4 {! d" J* R4 `3 c
    ts* a$ G# n7 t# J8 D- a4 j# B
    Out[4]: Timestamp('2020-01-01 00:00:00'); B1 O5 @# ~6 L/ @& G

    2 y( Z/ s4 d2 @7 {* Ots = pd.Timestamp('2020-1-1 08:10:30')5 _3 _; f( m; f# v8 F- ]

    - C- v* P) r2 O3 Z, @' G; Dts
    ) U- y! D9 l# Y: n& ?/ vOut[6]: Timestamp('2020-01-01 08:10:30')
    " B5 N0 _, z6 f. }- v18 S: j" K5 b' M9 ?' C1 Q& Q. H, i
    28 v9 z/ e8 N1 `) ]
    3
    * t* M# K/ w5 l0 p' A6 n4) G# ^* C1 N9 `6 p
    56 f$ l, ?; S. u7 v: i
    6
    8 v1 Q  k: g" O7* ^( h; g$ r# d( M9 [6 e; c+ V( i
    86 V  k7 Q6 o& q, Y2 S- H3 O* g
    91 q; ]/ |2 Y' J4 ]. k
    通过year, month, day, hour, min, second可以获取具体的数值:7 b8 ]' Z6 w3 Y: b; l4 _

    3 x3 x6 m9 X! Q5 d: W- Fts.year
    ' d3 {! ]3 A0 U# U$ eOut[7]: 2020
    3 b% h! _5 N% [- ~: h
    0 M# f+ l! [4 y, x9 L4 Ats.month
      j7 f" k: O/ l5 Q5 G( lOut[8]: 1: R: |* v5 s# I; w

    3 e  ^' F% o2 F3 [ts.day
    2 t; o  Q! W) e4 h) s# hOut[9]: 1% u, u. r$ A5 F7 @( K

    % P$ z& q/ b# e" dts.hour- n1 Y' F; `9 m( B) H3 t- e
    Out[10]: 8) q) a$ E  N( R2 o) h1 A
    - ^* B( P+ q/ ^( d+ L! r$ [9 L
    ts.minute2 t1 f& K% Q0 j0 a: B
    Out[11]: 10, |; J% D; j! M  Y9 q

    ! I5 F5 K9 B$ N2 q* u/ \- H9 {ts.second7 D8 n* y1 r, N( \' q
    Out[12]: 30
    5 k9 [4 {+ @* d& P0 r2 E$ v" q+ n7 n
    1
    2 A5 o2 P' C) B  N! D- L2 s8 ~2$ v  G* a( S) Q. N
    3
    , |* p, `9 v9 Q4
    - p2 i) ?: M& a* h8 |/ \' i5
    - X/ P& c, T4 ~9 ~" E, E6. @/ e# E( x8 e7 S& d7 r% V' D2 X
    78 y! x# e2 G& O7 y$ L$ J- O
    8
    3 F4 o6 d4 k" B0 y3 M: \' {8 A9 S$ D9
    # q7 F4 H& s$ u! D# G# z10
    ( J* S& d) e$ `! d6 S/ g  V4 f) A11
    9 J: N, a; T6 A12
    9 g* v1 [1 N# ~0 }7 L. i. M13# T- i$ v% }2 ^* ~4 N1 C
    14
    & t3 H  Q3 P2 J! }: s; J15
      ~5 n6 T" \- S16
    : c, ^# \& G* y7 S( l! A17
    3 X  h' [, Y" {- v# 获取当前时间$ k7 Z3 S) g( w$ K1 c/ w
    now=pd.Timestamp.now()  x1 z$ y- b" L, ~+ E
    1
    " l8 n2 Y9 p% ~5 Y- L; V$ I2
    - Y! \( c7 a' g0 y5 A  T7 {0 b* G在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    ! Y6 {- y9 u! m: FT 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)
    - A3 n# d' D0 Z! X3 m+ @. bTimeRange=
      Z; ?. K1 l$ u10
    3 e: c' U# c) Z3 H9
    7 V1 W( k7 ^: z  j) a8 f+ F, T' N ×60×60×24×3654 [# }. B/ s5 k7 s* v
    2 : Q9 a3 S, M/ l' ]" H$ w# U) d7 M  W
    64
    2 |0 ~9 A+ h2 M8 B0 T6 W) V1 h" h+ u* n# [- V4 T" o: S' l1 M

    8 }6 r6 N- J) z( N9 F; l. e: y% K ≈585(Years)
    . T& V' j, K. i( I& x3 \* g+ u7 g' k$ L* [2 z1 p' T# o9 i- o3 J/ I
    通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:6 J4 o; A7 {" l( d

    3 |& k+ s% i1 c; P7 Zpd.Timestamp.max2 s0 ?9 q) r- Y: p% M/ R& e
    Out[13]: Timestamp('2262-04-11 23:47:16.854775807')
    & b7 [% J% F  \* s* z9 l! G* z3 r$ _" z$ O
    pd.Timestamp.min% p0 H2 ]+ x' ]: l, o/ z* ^! A
    Out[14]: Timestamp('1677-09-21 00:12:43.145225')
    7 @  e$ s7 ?# ~6 O3 F: c7 b5 u* k& H6 [5 z( V8 j, T/ @1 _5 N
    pd.Timestamp.max.year - pd.Timestamp.min.year6 x1 G# U% h4 p! s7 ~. `% h8 g
    Out[15]: 585; m7 P; t9 Z4 w1 Q& h$ B- C4 {
    1, l$ d$ \3 A$ w0 v# [% q1 }7 |
    2
    ( L6 X6 y) r7 e- q0 o3
    6 p; Z1 h! r8 _9 o. F& t4
    5 U' [! q7 K! M: t50 v5 M, c: K% V7 v' }8 U
    6
    ( K% D! j7 N* c. k7( [' v2 X% a  Z# D6 r, w
    8
    ; j2 J/ j4 w# ]10.2.2 Datetime序列的生成' H" j2 T2 P8 ]% e. s  J3 q
    pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,; Q; Z$ O. |9 [( F1 k/ K
                                      exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
    4 N" K9 [0 k1 G) G9 E1
    ; b6 m1 h/ Q1 A: J) u) p& d, R. ?- d2
    : E7 E. n9 P% p# [9 g# ppandas.to_datetime将arg转换为日期时间。  D6 X' A4 }* x. V0 g: _7 V
    6 r/ y9 L9 D5 R
    arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
    % a2 m! p2 P+ K" o9 Qerrors:- s6 G7 i4 `* t9 K$ n  U
    - ‘raise’:默认值,无效解析将引发异常( U1 M" ~: T5 h5 V1 g/ N
    - ‘raise’:无效解析将返回输入
    ' L5 e7 |# Z& |( c8 G. T- ‘coerce’:无效解析将被设置为NaT
    5 n% L  j7 Q* a+ m. L) A9 g1 ?2 g+ zdayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。
    ! c0 F8 M- s7 `0 z3 ]: syearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)* [8 W; i/ q5 X. u# X
    utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档
    8 j1 h5 T: _, Q$ fformat:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
    6 f( _  c$ n6 {4 c) U. i. i7 J# W9 Funitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
    1 R8 i! q) h9 a3 z) I$ hto_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:" t( y# Q* g! I' y) D6 e8 \+ l. P
    pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])) k7 B3 r& H; Z. N1 t# y5 l

    8 K. N$ j3 Z' l+ P$ w" o  QDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
    ) _! V* v4 i' D1
    " t% y0 r1 O" E% L. x+ l* ^21 g7 M( u7 j+ b3 Q$ ]
    37 M1 d1 k# ]4 j* {6 m3 D) s. e
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    # w( j7 E6 W4 T2 p; J- G& @2 n. }  A2 |9 M& c+ H
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')2 e  Q# y& K. G( s
    temp
    - @* n2 ]0 S5 [, v2 _8 o( c1 ^( T$ `4 n/ [. Q! s
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)# ^% V* @4 v9 g9 l, x' @) j
    1
    / N! Z- D  H& B% V3 t9 q" D; ^2
    - a2 h. e6 N& B- t6 q3
    9 d* T2 f% H, U  {4
    % L9 Y* \, Y8 s" E6 S* M% I  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:8 I4 I" [2 q2 g0 ]9 a
    ' M0 ~9 b: _5 \# t' ^4 B# v
    pd.Series(temp).head()) x, y0 z, c: s, l; [

    * g. i7 `" [- O# v5 b) T0   2020-01-01" }; N& m/ H  |
    1   2020-01-03, I% f+ k2 q% G4 w& F
    dtype: datetime64[ns]
    1 d  b& c" A) x3 a' W$ y1: |8 @' G* P/ j8 u7 K+ k) ~
    23 |- Z( }3 t% Z! u, E# z4 |& d  U
    3
      {5 M3 m# \( P  R+ P44 O- R" |9 }, L  G  [9 p4 h3 a
    5
    4 G4 k- [! d) p0 y( |% r下面的序列本身就是Series,所以不需要再转化。
    % y7 c+ Y* P1 q6 i* g0 W! F# n( {% o8 w) A* g, P8 H( _+ D& l
    df = pd.read_csv('../data/learn_pandas.csv')
    # R" V. ]2 E( A' a+ hs = pd.to_datetime(df.Test_Date)
    " G9 {% j- c2 ]# t; s) O7 Ds.head(): d9 t2 k5 n+ f( i
    0 T9 H, s) ^0 M* x
    0   2019-10-056 V2 B  D* i8 [- I* x
    1   2019-09-04
      ?! H- [' S; G9 C7 g2   2019-09-12
    8 ^% Y3 l) V1 h3   2020-01-03
    & [! A; J# I4 x2 }" R# Y, y: j4   2019-11-06
    ! D- `; i$ D" m0 q( i9 qName: Test_Date, dtype: datetime64[ns]) R8 K2 E% _: p2 e& Q3 c# q
    1
    : ]+ M& ?2 R5 Y20 R( I  Q9 _. u
    3! |+ s# {* }7 T1 \
    4- o8 g$ N- X% p! ^
    5% T* l3 c: K( `! v& Z
    6& D: L, G3 O) |/ T; H
    70 v4 F* t8 |# c8 y* x
    8
    ; Q2 n1 F( Z7 j2 F7 [9
    9 s8 e! r' i" W8 T. x  d10# Z4 I4 Q+ O2 ]. o$ r7 T
    把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:1 E/ j. \6 V9 x( I1 F
    df_date_cols = pd.DataFrame({'year': [2020, 2020],
    6 j: L1 A$ C  y4 n, ?4 y1 p                             'month': [1, 1],) H% k3 \  j3 i" C1 Z  Y
                                 'day': [1, 2],
    8 O+ m' G# B6 |8 r  \% c                             'hour': [10, 20],8 K* J/ V: ]* b, X' j. v- ^
                                 'minute': [30, 50],
    8 Q9 T" Q9 ~  _- m  Y                             'second': [20, 40]})! N4 q$ p8 x9 y4 B! M
    pd.to_datetime(df_date_cols)
    & X2 b3 `3 l. [1 q. O
    9 {- {2 Q0 Z8 u0   2020-01-01 10:30:20
    " ^% b8 F0 ]* i6 {9 L- g4 i1   2020-01-02 20:50:40
    ) g- k' k) K: _2 gdtype: datetime64[ns]
    + o- Z6 B( X' E1  s( S1 R/ v" H; o1 V) T0 M. c
    21 k7 C' I6 o* d8 d3 K
    3
    6 ^8 a6 X9 }' e" J8 S/ l) J4
    ' t# G+ k$ |# w5 i* S3 b5
    $ G; W$ d$ ~/ ~; D1 M6
    0 i- [  P( S; n$ ^& k3 a7: }( b. ]. Q4 M) U! n
    88 L1 v4 b6 B; a. i
    9
    # k" j1 U$ {! K; Y; j! a- k0 `$ f. f10, R) N8 T& V) l- D. X# ]
    11; D1 ]' f' ^2 O2 X+ a, C
    date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:" V- ~& s, Z4 M- O5 h% a
    pd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含5 J7 T$ y. g6 Z3 z) }
    Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    / `$ L+ J! k/ n4 o
    8 _) y% t" w( w7 @$ d; Apd.date_range('2020-1-1','2020-2-28', freq='10D')& @6 U& o" y9 e' Q3 u8 l/ R0 m  C0 M# C
    Out[26]:
    ' J+ O6 f1 V5 I. I9 S2 A% tDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
    $ K6 V* K! B( n! v5 F- [9 d% F# M               '2020-02-10', '2020-02-20'],
    % S. V9 f: q2 B# N& w, |              dtype='datetime64[ns]', freq='10D')
    ) H0 \1 H0 I3 @0 w) T( r/ |5 `) q1 N
    pd.date_range('2020-1-1',
    5 D. L1 D4 ?# w* i) Q( b              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
      w3 h. o: I; [2 ~! H- ]/ d# Y1 C0 ]- D7 a
    Out[27]:
    & j7 S3 m5 x9 ]! U. ZDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
    & y+ n# c3 a2 D- r! K7 _               '2020-01-24 04:48:00', '2020-02-04 19:12:00',2 f% b' t- P: N( M
                   '2020-02-16 09:36:00', '2020-02-28 00:00:00'],; x& O+ h0 ]8 P: h) T* K
                  dtype='datetime64[ns]', freq=None); X! X' Z# _" M1 x4 I8 _2 S: E
    , |; |; N* A6 c. G0 Z  _, m6 ]
    1  ?3 S  u5 M1 ~9 J1 z# p( M0 P$ q
    2/ S' |* l8 Y4 F0 `( D- `& X! O
    3
    8 m: m" G/ [/ |' H5 l0 I4" R& q/ d# X1 _) \
    5
    ! y4 g" p5 m5 D% A60 C- Q2 a  j. \* H2 l) w" D6 j  y
    7
    " {# {3 y* q, c+ O8 M8
    # @- r4 b1 B) q) |" y! ^- {# ]- Z9
    : \9 k2 G) F7 o0 z10& Z* x2 N6 p, y/ B8 @6 ?
    11
    4 d& D$ S' n& i) X% B128 U. T2 }* U5 N" Y( i" E
    13/ P- C: G, n& ]
    14
    + g" @; V' ]( S0 E* X. \* j159 k1 p/ t7 w/ U# p$ C$ d* q$ H/ _2 D3 u
    16. q) [6 L9 H, p4 z4 {; `
    17; k3 i7 g& E9 z! Y
    这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
    . p1 K! K: O5 b' T1 A8 c" c
    & x  M( H) n/ A2 [; a【练一练】. d0 |* U4 O* d# o
    Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
    $ B; Z( Y1 e2 t: k. Z
    ; s0 ^2 }7 ^+ s# Zls=['2020-01-01','2020-02-20']
      a9 P4 Q" P, M# q6 Ldef dates(ls,n):
    & x* D* E* `- Y, X    min=pd.Timestamp(ls[0]).value/10**9* C" _% w1 [5 a1 M8 b% J
        max=pd.Timestamp(ls[1]).value/10**9! d) t. U* c: y2 t
        times=np.random.randint(min,max+1,n)% n8 }5 `+ G( P) Q  d, _# }( E
        return  pd.to_datetime(times,unit='s')8 d3 X. P0 |# O6 {2 @1 n: r$ J
    dates(ls,10)
    1 z2 ~; o$ w2 K/ ~: j" d
    3 j$ ?" W/ H/ U; g1 ?DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
    ; m& C' W! d4 P5 E6 S4 e7 ?3 C& I               '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    4 O- I& a# y; a               '2020-02-15 00:18:33', '2020-02-11 02:18:07',& x9 y7 E2 X' |; M
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',
    " [& }1 n9 ~* C  X. |+ \  _4 T               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],0 e0 L, n1 F# _2 {  k$ C
                  dtype='datetime64[ns]', freq=None)/ w/ C& z: _' r3 @
    16 \5 W7 O/ H* z; t5 r' i, f
    2
    0 G2 J2 Y' O; x6 T$ L0 |+ u. F3
    & j' ?5 K) a$ C" P& c4
    8 {. k/ [+ R2 Q! C1 _5
    + z9 e" e) [) J6
    8 O5 V! e7 y) `7
    # P# L) V7 ?! w  @' Q" ^8: c0 L) C% C& K  N/ }4 ?
    9! P. I& E0 n4 w! A6 v* G
    10$ S  m7 ~8 ^5 ]2 |0 E& e6 P
    11
    6 i' \& |5 q# F129 f/ a, S) _( \% h1 s! t& |
    13
    ( k$ z+ x0 U. v0 Q141 z, Z% t' {+ I; U! W
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:. V+ \3 }$ x" I1 R+ G
    s = pd.Series(np.random.rand(5),1 h8 v4 O! V) ?
                index=pd.to_datetime([' j4 s2 }6 X! K) t
                    '2020-1-%d'%i for i in range(1,10,2)]))" E3 K- w, _6 x2 C+ y4 h! v- c& P
    5 U% `$ _! K6 r9 O& \$ K% p

    2 V1 h- A4 t7 s: W& T5 }* a* Z4 is.head()
    9 i4 j$ [1 R+ }2 @( [" FOut[29]: * Q0 r6 c! z) j" P( x$ ^
    2020-01-01    0.836578! l) t3 i$ n. t# X
    2020-01-03    0.678419
    7 }1 y& O8 S6 U2020-01-05    0.711897
    ! u! M- L( ~2 |. M7 [9 ^2020-01-07    0.487429
      `. g$ Q0 m7 a& L5 x5 l2020-01-09    0.604705
      ]1 I2 B" Z* @3 q7 pdtype: float640 G/ g' G. q7 X# }3 l- \9 u: ?
    ( m4 A2 M) L5 R2 w* [, K7 [9 ]( r2 @
    s.asfreq('D').head()/ g! _5 m- [; W$ q- d7 r
    Out[30]: 7 }: F: a; x. K: D
    2020-01-01    0.836578" f9 B; v. |8 O) L& Q8 ^
    2020-01-02         NaN+ a+ U* O& ~. ~$ J' g% T( x
    2020-01-03    0.678419
    # V% m9 k6 Y* ~2020-01-04         NaN. @/ Q/ e$ N& b/ ]  B
    2020-01-05    0.711897
    4 ^" C* }7 d' M; wFreq: D, dtype: float64
    8 {8 {! R0 q6 X" s& L- _& K% M; K8 u) M7 p" P( [5 C5 W
    s.asfreq('12H').head()
    & A" `3 K0 k- p- _0 @! j2 U5 LOut[31]:
    ) m5 x6 C4 [4 k2020-01-01 00:00:00    0.836578
    7 w9 V6 B% T; x: B" i) J3 k2020-01-01 12:00:00         NaN" A: Y! P1 N& u& L, a
    2020-01-02 00:00:00         NaN$ k1 x$ a0 ]8 U( V5 V
    2020-01-02 12:00:00         NaN
    , H5 s! D( W" F* I2020-01-03 00:00:00    0.678419* K2 D0 v* D5 u
    Freq: 12H, dtype: float64: r( N- u1 S' [  @
    & l) f  N: q% w% ?
    1
    2 `9 ], C7 A- a2
    3 }- F5 O4 F9 _" R" ^) _9 U3$ b/ u1 S# y0 v) s3 k# k: H
    4
    ; w7 N7 z& O: c8 @5
    : X0 ^0 J, Z9 W1 k$ \. v* q5 ]6, p/ _0 C# m' E; l
    7
    : F  t0 {: D% u, S! e9 F0 V3 q8: F0 p! K! m4 B+ R8 [+ ~
    99 L+ k: h4 o) y4 j. w5 z
    10
    + s3 C9 t3 X8 l4 ]; h9 u11; C3 E  s: @) D/ |; w
    12% o6 b: N) k  q: C6 M, w
    13
    " ^1 {, [8 q' _3 y( I( [7 O9 z% L. d14- V5 w- t9 t* D1 ]9 c7 D  e
    15
    0 u. X2 ^0 Z9 X) {16
    9 q8 W/ c0 C% `" }7 ?17
    / W$ n  V. r" m2 m' X18
    # T  y/ z- S. u( Y0 @191 v9 f4 L3 J. b7 u, u( L- h6 a( j* ]
    20/ y/ a5 @+ V1 L- e
    21- }0 v) z2 Z/ N" B/ y2 S
    228 }' [% A9 {: G0 D$ U# i6 Z
    237 O# g% g9 B( z' `& l/ O
    24
    * q+ X! k# A( d! `. |+ w* M/ G, L25
    8 ]) Q( s; o4 s26
    2 t  ]1 q  S0 z* j27
    ( |  w/ ~4 U6 n+ |( t6 n4 }28" b' F# }9 t  x+ o
    29
    % `8 r2 v: w  g% C4 S8 E' Y30
    4 Y, _) O, Z. i; n315 \( D$ o5 f( A/ z/ N$ k
    【NOTE】datetime64[ns] 序列的极值与均值
    $ n1 `' [! [/ i' i7 Z0 A' i4 q  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
    + Q; m4 N+ H  L8 j
    " t8 {! r: @% F* w; o* k% c9 G10.2.3 dt对象
    / U$ d# U; G; ^# i9 n  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。
    ( v# N3 r# A6 {& k/ [: i" Q8 I0 Q9 Z" j0 j! H: C; }6 E
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。+ s1 X6 ?9 u% W4 |6 e: n- @, x# j
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
    ! Y& A0 ]6 `9 K- ?, X
    / m( _# N8 Z$ Bs.dt.date) R1 O, ]2 ~( o0 b" h0 y& T
    Out[33]:
    0 Q; c$ Q6 i! t) Q. t1 C/ E0    2020-01-01" }) ^. E7 T: {
    1    2020-01-021 t0 ^6 L1 l; V" U: w" I& Z
    2    2020-01-03
    5 f  c% j9 h, G' tdtype: object1 p7 v7 N0 b5 ]7 d- J# }0 f
    4 p- w6 G6 l2 e" l+ y4 Q6 v7 S/ @3 D6 T9 {
    s.dt.time
    & D2 E8 _/ A+ ]) p4 TOut[34]:
      B! [7 d- i9 [5 _( ^1 T7 r0    00:00:00: ]; T1 E& t$ E2 o# h) r9 N
    1    00:00:00- ?. c, z# O$ D/ V
    2    00:00:004 @, c6 }5 S9 ]& m% x
    dtype: object
    & |2 q0 k0 M# K0 o0 {
    ) B  E3 b4 q1 g, C6 V3 ps.dt.day$ s/ y- x7 j- L
    Out[35]: ; Y) a9 T$ k& I5 e( X
    0    1" g6 M! E- W  W( c* N
    1    2
    1 ]5 f) Y3 [' g( Q8 b2    3
    . {0 z3 E- E& G- G  qdtype: int64  D4 x! m$ D3 L. V  Y) R$ ^6 f
    6 m) F5 [! S" E
    s.dt.daysinmonth* Q; p0 f# a% @2 M+ X! s& x& U6 C
    Out[36]:
    ' t2 d' u( Z* }6 T& j0    311 \$ E7 q8 ^# Z9 h% M$ e: h# c+ k
    1    31
    ) ]. G5 \" ], S3 |2    31
    : x- N4 Y6 q/ \" i7 C4 udtype: int647 M3 ^, `2 k' _7 Z2 X7 f7 U

    ; H1 q4 V2 s+ u4 c3 q7 t# n* L7 m) f1
    7 p. J9 p- X" a; Y5 A2# s. j1 K8 y' I9 N
    3
    $ P, t; [* ~6 }4 y9 ?41 j+ |- d% [4 ^7 y/ D
    5
    $ y: Q$ P& `$ z9 W) v6. K7 z) d" A2 J! U5 D
    72 Y1 O! ], _3 O7 f+ w
    81 ]6 N+ o4 ~) _+ ~. C# }; `
    9
    5 A6 L, F# D  t) ?7 r. N) s10
    . l. ?& V4 I3 N3 g) ]" o, I11
    & n; Z+ w) C& H) j6 _12& T) [3 ?4 A6 E4 h, p
    13& k! t( c1 r: T* |  I5 A
    14
    5 [- e, _1 ^3 X* L8 R" c15" H5 G+ S& _" R- e
    16; r: E5 ~  ?' [" x! `8 L* x
    17
    % B& Y8 ?/ I5 l: c9 ^7 [* }; i18
    ' e  J, y" w* z/ Q- e( ?  [9 |# Q& a19
    + A+ o5 r- x5 ]4 X( o207 ]$ C$ d8 w6 g( U  o( v* ^+ h: V* M
    21( r: T# W; U$ x, t% @
    22
    ' D# W. x. n& B( ~# B5 a( h23, P( A/ C  C, J; z" ~- m
    24) C1 U- J/ i' P
    25
    * {. I2 ?0 m" y# d7 H; g$ t26
    ' }) @( }  L7 h. C: Q27
    , A4 H- B+ V8 N* g) g( y28
    1 m1 c! T! r) X' g" Q5 `: V! E29
    + ?4 M0 x* ?( w  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:- d& r' y& `# k6 `% _
    " h. b% C) Z! k
    s.dt.dayofweek  I  Z9 a0 t) o- Q
    Out[37]:
    % j) ]1 a; }& S: x) R" w' l0    2
    3 b3 T; V. I$ _$ f3 {1    30 I! g4 @" Z4 [+ ]( _  S: w
    2    46 w5 W8 U+ J5 U7 K) R  f" Z' _+ U
    dtype: int64# t; ?7 `; U6 L7 q  g/ p
    # w; R! B. S. I5 d
    s.dt.month_name(): d9 a- }6 z. @+ v6 ^7 M6 g
    Out[38]:
    , Z$ G# B, x, @1 r% y6 k0    January2 V$ D; M# A% x. W" O
    1    January2 U0 |. t# z  k1 ^
    2    January: }' n1 W% ]0 s; D, x  p
    dtype: object
    4 p  P4 e6 M: C/ ^+ j' g% _' V. }8 U0 u# b! R( l  m( W0 ]) x
    s.dt.day_name()
    9 P% D4 l% `( {: L. t$ ?Out[39]:
    8 ^$ G3 b; E8 R$ Q  i0    Wednesday
    * z# R/ n5 g9 g4 J% d: \2 ^& y1     Thursday
    2 c' g- }+ W- z1 v) Q" d2       Friday* d4 c. m: _) v! k3 t. S& X3 p
    dtype: object. m4 _1 x. Y: P2 D; S( k

    ( s2 _; I$ B0 {1
    + v: s: S. a; G/ W# ~5 ~0 S: u2# i8 A+ [/ i1 w" `7 {( J
    33 ~, S1 U  S; x+ K; Y* C
    4  {7 M$ ?, _2 [$ i8 m
    5
    3 k, R) t: C# h! ]2 p6) h5 \# e8 [# z
    73 o0 k# y1 ?$ L  |
    8) Q, }% ~7 A: X2 z5 \
    9
    9 h$ B, F  [. Y! X$ z/ Y" A% i& x2 g10
    , E$ ^+ s! M  }: o4 o11
    / u. G8 q* N$ P" @* p. Q0 r% f12
    ; ]/ s1 d; B1 r+ |7 H3 V& V/ L13
    " b6 P7 N' Z7 Z- R6 O4 J& U) k) W14- Y; W; y  X$ V- y; |0 s
    15
    4 s* X  i" R6 q& }7 G16
    7 k- S0 c4 T( q* m) j% }17  L9 r& w7 j& Z
    18' i- [3 I  K; c, ^* H  \
    19
    # ?  A+ E+ `9 ]20
    $ H/ _' D+ q. f6 W0 y( P第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
    + S8 f1 y2 F: [* ?! cs.dt.is_year_start # 还可选 is_quarter/month_start
    $ J& H$ G! z( y5 v. COut[40]:
    2 d( B3 y7 q6 p( O; f1 O  G0     True2 s1 X1 r8 g8 l$ _
    1    False
    ; p+ a& `2 B4 _! q0 a2    False# q4 M" _2 ^7 [. v: ~
    dtype: bool& O  a7 Y& ?* K$ T

    5 f* [3 `3 f& }3 H. xs.dt.is_year_end # 还可选 is_quarter/month_end" A$ {9 w# E3 w3 P
    Out[41]:
    + \" w7 V1 F- u6 I0    False
    - Q+ P: _0 O" L& c9 V- n0 O1    False
    : z# u) H3 u! L: j' W% ^2    False
    $ B+ }  ~# w3 r, Cdtype: bool- d; F5 y) j" ?2 ]
    1
    " |6 d  q9 i0 v2 i2 g8 i- ^9 K7 R2
    1 Z2 }. K$ C( c- B, J0 e; O% p3
    8 J$ Z+ ]8 n$ |. C4
    6 s$ a+ W6 y" \# `59 Q6 ^% H1 P; y$ r8 X
    6' D8 [: e9 |! @9 N8 O
    7( C, P7 R4 D) s' y' I
    8
    : W! R7 l6 H* y: Q( Y9
    " v' Y# b0 _' U103 L" {! \. i. b- I$ G
    119 W# [1 F" J* u3 |2 k8 M3 B5 @
    12' [% G, K- Y0 D: L% e6 u+ J, m* v- W
    13
    - \" Y* h) t) S* U+ H2 L第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。0 d- ^2 m3 u) L! ~9 r& H
    s = pd.Series(pd.date_range('2020-1-1 20:35:00'," L2 f  }. }, a
                                '2020-1-1 22:35:00',% x$ Y/ f2 z+ N6 r  @0 V
                                freq='45min'))4 g! {, n7 E* m) x

    " `4 q# z' C& c7 {* i
    , N3 T/ Y; r( us
    2 k+ M1 [3 n6 V7 q& p/ iOut[43]: ! l1 x" M7 f4 b9 O5 {3 U
    0   2020-01-01 20:35:00% L, a; [  v& \; I
    1   2020-01-01 21:20:00) z* z7 g. A+ K  N) m7 Y, ?; r
    2   2020-01-01 22:05:00: P3 {/ j# n3 X  t, G
    dtype: datetime64[ns]
    " |  G; u  D2 }4 R  u9 \. @5 _$ M& i9 g( ~# Y
    s.dt.round('1H')
    * a7 @* b7 P; Q  sOut[44]:
    1 i+ y% l( H. W3 f) ]' _+ R6 E2 S0   2020-01-01 21:00:00
    / q- H- n8 p4 u& H- ?/ ^1   2020-01-01 21:00:00
    * `" i/ E: Y5 ~/ \" \+ k9 E0 M2   2020-01-01 22:00:00- o- I: k' q& M
    dtype: datetime64[ns]
    7 n' N/ Z6 i7 v! ~3 z. @
    9 d% I1 I- h$ H: y. qs.dt.ceil('1H')
    : s& M9 R4 Z5 g) DOut[45]: ! a; L5 C; u% k4 A; h
    0   2020-01-01 21:00:00
    ! H$ D2 K+ `# _3 T: \1   2020-01-01 22:00:00: \) L: b, G0 H+ _8 Z+ }7 e
    2   2020-01-01 23:00:00
    & V" T/ G) m' vdtype: datetime64[ns]" B  _. G; k2 P3 ]4 E! ~
    0 u0 a* l- k. G3 H+ W* R3 \, Y7 h
    s.dt.floor('1H'): P) D; o3 Y, [$ ^8 e
    Out[46]:
    $ W4 S; W" X# e. M0 v0   2020-01-01 20:00:00; _+ a$ `$ |+ B" K8 {
    1   2020-01-01 21:00:00! ]5 Z% \4 z4 o
    2   2020-01-01 22:00:00
    ! a/ [8 x  N. [9 Ldtype: datetime64[ns]
    * f+ R- |3 Y- \! j9 F: u9 g. q7 I+ }' ]
    1
    " [* Y, Z1 b  W- \$ B) T2  m- M) J, o& D1 o
    3
    ' {' L/ O; s. F5 o! h7 G4
    - U$ ~5 x' p$ i& i. Q$ k; C5  C  }% C1 V5 q, J5 n
    6
    9 X+ o; s! v2 U" `" ~$ x0 G3 s71 u$ p" }' d! {& b
    8' F# f8 f% {& x2 C! c
    9; w/ }! `; M# b0 @0 f' e  S
    10
    2 k  L. D; O  F9 p: z11. u+ ?$ I! B4 M! n0 h5 h
    12" b2 u$ p) k# n, i8 e2 ~
    13
    + Z$ m) f9 j1 r14$ a; I' c3 Z9 {5 r+ g! i+ H
    15
    9 H9 G* s1 G; I  \16
    " M3 d) _% Y, F+ w( O& {17
    8 H1 J- J& L/ M( h! V9 \( L9 q183 Z7 H, z* Z8 x: h! d
    19  y. @! u# S. T( n, }5 S
    20
    " x; ~- @) Z' W$ r2 N21( q# s4 V+ k3 H7 k& q6 W% J
    22) [1 ?- ], p' o+ y! h! i+ x/ j
    23, ^" k4 l) \! @; D2 B7 ]4 j
    24
    ) Q8 J" a8 `1 ~  r& ]25
    / g6 w3 U8 w/ v# a8 W) i" n26' c  _  }8 V/ W( R
    27
    7 o! g/ u: T0 a" d, K  `28
    - s8 c; Q. U8 n1 i( ?29
    8 i, T. y% I: O& p30
    + S5 A9 Q4 U/ ]2 w% i+ A31
    0 o. [9 m/ q* R6 Z32$ B/ f8 X- J+ s; h" W# C' g
    10.2.4 时间戳的切片与索引
    * w2 p4 s2 K- m6 h, }0 c  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:5 T$ |9 m9 n  d7 M# Q/ O8 |
    - t; Q. w# g5 y* T+ ?; |# i; S9 ^
    利用dt对象和布尔条件联合使用
    9 y% N) b2 h. I8 f$ d1 A  K利用切片,后者常用于连续时间戳。
    1 p+ p" h. V% M6 d1 Hs = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))& E9 R* v+ }9 v9 q
    idx = pd.Series(s.index).dt
    + o5 x+ e4 y; C) L. J$ Qs.head()* X) P+ l1 e- h2 M3 ]/ l1 ]5 M
    + Z5 N) n1 p& F9 J4 R. a! _. E7 z
    2020-01-01    0$ s" o" c7 Y/ r4 W0 ~* h: S
    2020-01-02    1; f; L- u! _( Z4 F! q) [/ ^" p7 t& N
    2020-01-03    1
    : ^% j( m8 k( F9 n4 b; X9 b! w1 q2020-01-04    03 Y! v: n0 m6 I( d# b. `# x( Z4 D
    2020-01-05    0
    * }: r7 d% s- c: FFreq: D, dtype: int325 D, B' m; }- f  l
    1* S$ O& G7 r3 N( l, S% R
    2/ I! P9 T5 i  {
    3% X0 d1 \" ?8 d. e# w1 C/ A
    4, }- q1 _$ C( [3 {
    5' q, V4 t3 g, ]7 @% y! J
    6
    # N3 c4 t; R1 H7 M/ `, R: C7
    % C' [7 ]( Z9 c0 ?8# a8 q7 U/ c* ~' j9 W: o  F  r
    9
    . h2 F" n0 g4 b* y$ S1 O10
    ' E! A7 a/ S% b) A" XExample1:每月的第一天或者最后一天; z# \1 a# U6 T. k. F

    9 X3 u, I; M! c; y$ O( b2 W9 Ks[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
    1 g& Q0 N3 k% j- y" y- c( ?# hOut[50]:
    2 I5 g! I  G9 l0 n# J2020-01-01    17 K9 x% L! m! F; @9 D) o, G
    2020-01-31    0& t: U+ j; r" ?/ [
    2020-02-01    1- h+ c# \5 E% v! _: C
    2020-02-29    1
    ' u1 O8 ]3 N5 Y$ @+ N2020-03-01    05 \! Q. O1 K+ n4 h2 V0 Q
    dtype: int32
    8 p8 L: A5 F6 p, k13 H1 |7 c0 ]3 w2 d! ]
    2
    $ d2 ^' f6 m6 N3
    9 W8 c, W/ V: ^! B: X. \- H; o4) \! O  t! c, u  o8 B. x
    5
    ; ^8 T2 p* F% I4 C68 A5 w* Q! f3 U0 A% q* T5 z
    7! `5 ^0 G# E6 O- @% z/ g  ~
    8% z3 L) w7 b( F- B" o& i+ d1 u. {0 ~
    Example2:双休日
    5 Q. s# L2 }& g8 D7 W
    4 y: Y, t6 h; {: us[idx.dayofweek.isin([5,6]).values].head()
    4 \) M4 s1 o' R' Q5 C( R' }Out[51]: ! c4 y7 N8 s- w+ L- t2 a7 P
    2020-01-04    1
    ( R# m/ a# ?2 x% ~# d+ r* W6 s4 {2020-01-05    08 O& L7 q" X9 E! Q4 X: a- t
    2020-01-11    0" @+ j* d/ r5 h
    2020-01-12    1
    : d8 @! V. {+ L. \9 r2020-01-18    1
    ; w- v/ P" s5 U1 D( G# R$ l4 Fdtype: int32- q4 E6 P. |; t; e' N
    15 E- z& u, R. r4 L9 K& A( O1 p- m
    25 N2 |& H+ A) [2 c
    39 Q* ?$ ~5 n, J
    4' V0 X0 C0 ?4 H
    5
    & e" p( p1 ^9 h6 w1 ?/ T67 b- d* T% }) |( f9 w
    7
    % F2 k- O: B" t1 h" Z. p8
    . c4 ^4 W0 w# I* u5 {4 Q$ ~Example3:取出单日值' o- b% M4 t: I* k- j
    . d7 q0 v& L( E/ o: ^5 I
    s['2020-01-01']
    . {. L$ b0 r) R' p9 cOut[52]: 1. p4 h8 Y9 ~# U- ?
    4 k1 ?% C0 T! B+ f
    s['20200101'] # 自动转换标准格式
    ) }3 q8 b1 x; Y4 `4 e. @2 _Out[53]: 1, [" c1 k; E7 _2 z2 k( s
    1
    ; n9 k6 v/ G; n# x  }8 f" W2
    4 z) B2 `, U/ J) a" H; b3/ Z, n( ^% f# u1 v7 @- e
    40 R. J) i) v7 C2 Y1 J" U4 i
    5
    5 c# e% x8 H% t/ C' z' ?8 e; sExample4:取出七月2 n" \& M: C8 e9 v1 X

    - D+ ^. }* y( y  O2 Ws['2020-07'].head()8 s! L# l8 B% Q3 X4 W
    Out[54]: * C- v$ A: s. u; Y0 q1 [
    2020-07-01    0* Y+ H, W9 g+ l5 |
    2020-07-02    19 N  Y4 g& q; ~$ N
    2020-07-03    0
    7 _6 ]- e( e% d5 y/ x, Q8 x2020-07-04    0
    ( i* U  T: ?- w! r2 K2020-07-05    0
    ) `2 L! v5 \3 ~$ }  J# O$ ]/ `0 P( [Freq: D, dtype: int32
    $ Y1 |7 ?/ q+ u4 x12 @  y6 X! I, J$ R2 b9 M# u5 S
    2* ^/ _" n  V- J3 z4 Z9 r& @" T
    30 M4 {2 ]" P0 w8 J/ `) d& F
    4; c) r* w! O! g8 W4 S
    5
    / q: j! T  p3 A( x- h! s6( a$ _6 x' G* y$ A# |2 a
    75 C( j( U; q9 y' T( e- b% G! p
    8
    8 `9 R; N- d: N* o' XExample5:取出5月初至7月15日" R& |& E5 A. M9 P. C- C
    % l7 \- t0 G9 B* \
    s['2020-05':'2020-7-15'].head()
    % ]% {- R3 Y7 ^$ K! W5 n9 [Out[55]:
    . l, s2 h' f: ]1 I0 ]2 W2020-05-01    0/ f: f4 X  s! L% U. g
    2020-05-02    1
    $ F- N* W6 @/ A: Z* _9 s2020-05-03    0" q' s5 E) k  H6 S2 O- L3 k1 _
    2020-05-04    1
    3 M9 w, c  J- k2020-05-05    1" Z0 a2 U( z( U5 ~4 t; T+ q% y
    Freq: D, dtype: int32
    2 ]. k/ A, L& V4 F0 R* w5 P
    $ ^/ X$ K; i7 Y6 _) l0 q1 ^s['2020-05':'2020-7-15'].tail()5 G0 |7 N) H" e9 y' C
    Out[56]:
    0 |* L9 w/ f& k$ ?: X( O- [3 g2020-07-11    0% o2 s0 w) G% e& P
    2020-07-12    0
    . m; `2 t8 X* ], L+ ^. M2020-07-13    1
    + o3 `3 h; t3 `  S8 Z2020-07-14    0
      J& O! W( |7 E0 g2020-07-15    1
    7 U% m3 }: A2 \5 O. _! {Freq: D, dtype: int32
    * ?5 q! C4 s+ |0 z
    ; m2 x3 s) `  G. c8 t2 @: e1; w( A: c* G: J; B
    2
    * v5 f- f; z* x7 _0 O8 U3# z! y4 Y- U  [" ^- a' p
    4
    . O4 J2 e8 E7 J. [( Z8 t* L9 j, p5
    - m; I5 U" S2 ~+ L! T$ ?6% o+ n9 e3 {& Q1 [! U2 B
    7
    6 C6 c4 r9 ?& K: O  u8
    . _7 [' X8 y. X3 z9 ^  c0 O: R99 x4 V4 }& a5 O2 {% _/ w( [
    10" O$ u7 u! K4 G
    11# q! g4 A  F8 w
    12- Q' n4 B/ O3 P, J
    13. R1 y3 c8 @3 T  l/ ]9 K
    145 ~* |  Q# C, B1 ?& C6 [9 W
    15  X/ p' P. {" k) Q& w
    16
    ) ?+ A7 ]7 H4 p/ [17
    * K$ B2 U- i) B2 x10.3 时间差
    ) x/ H* f4 W  {$ t( y& t10.3.1 Timedelta的生成! V7 Y+ M0 {) {) G& x9 h
    pandas.Timedelta(value=<object object>, unit=None, **kwargs)
    # G1 e  ?0 J$ \9 g/ `  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
    0 \) n+ T% f/ H+ Z- h& i  可能的值有:
    8 z! T$ ]* s3 S! X2 \% J( y: D3 `+ ^
    ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’4 a6 ?' Z4 l( x! S' [% M
    ‘days’ or ‘day’  W5 \$ I  G; ]6 x) A: ^# V
    ‘hours’, ‘hour’, ‘hr’, or ‘h’5 V8 d7 Z' i+ a" n- E
    ‘minutes’, ‘minute’, ‘min’, or ‘m’- ~* N( L5 O! [, J
    ‘seconds’, ‘second’, or ‘sec’' Z. @3 q$ U0 A' Y, L
    毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’- `' U+ e, ]4 N8 j0 m- B& @1 N/ m
    微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
    , ~  a2 J8 H/ v# k- j1 Z8 B纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
    ! |' X; u1 f' q8 s% M, i" M时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    5 [' h2 q; c/ M5 x* @pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')  K+ I8 o% @1 s
    Out[57]: Timedelta('1 days 00:25:00')
    % l0 J/ n) t, K1 O: r. b
    4 L& J7 o/ A3 h0 Wpd.Timedelta(days=1, minutes=25) # 需要注意加s
    1 Z% d6 a8 t; P: d. _) VOut[58]: Timedelta('1 days 00:25:00')
    * ^$ K% k- Z+ N: N$ U% T
    8 Z0 _" U1 f- G9 u/ u8 J) H% d3 Lpd.Timedelta('1 days 25 minutes') # 字符串生成
    . B% j2 ^" m6 X/ e3 POut[59]: Timedelta('1 days 00:25:00')
    7 r8 J, Z  [5 G! `$ @* e% ~% O# F& x7 q& M
    pd.Timedelta(1, "d")
    ' q* r' K5 @9 L! W  [5 a: aOut[58]: Timedelta('1 days 00:00:00'), Z  |7 b. G# e, D; B* u
    1
    : ^; d2 N* w, ]6 @/ d7 Z! z2
    ! s3 S; }6 L' X5 J- ~0 I' T6 U3
    / o9 f$ B, o. \) }48 B& F' S" a! z* U2 H
    5, q  ^/ ^: A( Z3 @9 U( I6 A6 U* {
    6! L( i1 y% P/ i% }* s, |- T1 E
    7
    ! O, ]" w( l' F# F* g9 Z8  q9 i: Q, x" c% [/ N
    90 ~) A  u% N: n1 C
    106 d/ U) ?' I6 p# j: O$ u# t
    11: Y5 w% B$ g+ [9 b0 W* v. u
    生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    8 [9 K1 m6 M4 y; X- ?5 }) X. _s = pd.to_timedelta(df.Time_Record)% m8 ?/ H6 Y8 Y$ K- v2 V4 V
    : M% Z3 `5 R, V* k, q  r
    s.head()8 J6 z( T/ H3 E
    Out[61]: $ ?5 O7 E; j2 o5 V
    0   0 days 00:04:34
    ( ?* U$ A% x" }, B8 c% h$ h- `1   0 days 00:04:20
    & _" _0 s, o# a6 w+ Y2   0 days 00:05:22
    " {1 X/ c1 v! G3 C8 b' H3   0 days 00:04:08
    5 B& K/ m  l+ [4   0 days 00:05:22
    9 k' S9 Y. L1 n7 Q7 kName: Time_Record, dtype: timedelta64[ns]5 i( N4 }* O1 Z! b$ a/ X# Y7 `- Y
    1, Y1 p$ E4 [3 K; v5 K, {+ K( x
    2# }6 O' z3 E0 I! g+ n  S
    3; I1 i. g+ n1 E2 _  M
    4
    / E' t3 A3 `4 e7 ^. b; u- Y" A51 s9 N$ Z# f- H7 f9 B
    6, u# n& f6 B8 j+ v
    7! x- o3 e3 o7 I& G& y; y
    8
    3 c- b, u- p( U6 h/ w/ l9" V7 p6 o1 G+ A7 T
    10
    0 q1 i2 Q: {  F  S- L" H) O与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:/ _! T9 @2 ?% {
    pd.timedelta_range('0s', '1000s', freq='6min')& K& Y7 B* f/ v8 W( g# o8 a* h
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')7 n" T5 r( ^6 v4 w3 b
    ( `  z6 O9 U! X% J4 K5 p
    pd.timedelta_range('0s', '1000s', periods=3)
    6 w- Z* @& K( ^! c: uOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None); |! w3 V, I' H5 F
    18 d% N9 f8 c' G# \7 J
    2' v5 E, F$ {; Z, M9 c+ C! ^7 l, I
    39 C7 F8 u. ~5 Y" i8 j% y& ~7 l5 ?
    4
      `8 k' Y3 p0 ^* ?5
    ' ^5 S) v6 }6 u+ N1 p对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:" {. ?/ s4 D2 z% {! r) u1 e
    s.dt.seconds.head()
    . q" ~: \+ z. y5 cOut[64]:
    ' \; Q( |* ^& q/ J: |$ M0    274, \$ Z6 P/ h( a1 G
    1    260
    7 z4 [2 ]' [* G5 V  `2    322* F8 x' W' X0 V  |2 ~
    3    248
    ! D2 k3 w, X3 ]: e4    322
    : v1 j4 [  P+ I# A0 HName: Time_Record, dtype: int649 e- {* m% \( y& U7 [8 v/ e
    1
    # ]5 T1 N* M; J2: D) v8 m! H2 v5 V5 D  {3 J8 \
    3
    ; `5 i! |8 K6 R+ L2 _9 H4 X6 E4% r; {2 e3 O: A8 l* M/ `
    5& e- h" m" l# D. Y) ^
    6! g' T& o3 a6 m: I; K
    7
    5 E6 }& o, R  }% r8$ U- O7 A8 Y* Z* V( Y, |3 s% G! O! q
    如果不想对天数取余而直接对应秒数,可以使用total_seconds
    $ g4 C5 y+ u' ?  K: H6 S9 e/ y9 A% U( i* }8 X/ r% F3 H+ D
    s.dt.total_seconds().head()! I) G0 h4 v9 U
    Out[65]: # K# L+ \$ [. Z! ]6 v
    0    274.0
    0 u( H' x) v/ a$ ^6 L, Y9 q1    260.0
    - ?4 F) L3 v/ \% K7 D4 m2    322.0
    ! ~% N% i" |9 C4 `3    248.04 v* M9 ?# F! J; G
    4    322.0
    / p1 \. z7 W& AName: Time_Record, dtype: float643 T" ], F' z1 w. @, E, _5 b
    1  f6 H/ D+ p' r3 M2 z3 J
    2$ j0 t- I5 W7 _4 Y5 @' H
    3. Z5 \" @( m) K" Y- _) S! q
    4/ b# {+ {8 _$ x/ b% n+ W
    5  C* h6 ^/ x4 l5 N- m
    6
    9 R/ Z; [% T6 s2 y4 P7
    . F& x3 t# r: X3 W' q84 \& v/ r% X5 ^+ o: ?! T( g: e
    与时间戳序列类似,取整函数也是可以在dt对象上使用的:- M8 N3 Q0 ]0 f7 V! ^
    # L% }" t2 }& {6 v
    pd.to_timedelta(df.Time_Record).dt.round('min').head()5 W' y  t( P& |3 G1 k( F
    Out[66]:
    3 G% Q5 x9 J4 E! d- T( J( ~0   0 days 00:05:00
    ) E) Y) x3 V, K/ l1   0 days 00:04:00
    2 I) ~+ y' M6 [/ \3 ]4 u# D2   0 days 00:05:00# D, W  M# Z/ h
    3   0 days 00:04:005 C" S' i( Y* ?0 t4 g6 }
    4   0 days 00:05:00
    9 b2 \3 w, t* E  J" n8 _" GName: Time_Record, dtype: timedelta64[ns]( B: z- s6 ~- a1 B
    1
    1 B3 W( g1 x% {# Q4 v% C2
    + Y; E5 X2 t8 d- x0 h5 ?; j" Y. x3& I$ F8 `$ Y$ @
    4( C- D# b" H2 t$ Q2 X" x& l
    5; q3 H% G2 H# [2 M% Z& [& Z
    6
    9 p9 G' y5 L' h2 ?* G2 Q$ ^: Y" s7% V  q, I. c7 a  k9 u
    89 h, y- P5 a$ d" P" D2 _+ U
    10.2.2 Timedelta的运算
    2 u% R% E0 P2 A# j单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
    / e: h& u, Z1 V  [& }+ utd1 = pd.Timedelta(days=1)
    , x* b* v% S2 Gtd2 = pd.Timedelta(days=3)
    * P/ j$ L" P0 z6 P3 z( q( c2 I6 z0 Yts = pd.Timestamp('20200101')5 I% c; J8 L  Z& \# Z& W

    $ G3 E! s5 s1 w; i, Rtd1 * 27 q7 X7 `/ g7 B" g! [
    Out[70]: Timedelta('2 days 00:00:00')1 q* Z( u! g3 B) o& B

    4 m6 N8 H  ~0 p" @+ Ftd2 - td1
    5 r' Y( e0 ~; }Out[71]: Timedelta('2 days 00:00:00')' G/ ?* }% E/ B: F7 }/ s

    5 Y! M1 E) X) [" I' N5 ots + td1
    + `# {; L6 [2 [( H6 {5 NOut[72]: Timestamp('2020-01-02 00:00:00')
    4 w0 L( b' ?' o9 c: L& e  p
    ( C) W. d5 v( B% E2 G* K/ dts - td1
    ! y2 q+ |$ e9 g+ _  ~Out[73]: Timestamp('2019-12-31 00:00:00')
    * W% \. u5 }1 W) W* \+ D1
    * w/ L8 N" i; X& n- f1 J2
    ( A% f+ G& y& U# Y, |1 M3
    + i4 T0 V  v4 M! x: S: v4
    3 n3 @, r6 [3 s5
    * [" w# N+ g8 K* s6 U$ _+ L' Y6
    1 Q# h' `) F2 _, L% d( y; V7
    5 ~+ @7 z9 n3 w" ?! S' U) }' j8
    9 W! k# p) p6 `. M0 b" W91 ]6 X# j. D* f% \0 ~6 w8 c
    10: Z* A  b0 y% h8 N& t8 Q
    11% W+ ~' w  M# {5 Z- y
    12
      w: @' N4 i8 p1 ]1 Z+ v$ h0 x% O13, \. B; x: k, v$ c9 @
    14
    - ?- U4 P; j$ A" ]3 m159 _; Z+ H" S( s
    时间差的序列的运算,和上面方法相同:6 @) V, v. l4 O  ~" Y
    td1 = pd.timedelta_range(start='1 days', periods=5)
    7 L; z' c, V  a; I! u; @) ]td2 = pd.timedelta_range(start='12 hours',
    : D* Z, {/ G8 q2 ^; \% {6 b                         freq='2H',* D2 y% I- h- b
                             periods=5)  N8 }" T/ W( z# F% X9 [1 y7 F4 H
    ts = pd.date_range('20200101', '20200105')
    * L( o9 Q# d# [$ Gtd1,td2,ts
    0 B% _" G& Z/ h6 I2 W3 k# N: X
    ! k8 X( V# e4 l  I  O: M3 ^* n( k5 BTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')1 _7 k, ~* C4 b/ K# {
    TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',  X4 t# U/ O; b; p/ z+ C1 x) \
                    '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')
    - C; f# ~; a+ k; \/ O  o+ E+ kDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
    ; l, \2 V2 x( v4 x$ q               '2020-01-05'],/ S- i" ^8 a6 G  [* R
                  dtype='datetime64[ns]', freq='D')9 W! T! c1 o/ T. P" _7 F3 H" `* [+ V
    1
    % }6 [/ @4 d/ G  r( x; u2) a, ~" c# X8 c3 |# j2 U4 i
    3$ W- p! |6 ?( A- S1 v
    4* n# E  ^& S. F4 T  e
    57 _* T& n$ r( Y, q
    6* T8 J' z  O! r# q# H7 @
    7  X' J0 r8 v9 h9 P+ b. c
    8: O/ D- }, r6 k) O; ^- I& M
    9' i! C  N& B: ]& g3 v
    109 k1 G" k. z1 V9 Z2 ~" `! t/ g, n
    11
    0 D" h  d* c+ t- I( @3 t) h12
    2 }2 D3 f( }- N# f0 w13
    1 P& A+ S- Q7 ]* vtd1 * 51 o: w) w  ]* c! v3 P. f
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
    % Y: n5 A. s& h* R/ k; c3 k" l6 {; o, D; {1 ~
    td1 * pd.Series(list(range(5))) # 逐个相乘3 K" y  z7 e; H& e6 s
    Out[78]:
    6 q4 o' f3 v2 @0    0 days  j+ X5 x+ }7 ]+ A, g
    1    2 days
    % _) J4 z. a( G2 P1 I  t2    6 days
    1 T# v7 l6 q! _& c/ A3 @( k3 I' f3   12 days
    ! @" O4 D/ T) K+ j  _0 z6 Q4   20 days
    ) `! b8 ]$ n& B0 Jdtype: timedelta64[ns]+ a0 b4 o. C7 y0 n) ~2 P: G  ]: \
    & N; W  C0 p- E
    td1 - td2
    6 b+ h& X% X" n( _; M* W" HOut[79]:
    - \6 D- S' e3 E8 y  S1 P) l' k* ~4 h0 C6 sTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',9 _, U' d5 h# H9 P' j! _; `
                    '3 days 06:00:00', '4 days 04:00:00'],- u8 m) }+ @  y& b. `
                   dtype='timedelta64[ns]', freq=None)2 C8 @9 N0 j8 t. u% ^
    : d9 ?; I2 {1 I% j) b
    td1 + pd.Timestamp('20200101')6 R$ O/ H+ X& q; i/ q
    Out[80]:
    $ t0 q* @. @6 b- o. i- [$ i$ \DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    5 |3 x! b/ X. |, |) K) i& c% C               '2020-01-06'],dtype='datetime64[ns]', freq='D')5 q$ b5 T: V5 R5 }1 Q1 h: W; s
    3 s. A; ~9 A5 y, r" X& T) R
    td1 + ts # 逐个相加7 s: `: x' ^/ p; p9 m
    Out[81]:
    ) K+ U/ @; o6 w7 C2 E  [3 mDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
    5 N, w2 O( P. }$ [' ?               '2020-01-10'],
    ! B) l: Y, m' i, V% L              dtype='datetime64[ns]', freq=None)9 r: O- t' ?: I1 k' `1 `; ~- F8 G

    5 f0 A+ P: e7 e  y1
    7 V: C2 V$ J  v* }8 T4 b2
    : m& _8 T, {# f( k( L3
    ( Z. K2 r2 S6 }- U5 }1 m6 V( k- c4
    , x; G! |7 A3 L5
    , b6 r( `0 A' ?7 d3 n6& l% ~4 C6 p$ k* w- Z& s
    70 L& G; W* L/ U
    85 E+ l0 P! J6 v2 L( d2 B
    9
    ! a  I- N# C( Y8 g7 E10
    0 I% J. w  u2 m! P11; a- l8 y% V  O4 y* i
    121 H% v% W* q! G6 T6 S
    13
    % |! d, l  _9 q2 S& e7 T146 `4 u2 n- Q/ I7 R0 {% g
    159 H, h7 U# a5 O2 M. }
    16
    : _2 A5 Y% x3 f9 Y17
    " P- L# @: m& [+ S; U2 o% W18
    - N# L. E& L) _+ T& s$ d( X19; \( U: x7 g; B4 W
    20; j+ h4 k( w. J; k
    21; l# |( m! B+ `8 a5 Q9 }. |. Y
    22
    * d: t: a4 X. p+ j* i; a235 \& j# h' m6 ]5 w3 l. A
    24
    0 G* Q. f5 O4 N6 u3 E25$ p* N$ G6 P% f- W2 l# ]+ @; `
    26
    9 n% |5 \) R: w% G27( ]7 p8 l+ K4 ^, p% u
    28
    / q# G9 N- g$ W4 h/ E10.4 日期偏置
    * b; I, {. m) i% f7 C9 i10.4.1 Offset对象
      G3 F, ?. n6 x$ D! C2 b  日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。8 s) L+ u7 F4 l5 S8 O; F- `

    $ a1 U: ?7 _# S; MDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:2 M, e) G% N7 Q3 Y: K- h

    / A1 F; Z" Z# D( P/ ]s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
    # x; Y" o" G/ t' m, o8 H  s- j" @$ Js.kwds:{‘week’: 0, ‘weekday’: 0}# J* e: q* A1 ?7 q
    s.wek/s.weekday:顾名思义7 h. L& \, n1 A9 Y3 N2 c9 p
    有14个方法,包括:" ]9 J+ ]' |' L9 r  c2 }' W
    5 q1 ?" l! e9 D1 g
    DateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    / Q- c1 p6 |# F4 q# I  npandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。! J: R" f, f! k5 o7 j
    ; R& y( O0 ]1 a
    有两个参数:
    ; J/ W  Q! {' R. _- wweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
    0 r# H0 N5 c0 ^4 ^& J) V2 \! j: X* Mweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
    - F2 q7 q7 T# l( C! ]7 T% f9 R! }pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    # M3 f% H; j: @2 I: Y9 d4 ?0 V/ c2 ?9 w4 x9 Y; t
    pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)9 ^$ _2 L" ?0 M7 t3 I" Y/ h
    Out[82]: Timestamp('2020-09-07 00:00:00')
    8 g  I+ K! p* W4 e$ X* e4 U
    # @- H3 w* c4 e0 Q/ {6 P; }pd.Timestamp('20200907') + pd.offsets.BDay(30)
    ! q- H$ `4 S1 X' ~! W: o0 COut[83]: Timestamp('2020-10-19 00:00:00')8 w5 i+ I0 w- `+ r7 i8 T& K
    14 V9 X8 y- T+ B# ]
    2
    # A' A1 B& ^! G  t# ]) n3* Y7 {) k$ t  U4 ?9 l8 S: K; ~
    4& S2 _/ X$ T( _5 I, `2 }2 q
    54 I* i& w/ z" G
      从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:; l, Z. m# o' j) c8 y8 Q
    ! V5 n  t/ x1 }9 f  ?2 b
    pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
    / Z. {0 U; O; A# R9 g0 YOut[84]: Timestamp('2020-08-03 00:00:00')0 A$ _* d# {; u& ^
    " b3 H; }6 l3 h* }+ _
    pd.Timestamp('20200907') - pd.offsets.BDay(30)& u7 ]' t2 ^, B6 J9 ?" p2 f
    Out[85]: Timestamp('2020-07-27 00:00:00')) N- {: }% w. Q7 V8 U. A) F

    0 \( j  J' O$ N, ^9 I& K! Epd.Timestamp('20200907') + pd.offsets.MonthEnd()
    8 Z5 E5 B$ K5 m( aOut[86]: Timestamp('2020-09-30 00:00:00')
    1 k- n3 ~8 B% l" f* q9 B' g3 U1 P13 n1 j* D1 b3 F0 G5 u- G/ r
    25 G5 J4 i: F/ Q8 w+ v  V" R. Z
    3
      m) B$ n3 g  K" j4# g: x2 Y) m! r8 |3 I* b
    5
    6 Y8 P+ p; K3 _6( b/ W( I9 m1 h* X
    7
    : u; s$ l6 l% n) w8; J# b& O7 I9 n/ c
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。- x0 J0 u6 B. S2 ^. h
      其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
    2 L4 S6 ]1 _* Y& b2 [" l' n  T/ r# z0 S6 R! ~6 n9 g
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])% t( O! V# S9 J+ f7 Y
    dr = pd.date_range('20200108', '20200111')0 g, f: B4 D) |$ c9 u( D9 R- t; J3 q, y/ e
    ) }8 {: |  J' F
    dr.to_series().dt.dayofweek: S! c  [/ x3 K3 E" h: {
    Out[89]:
    ( x+ M0 p: H9 x# k2 Q, Y# G. W2020-01-08    29 a; w( E# V% Y$ E0 G
    2020-01-09    3
    0 {; z* U( q) s. ~+ q* G2020-01-10    4
    0 O% _  i: {: g. f4 q. Y' ~0 R5 I2020-01-11    5- k% D7 n0 P$ q( J
    Freq: D, dtype: int64# O- q, S0 V0 x; M3 Q+ f

    # R( _4 {6 G9 l9 G1 n$ T+ n[i + my_filter for i in dr], g* D- ?$ R) I
    Out[90]: & z2 g6 ]) y: u  M* r& X
    [Timestamp('2020-01-10 00:00:00'),2 B" \* @, i+ I$ \' g
    Timestamp('2020-01-10 00:00:00'),4 w7 S# A) p( |) |
    Timestamp('2020-01-15 00:00:00'),$ @8 ^5 _; g+ F, Z
    Timestamp('2020-01-15 00:00:00')]
    ( U# c: W4 B( T( v+ o
    ( S- n0 G' c- g. G$ Y9 @$ Y1: o, C. U, y1 y6 l3 n
    2
    0 B; E4 t7 `6 o- [3
    ) F8 U; r2 k; V) ^/ ]/ [4
    5 s2 q1 U0 p- {( L5
    ' i' E  p5 D+ g8 ]9 Q  ]! T6
    3 o6 c. K2 |, _! h' \9 L9 W/ W2 |  i7 u7
    6 d* W; [4 m  b& f0 ?. j" h8
    / N; I! K. W1 I9 b. [99 i& _( @, x, r4 R7 H; i  ~
    10
    , s. L1 i: ]3 H0 d  h6 i11) Z  @$ c" ?9 @1 [( i2 o+ I
    123 W4 C& _% `! a3 V3 \6 K5 O
    13
    + ^, Y6 i) ?) ^: m' ?9 `14
    : X5 `# A& J7 `+ o7 b15
    , p, P9 |1 h# v4 L% ?0 R$ W16
      B% G9 s& X. s" S; D: ?17
    4 J  V% ?# E5 d1 u: X+ e8 \  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。, n3 Y( P4 k+ @) [) F) `9 n' s/ I
    + H! H3 `2 c/ T: V9 G7 j: a; v% y
    【CAUTION】不要使用部分Offset* q5 u; W, ?- s7 E1 v" z/ m) c
    在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
    % K# [# y& _- G: @" ]0 i5 ?( U: V- o/ M8 h0 Y/ Y* I  |; M" B6 i3 W
    10.4.2 偏置字符串9 m& W+ H* I7 m& K( J
      前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
    8 t$ b5 @  [. `& J7 W/ @) J5 t. q# G! B& D' r# M" J6 s
      Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。" N2 X$ X2 o" H, M; C; n. t% [
    $ R, Y& s2 G# O9 V6 R
    pd.date_range('20200101','20200331', freq='MS') # 月初
    ! x; {$ }; C( t( C! S" ?6 lOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    ; ]9 ~0 z0 }+ y! H' l) G/ ~, `" G
    6 C2 e3 f  H7 Y, Ppd.date_range('20200101','20200331', freq='M') # 月末* o7 P' J' t. ]% m5 Z0 ^9 W- ~5 w3 C! Y
    Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')0 I# S/ q5 n! k* h. [
    - R1 F5 N- t4 [4 G* M
    pd.date_range('20200101','20200110', freq='B') # 工作日  T$ G5 Q9 Z2 i: x
    Out[93]:
    / t% r2 B# U% g% dDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',& s/ }5 v: p2 f. k5 x/ z
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],9 z# F4 y* q( C
                  dtype='datetime64[ns]', freq='B')( r% g) g( {3 |" x. r) u
    4 D  ]& t& e- S' t! w" P
    pd.date_range('20200101','20200201', freq='W-MON') # 周一* K# k7 f% {- y% r$ D1 r
    Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')$ M+ n; Z; W  u  z1 G- t" w! Z

    9 U3 \' P/ z+ d+ Tpd.date_range('20200101','20200201',
    7 C) x' h3 e0 x8 ^5 Q              freq='WOM-1MON') # 每月第一个周一
    ) A' |; n( j- Z0 w3 H' A) m' N8 Q
    9 v) W, M6 X: \& X$ M6 WOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'). z6 J" y6 {7 p  B: m& V

    3 D2 D  m" I$ S" D3 x9 ^1: C& W" h) p# R0 @5 _& q1 v/ a# t
    26 M* w' i* z  T0 C% \$ j
    3- B4 ^( H8 G) o! I
    4
    * D: W- W7 _  ?( [59 G3 G" \) s( P. s
    6; A% S$ l7 y& h) w% \' F' x  V
    77 Y3 c) v+ ^, N- a: [9 s
    8
    ) N8 Z) D7 ^! B0 ?- M* T6 J/ I94 V! |! l, _" x7 a: \+ ?0 \
    105 P3 }( ?( y" o
    115 Y& j0 J, N$ a. L" \+ o! v
    12# D- r0 y$ U1 n' ]3 s# f. S+ D
    13
    + h( a% x" C$ P" e% Z14) F7 E" ^5 }7 t/ U# v
    15
    ) s+ a. P# t. C$ j( ^4 U: x' V3 ?16& A; k( o7 `2 T& p( K  T
    17
    ; n' E/ N6 R8 s6 ~. P- r1 Y: G18, T4 h$ ~% K/ G* c
    19& _9 ~- d- T& \* i2 K: ?
    上面的这些字符串,等价于使用如下的 Offset 对象:
    . `8 ~8 o8 \. ^, F
    ! U0 d: Z. _/ w: b7 u, g  ipd.date_range('20200101','20200331',; z5 }7 i3 o- X" s; T: [$ k7 g9 f
                  freq=pd.offsets.MonthBegin())
    $ h5 o. @0 q2 n0 z3 M7 Z$ r7 U6 Y2 D, c! q  d
    Out[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')/ A0 ?2 e) P  U. n; z

    ( w% g$ {% [+ l; Xpd.date_range('20200101','20200331',
    5 S5 B9 @7 P& X/ W+ H) w* k              freq=pd.offsets.MonthEnd())& f1 Q5 E( S2 |% ?, ^
      G) e6 I! V+ l- `( a) K
    Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')* N" q0 a' W2 _

    $ F& v; s$ w, y4 ~pd.date_range('20200101','20200110', freq=pd.offsets.BDay())% C7 _- Q. d; k, k  G3 j
    Out[98]: . I& Z$ {& ?- O7 a; w0 k% [' H
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    % a; _# j- }) [               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],, C, b6 ]% L8 Z: L7 L; d7 j/ }
                  dtype='datetime64[ns]', freq='B'): m' i+ }  Z3 X" A8 J* z( K
    * n4 g6 G6 i4 n# _* @- w. J
    pd.date_range('20200101','20200201',
    7 y  ^' Y1 ?7 ?) g              freq=pd.offsets.CDay(weekmask='Mon'))
    ( q/ a  w: h" G( r$ H6 q- }( m! j, v% D! v' }( \$ ]1 l
    Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    1 O/ \$ @5 k( j+ @+ d" \  u9 d
    " i7 G, }* z5 K/ opd.date_range('20200101','20200201',
    7 i. z* Q8 p- t              freq=pd.offsets.WeekOfMonth(week=0,weekday=0)), Y5 A1 N5 y! V5 e

    ) a: _/ b, _3 h0 \6 JOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    * q' G. C0 c: X; ]# y9 ?
    / |% {+ ~& L( k" L: y18 r( v3 C, D6 u( N  |# P7 Q$ ?+ e: M
    2
    ! I  z2 ^6 P! d) K- G3
      i5 U4 F' [  b8 p. {6 x, U2 a44 {9 M6 z8 w3 _0 Y
    5
    / X# j* i& m0 Y& e6% J$ {( v8 i2 Y# S# _: q
    7" `) T' X. s0 [/ J  ]& \6 \
    83 j4 X" |  T; N- t# s
    9
    * K" {. y9 H; O& I* Y10
    5 C0 r! e1 P! v- X6 Y- X11, |! t7 O9 f& }; Z1 U
    12
    2 z4 f; u: o3 A  Q; Z: f/ o. f: _13
    6 R0 S; H0 _* r5 T! _6 f3 q14/ X/ I. H! a# s2 j9 Q& t$ c, i
    15
    , O& n* K7 D) X+ ^+ X16* C4 H' u) x  y' U
    17% U( A( d; F" E& U) n& i# J- J
    18  j# i% e3 t7 m! y+ T6 |; i
    19
    % U9 M; ~% ]- e5 c! _20
    8 j' y- {- u$ V$ p' v, L21
    " b0 \: s2 b0 O) |. o4 a* J22
    / Z4 H& T" m" ]' I$ g* k23# B; B8 P4 D: F; Y/ a+ h" j
    244 W* @9 F' M* t& H1 a+ X1 ?  w
    25" X: y- g, B. z
    【CAUTION】关于时区问题的说明
    4 ~! m- b4 m0 T7 b  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
    3 R7 |& N5 n. t2 F: F/ z. T5 E/ J6 H5 u- }' ?0 D1 Z5 u! q5 ]; D
    10.5、时序中的滑窗与分组
    # B% l. P7 l  @$ B* j3 p10.5.1 滑动窗口5 q3 n3 n, Q" s, H: f( Y$ [# y
      所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
    6 v# a2 ^5 r# l: ^2 j! i. h/ Z) X
    7 x1 t6 V3 Y  q$ z6 ^, himport matplotlib.pyplot as plt
    3 B! H0 Z& c/ C( C1 fidx = pd.date_range('20200101', '20201231', freq='B')
    / Z% z9 m% ]' l! t, Wnp.random.seed(2020)
    & n, T6 I+ j; P# _
    # N1 s/ J2 h2 D6 J' \+ Qdata = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加
    5 X5 x/ {9 J. V5 R9 s9 Ns = pd.Series(data,index=idx)6 {4 r1 {7 T8 \9 X& u$ s
    s.head()# z- P5 h  u' L, W0 `% k/ ^) T8 n0 N2 ^
    Out[106]:
    1 J$ _. @% S$ |! V2020-01-01   -13 D- p' {- h5 @( y# h
    2020-01-02   -21 H& k# [' \6 X, i+ b9 B
    2020-01-03   -1
    , k6 v5 T0 U; s% d( m8 m; [! c+ X2020-01-06   -1; J0 D% Y" I* Q1 s2 @
    2020-01-07   -2$ ~* J, @6 C7 X; v- G- u" |) ?
    Freq: B, dtype: int32
    4 k9 L* [! p) H9 c7 \r = s.rolling('30D')# rolling可以指定freq或者offset对象
    ) V/ Q( p: k% i7 d. W/ R
    ( ~: Y- Q' Z" l( i5 z& Dplt.plot(s) # 蓝色线
    " |1 y* s1 t" K. XOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
    * R3 y% d: y% e+ q0 f! Qplt.title('BOLL LINES')
    ( Z: e$ i: ]6 b. AOut[109]: Text(0.5, 1.0, 'BOLL LINES')
    : Q+ i) A4 @" c! m1 }7 F
    ; J* s  y9 R4 V* [5 v' v' v$ yplt.plot(r.mean()) #橙色线  }, Y0 }, v) p0 \3 m, ?
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
    ' r" p9 Q$ Z( h: W
    7 D% t% \* B5 R7 kplt.plot(r.mean()+r.std()*2) # 绿色线
    ) t% O  `# Z) r, c( c; FOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    0 o0 V" R7 d* q& F1 p) S0 m* H9 o% ]
    plt.plot(r.mean()-r.std()*2) # 红色线
    $ Y4 `+ K0 A3 L# [1 V! ^Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]. y8 B2 S1 w' r7 H
    - q1 [% P) m* D+ E. a; O* E
    1* t: R! f( Z# ^" h
    29 a: Z! v# e6 Q, G' m. S+ I
    3
    3 w  ~4 I* V  z% p2 P4
    . B% [: j' P' e* U7 r, o( ~4 ]1 Y5& A6 x2 I5 t$ m' n5 B
    6
    / i7 G; q3 W2 @: M' h% W5 x1 T! V7
    % W, J# |2 b9 q) `8
    9 ~& m* m0 v  B! r+ R+ Y9% J( a; J# Q3 M2 w* P: J4 T
    10
    3 F4 v9 |, |: q' S& I0 |11: `* v" P/ C, L+ H! w' b
    12" i: L( Z. }. x
    139 O( O, i5 E3 b3 Y
    14
    * @/ c! I2 r  W" |% a$ v, `& x15
    ! p( e/ O4 h- ~. J. o% N16- m7 t( l' R' y
    17
    ! D6 V- h2 a; ^* }# s18
    : l9 X! I) ~# q% T0 X8 X# f4 [  a$ j19
    $ k, S  M8 ^/ k/ G. ~205 h1 C/ v, S6 G% }
    21
    8 x( q7 N6 N, I1 i( W1 W22
    ! i2 G: N$ s# ?7 j239 J$ @+ Q% H$ K- ?. x
    24/ o( Q1 ?9 J5 k9 P8 q
    25" j  T) O  N5 B! P  {: v. b' r1 z
    266 W( V5 N- `* @! l
    27
    + L& h" Q1 p) F, f7 p( [28. `- p& {8 v) [. y
    29
      C+ B. l# {0 t2 _% z7 o- V; D
    . S' `" T, D) a8 s$ o! c   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
    ' O$ h& r$ t, H/ ~   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
    & B6 g" `$ s5 R: I* `$ @# A! M* I- c7 g7 G6 K# B7 f
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
    % ~* ]+ s, L0 q2 A2 Jbday_sum=select_bday.rolling(7,min_periods=1).sum()9 L/ r) S  @2 {, e; q3 u) X* n( C
    result=bday_sum.reindex().ffill()3 b' F# e9 H7 E1 K  c) i1 v
    result/ h0 N; r: C& r' ~
      z, U0 b6 S+ o, v6 H1 l/ f' V
    2020-01-01     -1.0  v- U* O7 S1 v4 F2 z
    2020-01-02     -3.00 z7 b6 _& _7 u+ c( }  d% \; ]9 ~+ L' @
    2020-01-03     -4.0
    - H9 S+ m" S. k4 v% |# T; G2020-01-06     -5.0; D6 o9 d8 _+ o3 C4 ~7 o
    2020-01-07     -7.0. `$ g# D, n( W) K, I2 g6 w( y( w  T# W
                  ...    q& B1 F5 m+ t) {4 G& h
    2020-12-25    136.0" T( p3 T, l0 ^
    2020-12-28    133.0
    - e0 V! H" B9 H' c2020-12-29    131.0
    " {, q% A* a2 u( P7 x- k2020-12-30    130.0
    4 q8 R/ v6 E5 x# ^/ O/ X$ G5 \2020-12-31    128.0
    : X& {& F2 C# C! H/ I) @Freq: B, Length: 262, dtype: float64  |: I. Z3 t- o4 A4 M- m0 w

    4 s& }4 ^6 b0 S3 m7 ?- {1* d8 l" O& J3 n/ v5 l% a+ e4 A6 T
    2
    " a$ N& g0 L0 v31 m' `& z, L& h5 Q
    4( k8 t! b8 a5 j8 Z
    5
    5 _; l. z: Y* j, d3 `8 d& M/ M6; k- S8 n5 m( p! d0 q
    7
    7 P6 g8 D: X+ V/ q1 e1 K8
    7 e0 g7 h6 C- l9
    " d( H" m/ X* n+ G1 O10
    $ D7 ~  E1 `4 w5 D11# E; j+ F- W5 B3 z* D8 v( O. h: W% e
    129 z: {( W9 K# l) I
    133 q5 O$ t, H' o; \
    14) _: {; Y4 f* |1 n; s
    15) Q; N/ h2 v7 ?( K+ j7 ~
    16
    ! H1 o9 S* F. i4 b17$ ^% q4 w: _* U+ I7 o  w9 g
      shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
    ! P% l1 x/ k* g1 K8 _
    0 G3 b- Q0 b, p4 K  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:* i+ F1 c- e4 Y6 u& ^- N5 C

    : y8 P, A) x0 ]$ ~0 @) i8 D$ R8 hs.shift(freq='50D').head()7 k" b0 M3 _; O9 }( r- }+ H
    Out[113]: ' P! Q. B9 N' q! u
    2020-02-20   -1
    1 i1 E. O3 q% p, x# O% h6 {  q8 O2020-02-21   -2) N9 l5 X( k' j/ B
    2020-02-22   -1$ S% h" X' I5 |1 ~, C: L0 T
    2020-02-25   -1
    0 [+ z" G. I" J  }2020-02-26   -2
    8 U3 B" y. F. o3 c3 z- |dtype: int328 Y  r2 Q) G4 |
    1( b# H2 ?% l$ H% }3 q+ k
    2' e2 }6 H* h' }8 c
    3
    ! d6 k" v6 @2 P6 P4* D4 E, t) W0 ]; D4 q  J
    5
    2 Z& g- j0 ^) ?( W6, b% U( Z- b8 [1 g) [
    70 S% C2 B+ u- T; e" m2 Y* v$ {
    8
    : e6 R3 E7 J/ ?* k. h, `$ i  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    6 I! p6 R4 u7 I5 O9 W' I( N% _; ^! f: y8 \9 i. x- B% @
    my_series = pd.Series(s.index)
    ( R1 Z  r0 ~$ o$ m8 V9 v, umy_series.head()
    4 o; Q1 [( ^* SOut[115]:
    1 q; J2 k% e. E) a5 R1 R$ d0 N; m0   2020-01-01/ K  T0 B; F/ t0 H7 [
    1   2020-01-02
    2 ^0 n' j& w$ e# X9 Q% w2   2020-01-03
    $ O! [8 f* H4 n. ]$ S3   2020-01-06
    : ^0 B" P* c' A0 M! q4   2020-01-07! E: I/ }/ @% [$ D
    dtype: datetime64[ns]
    0 e6 W; o4 O6 k. [3 M/ Q% K$ z. V# \+ Z$ ^1 m7 U- c: S
    my_series.diff(1).head()- q  e2 z2 f1 j1 f: `: ~( D
    Out[116]:
      G" c) ~& h  ~: t$ ]3 x0      NaT- G# M; o+ H& [* P
    1   1 days8 z1 Q# K6 p& r/ i
    2   1 days
    0 {+ J0 G7 ?* b& v# X" J  g, G9 L3   3 days
    * t- F% L+ Q" f6 m" ?7 x4 m$ o: y( I4   1 days
    # L/ {3 j2 G) Ydtype: timedelta64[ns]& d0 ~: [# c% x, [  r- {' K

    ; `  I  C$ p. W5 N1
      y& c& ~: j. Y- }2
    5 |$ @# a! k% h  K% A: `* d1 N$ A34 G7 i% `# n5 S3 a
    4
    7 A7 R, v: V. c5/ x5 A. b' r4 E* j. R' k# \
    6
    & x4 e% X4 u' |3 W) |3 I: E$ o74 r8 o6 y  `. q, _
    85 E: U& R. O- L
    91 W4 G( \. J8 k2 L* h/ v/ C
    10
    $ I* T5 Q  p2 D7 F" H" t11
    $ R0 a+ j% r+ F) a+ e12
    - K; a/ X0 }/ u13. N, r( Y! ]% R/ c4 b! z. ^, x: A
    14
    9 N0 h7 s. d8 p$ ]* x15
    * \" x, A7 v9 G2 i4 Z3 k168 N% K/ R! P1 v" J% s# T: w$ t( b
    17
    7 G! N# s: D$ \2 j6 {( M. k18
    ( V2 z: Y9 m% X( r6 I10.5.2 重采样
    4 @4 p# v$ m' W# g( S' q  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)$ ^- s9 [6 ^$ i( i+ {
    常用参数有:( O  L6 u0 \5 `' @- T

    3 \7 p  B3 G2 F2 m, L0 F( srule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    ! J* _2 m3 g# U7 Uaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
    9 q. O- B: s, o/ Dclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    # \. f6 F2 k7 ?4 @$ vlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。+ G- \. A3 s  N# f: s
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。
    3 H' k' |+ \% y" ~! _on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
    . j& J" _! o+ ?( J! m$ Tlevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。2 K+ U/ |5 O* r
    origin参数有5种取值:$ ?& M  b9 x& w( }6 Y
    ‘epoch’:从 1970-01-01开始算起
    : a5 A6 f& o6 r0 N0 V7 A‘start’:原点是时间序列的第一个值
    . |1 v! n# ]  I0 _6 h‘start_day’:默认值,表示原点是时间序列第一天的午夜。* {) q+ n$ k1 |# N0 ^8 c4 F, \
    'end':原点是时间序列的最后一个值(1.3.0版本才有)
    ' y$ U- @6 M% [; u9 b) B‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
    8 |2 x3 X6 m/ o1 C. q# [offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。" \: ~0 T5 R% c
      closed和计算有关,label和显示有关,closed才有开闭。  b0 Y! }7 `+ o4 I  D, y% i
      label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。; s& f2 s: o& k) s# W
    " x4 R$ E* |* I: B9 Z
    重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
      P' t: Z3 Y) G$ }- D/ D# ]s.resample('10D').mean().head()2 e0 c: j. d$ J3 x, |  q1 V% _
    Out[117]:
    ' \' Y' T1 Y) X6 [2020-01-01   -2.0000008 [9 z' v# F; P3 E7 s: U
    2020-01-11   -3.166667' m4 `; i4 Z3 Q2 y) R  w) }4 ]
    2020-01-21   -3.625000% o. R# T# x2 C" P* ?) J
    2020-01-31   -4.000000
    & J4 a$ T3 F7 X3 a* y2020-02-10   -0.375000
    2 d, a5 J4 J/ N( H4 EFreq: 10D, dtype: float640 a& F( I8 ?) c9 ?' }' X- \
    1" F+ {0 F6 _; c) X5 ?
    2
    0 ?/ Y2 b& Y& ^) F  o33 x: R. S, L/ n0 p/ c1 _& F" U
    4& G0 ~# \1 g6 X5 B# g! i# `5 ^# u
    5, j4 M# l  Q* n/ A# ?
    6
    # |( p" S1 T" m5 x77 g4 Z: R9 x! q" T: s, V! N
    80 h8 Z  A8 M, Q! O6 b0 n; h/ w
    可以通过apply方法自定义处理函数:
    2 S8 V6 ~" b& F& d6 s0 F3 cs.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差/ }4 R! Z3 Z, l4 f9 F5 f
    + H& {' f( X, Z. o
    Out[118]:
    5 s$ j0 o" |9 s9 d$ F2020-01-01    3
    ; P" O5 d! F9 p" D2 i; P0 o& _1 G2020-01-11    4
    : Y2 v: J1 Y7 z$ r) }2020-01-21    48 j8 U& ~+ z7 _" ]- x$ U8 z
    2020-01-31    2
    2 V$ N3 Z7 \" M" W+ N' ^" h2020-02-10    4! _$ T$ F" ~2 ~! F% F+ ^
    Freq: 10D, dtype: int32
    3 N0 _: C, P% d# ?8 R3 }1
    , {9 V% F8 P8 T' @2
    . g/ N, L* `! ^7 S3
    9 n% e* U, `) Y0 [* q4
    * Z% F) P7 q# m( t4 |6 ~2 H2 X5 F5
    ) e# m  G; j+ k6* a9 M; Y1 Q! p& u/ u8 {" d
    7" j! g/ O) t2 b9 w- k
    8" w- |9 c/ X! ?7 h
    9
    7 C3 Z$ d+ ^" t5 R6 T, s  在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
    ! X/ E  C' O2 a5 p  ]
    6 @( G0 I, ]- @; m8 didx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')2 ]7 T' l; Y) F
    data = np.random.randint(-1,2,len(idx)).cumsum()1 U* H0 K( v& u4 l. s# ~
    s = pd.Series(data,index=idx)
    / d% e  j# ^  I$ Es.head()
    . G' O2 g. W3 z5 T2 ]. K5 q) d- y5 h/ d' U! T8 I
    Out[122]:   g; Q- {4 U- E5 y
    2020-01-01 08:26:35   -1
      S: H# _! n. U$ r2020-01-01 08:27:52   -1
    ( E3 h# K0 p5 p: c9 b; o2020-01-01 08:29:09   -20 `6 F* L& x8 \& G5 A! N
    2020-01-01 08:30:26   -3* d: K4 g1 W3 G: A4 L
    2020-01-01 08:31:43   -4
    7 j: a- `# h1 j$ ]: C' O$ A$ p4 dFreq: 77S, dtype: int32& s! T# i2 m) c; ^4 {1 T
    1
    ( ?) Q; x4 @, m) H2' H) z2 z" V; H; d) l9 o# m: V
    3
    * [4 O: k6 K4 I1 W, x1 t! E$ J, o4
    1 z2 J: [' {1 f- ]5 w, F5+ q6 |: e& G* \; S
    6
    : u8 e$ N8 i: Q/ b% o7/ v  b5 g, C, z, r) b5 }8 z) y" c7 n
    8. c  U4 |$ h" O6 S9 m
    9
    - L" e6 D4 P3 Z$ a' l5 O10  l% n8 n( A# C. Z; q
    11- D% d* G# c0 m' p8 u8 G7 k
    12
    # `. ]9 ~) O3 Z# W: l4 p% K/ y  下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
    ' P$ G. h: }' l8 ]* ?+ R
    5 U1 R; ^/ L3 Ts.resample('7min').mean().head()
    ; a8 q3 b8 a! p0 y2 POut[123]: 0 `5 j$ f. C" E' n' U  v
    2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值
    7 E" b- F* w( Y$ e5 S( r. e8 `( }2020-01-01 08:31:00   -2.600000
    4 X( W- ?& D1 Z: ?" x& q  K2020-01-01 08:38:00   -2.1666671 z7 b' }7 ~- q7 n2 z" ]9 r
    2020-01-01 08:45:00    0.200000; f% I5 R# H8 P
    2020-01-01 08:52:00    2.833333$ R, f% E: e4 ^4 Y$ M. O: r4 x
    Freq: 7T, dtype: float64) u+ {/ h7 V2 ~4 v0 S
    18 f& H+ w- j) D# D9 E
    2
    # t5 H6 Q. `4 y5 [3
    7 w: T; t5 l! _/ G5 k+ U4 p4
    3 l( E+ H. ?7 `' T5
    ! M8 Q" Y2 x4 _% v. F- a  t6
    " E/ A. C/ p6 Y# |" O0 S0 J9 ^7' o4 Z6 [1 Z6 ~7 w
    8. k* x: E# A+ C. g
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:9 Q/ w  B& [, ~5 f( D
    3 H: k7 m: G) }. N, V! u7 L
    s.resample('7min', origin='start').mean().head(), G7 C* T' N) j7 c0 g( ~% t: }
    Out[124]:
    / N$ D6 U/ i8 z2020-01-01 08:26:35   -2.333333
    2 Q& Y0 x; t; S( U+ Y. v3 V2020-01-01 08:33:35   -2.400000
    # D# M& a+ P* t2 u) n+ W  y2020-01-01 08:40:35   -1.333333
    & h& n5 v4 E7 q$ o+ Q5 ?  g$ L2020-01-01 08:47:35    1.200000& s3 J2 p( ^7 k& z. N: k$ Y/ ~" ?1 @
    2020-01-01 08:54:35    3.166667
    , O9 k: v4 U& |6 v1 C0 b3 {Freq: 7T, dtype: float64. G$ a- m% ^! H$ p% B
    1
      B9 |  ^" a4 U3 Y4 G) A2: {& C0 j; y! K
    3
    - \8 Z1 i9 \" P% E) E4) c' h0 p3 Q7 Q! X/ T
    5
    + Z8 f* ^$ ]; ~0 \* s61 q& B9 C+ y% D# g
    72 P' S! M2 v, Z5 p
    8
    7 u* D+ Y6 q: }4 ]  J  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
    $ {& y/ m, z( N6 u8 }5 Y# N0 c$ {' R9 H2 V
    s = pd.Series(np.random.randint(2,size=366),
    7 o' }1 \5 y/ e/ F: R! l              index=pd.date_range('2020-01-01',$ Q6 z. L0 h( |6 K; l
                                      '2020-12-31'))+ D9 w/ S5 ^9 ^* O$ }% l2 W, n
    9 o0 p; r7 B* |0 H& P* r) [

    + F3 J' G, `6 M* n' Js.resample('M').mean().head()
    % P- T: e; K3 y& n# W/ Z7 ~1 dOut[126]: " m4 G" p( y6 I- o/ }
    2020-01-31    0.451613% L: H7 M7 ~+ ?. H& c2 `* e( G9 v
    2020-02-29    0.448276
    ! p, X  V7 ^, I4 O0 I2020-03-31    0.516129
    0 O9 R/ M2 P: Q8 L- Q, k2020-04-30    0.566667
    ' p* V5 }) ?7 C7 L8 d2020-05-31    0.451613
    ! e7 {% h6 R6 s- P+ Z& e# {/ ~Freq: M, dtype: float64
    9 y7 \* B! l: r2 l5 p( f% q8 T) c# g5 z
    ' k* Q  R; ^) o# i9 c7 o9 b' |s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
    , q% G. ?2 k: O! O1 u) G* a; P! V+ SOut[127]: ; V" D: w% Y: _5 k' W: n* s
    2020-01-01    0.451613
    9 N: k3 v6 D! |% E2020-02-01    0.448276" }5 |6 C% `6 T6 t% |- b: R
    2020-03-01    0.5161292 l# \+ R$ o! m! [1 R. _
    2020-04-01    0.566667
    : W2 y4 {0 y% ?3 k/ r2 k2020-05-01    0.451613
    0 q2 h  z; K5 i/ rFreq: MS, dtype: float64
    $ O9 H7 p/ {6 W6 k
    : f  |+ `/ ?( s% Z% o, X4 ^9 E1
    / r- |4 X, h. Z  C. b$ h2
    ; N. [% {0 O' Q; O2 n; E& P4 }3 j# R3
    ) ~: a) C! ]' L7 J& N' i( S4, S1 A, l: Q0 d
    5, Z0 k4 H: h0 W! z
    6! M0 Y, J, u! A' U
    7
      w% ~7 J/ t4 P! v+ ?8% _( U) x* T: P3 t: S
    9
    8 k* f  A/ q# ^# `& `109 N4 P$ p0 G" U0 J- n
    11
    0 u3 ?/ {  A* y12
    / q7 u7 E  x" C( I( M9 ?13
    - D% ~& z6 p4 W146 S  `3 R5 r6 i* ^( J
    15
    3 s; I7 n% F+ L& A16" ]! p" \. r6 O
    17
    / ^+ V' r, K2 k3 x7 |3 ^18
    ! L& t; M/ G5 Q7 B19
    % H2 i1 g+ `- s. J) i+ a, ]5 S9 ^% ]20& K9 V( j; n0 K% Y) e  @8 y; b
    21. \# `/ ~+ c  r) L/ B; u; Q
    220 ?: S- P; [. v
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
    8 O: e; W, O* @6 Fd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],5 r5 ]: O( l, ^& F9 }5 _) W
         'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    4 R# x5 j1 N% p$ V0 }! p, cdf = pd.DataFrame(d)
    2 v& ?: ~2 h+ Y0 o7 L; W8 K" A5 ~2 odf['week_starting'] = pd.date_range('01/01/2018',
    1 P2 ?/ a4 P% h3 K7 Y3 X                                    periods=8,3 s. `6 @8 g. L
                                        freq='W')
    - ?& K# H0 j! |; v' K5 p/ Ndf
    & R; N" c$ E  m$ ?! D   price  volume week_starting
    2 T+ Y: J1 |  M- f/ W7 }: Q0     10      50    2018-01-07* X. e% |) |1 P1 V& A! x
    1     11      60    2018-01-14' }0 C( P- ^2 r' {9 x+ u" O" ^' \
    2      9      40    2018-01-21. c6 I/ `& F3 p+ R
    3     13     100    2018-01-28
    , G, K# E& K+ P: U7 @) q4     14      50    2018-02-04
    & x2 k+ K$ D) @1 ]5     18     100    2018-02-11; Q6 D2 o- o3 F3 Z! x, X
    6     17      40    2018-02-18  Z: D4 Y6 b/ l2 v
    7     19      50    2018-02-250 r2 ]2 P0 H6 U8 ?
    df.resample('M', on='week_starting').mean()
    ' Z% Q' C4 g* f/ }               price  volume5 V; R' |/ t1 U2 d. O; X8 ^1 t, v
    week_starting
    ! i9 ?% k5 F/ v: b2018-01-31     10.75    62.56 n/ P/ g4 A, F9 X
    2018-02-28     17.00    60.09 g: u1 @8 K* _, r$ a

    4 S: {+ K5 u/ r: {3 W7 n1
      |" |4 ^$ \: F2
    : G! ?2 x1 W( c4 q1 P: E3
    + p2 o' |8 f9 q' K43 e* H0 F3 l2 \4 \- T! C
    5
    * D: e% _  S2 Y3 [% K6
      O- S6 K' R/ F' ~" J74 p/ P. a1 i" T$ C. R
    85 V- c1 }4 G6 A1 p4 r
    9
    ) I7 X" t0 t/ V7 p8 l$ f10
    4 c0 g! g2 x0 v, I( J; B11, L# i  u% X" ?9 K: ~
    12
    - |6 }( a* X& x0 v8 H9 T7 }2 M2 I5 l" O1 j. O130 H2 X/ K# ^' w, w. z* ^# e
    146 A3 ]; {7 l$ m# \/ @3 I% I
    15
    " E- L" ^$ ^: S16) N2 b. B# S1 R2 L
    171 Y( C) g2 C3 w# J
    18' P" F/ I1 L% t; i0 W
    19
    7 a8 v: \5 ^3 r# k$ |3 P3 C, a7 q20" ^) E; X1 X$ b" X/ S
    21
    ) l% _9 \0 s# @# m. d1 p; d$ u对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
    . M# p3 }7 V# \days = pd.date_range('1/1/2000', periods=4, freq='D')
    * c# I& `' \  Xd2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    4 R) G7 ^2 |! P7 {) ~      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}4 \* c( d( \: o0 B
    df2 = pd.DataFrame(
    $ r4 R% a$ @: F8 ^8 N( J    d2,
    3 [- B& v4 t6 O' @4 [  n  s% C% v    index=pd.MultiIndex.from_product(
    # G$ c6 w5 T8 n& Z- g        [days, ['morning', 'afternoon']]
    " _3 ?/ y9 K2 D6 A. C8 k) r    )
    1 g1 N/ G; [4 @)3 h4 ~3 i' p" _3 O$ n
    df2* \+ x- Z' o; O& J' f* X6 \) S; X8 U9 \
                          price  volume2 G" i3 n1 h8 M6 z5 n) E
    2000-01-01 morning       10      50
    # t4 d1 R5 _; m+ [$ B. O" _. ^$ W           afternoon     11      60
    1 }' O* r) ]1 D# j8 O9 m2000-01-02 morning        9      40
    & w0 d" O. k5 p! P, ]8 |/ t6 N           afternoon     13     100! t# }1 _  N# E
    2000-01-03 morning       14      50' r2 R. o* O4 I4 P; D" Q
               afternoon     18     1001 u9 b) Q# r- t9 D) P; H& `
    2000-01-04 morning       17      40
    ( v; Y1 W- Y& A           afternoon     19      50  b2 v1 o6 M; o* r9 n0 \# h2 m3 E
    df2.resample('D', level=0).sum(): ^# Y! k0 L& @- x! _
                price  volume  r. i) z1 |! ~$ _+ }8 i% W
    2000-01-01     21     110
    6 `1 U8 T( c0 s/ l; a+ g0 V2000-01-02     22     140- t. w5 O; K3 B# R3 u7 d
    2000-01-03     32     150
    3 l1 J8 t3 v1 }2000-01-04     36      90: M( I6 B% }; Y2 f; {2 y- t- F
    1 H, L$ M$ @, ]% p1 S1 D9 O1 @
    1
    , {( e3 F. z& N0 O2 ~9 B, C7 f2
    8 I' F5 C  D2 Q# n$ X3
    # i! c0 l/ @. J) h5 a- o/ ~- H  n4
    ; O8 l9 O# |. b+ q53 U& i9 ~. H5 w" Q6 W2 E6 B7 a
    6
    $ X8 W9 u8 ~8 E0 E# l  w- p7
    ' g1 l; r9 E, F/ E# y89 A% S4 Z' B% W% }& D
    9
    / k7 K: y: N& x, q1 F* A' n3 p10, Z1 B0 B* U; X8 j  k+ A: h  ^
    11* }4 Z( R( T1 _  ]3 @% v' H% H
    12
    $ g- J: d6 Z) Y6 p13
    # [1 w" t2 V( E7 w14
    - K1 W4 B4 a1 T' q9 I5 `15
    7 f9 r4 S& E4 r% V+ C( L! b169 m, X7 H' |+ }- t
    17
    2 o+ q  \2 b! j5 x6 l# ?: Y18' ~; s# P4 e  a8 N6 `3 L" m
    19
    ; m5 W/ r$ d  H/ g3 ]! i" D' Z20
    + ^- w0 S0 F! c, \$ {4 \, F- ?21# N8 L; }; @: G- Z+ N' [+ w7 ?
    22
    * C# B7 C2 @4 p. S23
    7 H, ]+ @( H& c246 i4 h8 D' I: t6 ?: @$ b
    25
    % f  Q6 |! ?, \# R- T1 H- f根据固定时间戳调整 bin 的开始:
    ' \) t3 h4 U9 }* }& X* xstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
    0 I3 l! J' t' D) ~: `0 {0 lrng = pd.date_range(start, end, freq='7min')/ \* ^9 Z0 d9 \: X4 s. [' x6 u/ M
    ts = pd.Series(np.arange(len(rng)) * 3, index=rng)
      a/ z8 D3 J: ?# f( D5 B8 z; W; k" _ts- I6 h! h+ |8 Y! r
    2000-10-01 23:30:00     0
    0 ~$ D& v/ L7 q0 d. I: H2000-10-01 23:37:00     3
    7 ?1 l0 M1 J9 d5 ]$ m# ]. U2 R$ V% B2000-10-01 23:44:00     6
    9 K/ v2 s$ V+ d+ ~" B2000-10-01 23:51:00     9
    ! ~8 a# v1 }2 T0 o0 Y0 b+ k* Y2000-10-01 23:58:00    120 N; p- o( g4 t5 Z
    2000-10-02 00:05:00    15
    3 N$ j# g2 }; F$ U  l5 v% H. |( A2000-10-02 00:12:00    18
    , N" `; e6 I2 h2 r/ i2000-10-02 00:19:00    21" L8 r, l; b9 J1 C- l* z9 X7 o
    2000-10-02 00:26:00    24& ^+ \7 h4 r7 V  N4 {  j& S. D
    Freq: 7T, dtype: int641 k7 h4 a0 N! w+ u3 k$ d4 @
    / o& ~% j4 V# ?
    ts.resample('17min').sum()
    8 M# L1 o2 @! y. m, |2000-10-01 23:14:00     0! q5 t  k. q6 t. P  j2 D$ A: }
    2000-10-01 23:31:00     9
    " f* y" `8 M6 N4 {2000-10-01 23:48:00    21
    0 E' V8 d! i! U2000-10-02 00:05:00    54
    - d1 A' x# R" y; `2000-10-02 00:22:00    24  p! a2 t6 ]  h1 D
    Freq: 17T, dtype: int642 D3 C* c' l; U+ b4 c. D9 M. R
    ' Q. W7 L0 @$ ?& g& v
    ts.resample('17min', origin='epoch').sum()
    * W$ ~8 n  A% h2 G) ~( v, M! l2000-10-01 23:18:00     0
    ! P% U- Q! A' ?- w2000-10-01 23:35:00    189 N- b8 T4 T# a' H- k2 A9 s
    2000-10-01 23:52:00    27
    $ ^* a* _7 x' v( X- y" P- g2000-10-02 00:09:00    399 h5 K% @7 K. e5 S- |
    2000-10-02 00:26:00    24/ {& D, s0 h9 o  J
    Freq: 17T, dtype: int640 V2 Z3 I3 l9 C

    - }4 e* l* N$ f& M7 I7 I+ p9 ~ts.resample('17min', origin='2000-01-01').sum()
    : j7 v* q! \2 r. ?9 G2000-10-01 23:24:00     30 v" B$ y2 {# m/ q7 q9 K" U* g
    2000-10-01 23:41:00    15
    % T, ~# e- Y) F8 v" j& g2000-10-01 23:58:00    45
    . R4 G6 Q; }' S6 Y( I* e: ~8 M2000-10-02 00:15:00    453 t6 c! R3 i, W5 N, B6 a; P
    Freq: 17T, dtype: int64' w5 r6 S% `: T: V! F, v; d

    : K7 H$ c! `% m6 K' {4 @9 b18 L+ D  ]$ _1 g# i7 V- P
    2
      o0 l% C0 u2 _" ^31 U5 R1 \* W8 K/ @. p3 S
    4* I" d! S# g' L
    5
    8 Z7 Y9 z. s0 t" |8 B% {3 J6" ]5 c8 p- x5 B2 J
    7
    9 T% b) }" P' U$ O- W, O- j86 ^6 p4 ?0 j" p* t7 }4 s+ M
    9
    : |7 I6 @' |# J1 Q  Q) [; [, y10
    " C/ G5 I0 r- S7 I- h4 ?11. o+ s- g  ?+ u! }
    12  M- X1 ^& u- w6 D
    131 n# g0 H# j' H* m' B
    14/ O( p* g5 H& V- m7 l& N* r2 N9 k
    15
    # _: d0 ?5 T% f; V5 d1 Q4 n$ o16
      A% L1 B4 @$ ]( D4 y3 _! o17
    4 f. c: Q0 f$ p4 f3 w. o18: F/ ?4 U3 p6 s; ^. L# I% Q
    19
    " S. v! Y* g, X1 Z5 Y5 O$ l, l20
    1 W0 l! W: s! u9 x9 M" _6 o4 O5 K21
    ) @6 S2 R* i3 i' F/ k22
    & H1 ~. l- D1 I" h  [0 b1 i23
    9 z7 S8 S) u" y3 Y) R! B245 g7 P1 G( d; k8 l* k( D) f8 g
    25' [+ v; j$ {/ p5 ]8 ?7 w
    26
    # t6 Q4 W1 X, v8 S27  c( s, S+ C0 n" n4 k# W
    28* ]# _) |# D) s2 W
    295 |- q8 J8 [) D2 v, r
    30
    5 H& c7 R/ w5 W7 M5 ^2 C315 i1 s+ L+ Q) m+ m( h
    32
    % X' F7 R3 Y/ r33
    % u6 ?! Q" U* w$ L' g+ v" ^) r34
    0 B( C6 }$ w5 N354 U9 n! u/ l. m/ O
    36# U6 A7 X) R, Q1 L) _) s3 L
    37! @' i2 L, P# t+ U
    如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:4 |  N+ |5 |0 g& \
    ts.resample('17min', origin='start').sum()" q2 L6 e: [* S& x" w# C( f6 X0 R( ^
    ts.resample('17min', offset='23h30min').sum()9 N; F  h5 U! V/ \
    2000-10-01 23:30:00     96 s3 H( A. N4 ~5 w4 T
    2000-10-01 23:47:00    212 A/ J- b. F" Z* Y0 y
    2000-10-02 00:04:00    54
    / i* \5 R5 r9 v; L$ y2000-10-02 00:21:00    24
    , v0 B+ p% N& k. A! ^6 h) z9 j  r- aFreq: 17T, dtype: int64
    / A% Y' C9 g0 M7 }  f8 E2 ]8 |# j  \- A1
    * U/ Q5 I) |7 H  i  H9 M: [  T2( y3 ^3 i6 a. f# P
    3: G6 m% }2 o& ?; |
    4
    5 N. E+ Z5 W# L# i5, t( M4 b& T$ d7 w! u; e
    6  S' w3 i/ c6 ~6 M. z" c1 V7 ?
    7( ~* g5 t* m: _
    10.6 练习
    # Z0 ^7 R) P8 g  K4 t! V" \: PEx1:太阳辐射数据集
    # J* P. r4 n  _现有一份关于太阳辐射的数据集:9 ?& a0 t) `0 D1 X3 T; O" H3 `
    9 C5 u5 s# w/ ^
    df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
    ' {6 |+ v1 M" h; l, _8 h  \df.head(3)
    # P4 n) U3 b, e/ g( H9 _' Q
    - d3 c- w- b6 q8 b% {Out[129]: 2 B- k. S2 R( [( B: F
                        Data      Time  Radiation  Temperature- O1 Q- X) Z. k9 x; ]* v% R
    0  9/29/2016 12:00:00 AM  23:55:26       1.21           48# r) t9 W7 [# o- o. y
    1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    ) W6 {3 U" H8 w8 ~) j9 P2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
    4 h3 I* \- g' [# `1, m# v" ~! [% W; t$ k. B
    2' e$ x2 ]" l: i0 E5 L4 U; X' E7 T: R
    3+ E5 v1 U# T0 s
    4# U  t2 `6 S& |6 r- k7 h; G
    5
      ^; ~, `6 J' Y: v( E* w6
    : [$ E! i# _$ C9 k; E+ M0 r1 q70 }9 z8 Z3 e2 T/ _; h7 W1 p( n& b
    8
    ( t* @9 v; b0 |& g4 p将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。; G* L6 |0 W% g# O0 n6 @) x
    每条记录时间的间隔显然并不一致,请解决如下问题:- _9 R, T, V, `$ A0 K9 z' u
    找出间隔时间的前三个最大值所对应的三组时间戳。
    8 f0 J* N# R5 C! b- _* j5 q! q是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。* W: ^' `, l) T+ `( O6 M3 z
    求如下指标对应的Series:- M; \2 `0 D! F$ w6 C  L
    温度与辐射量的6小时滑动相关系数
    8 o/ d. T' \' s# o! Q以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    ( |+ a3 J9 W6 R4 z每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    7 X1 n$ I! T5 e0 b5 jimport numpy as np: A% `$ W4 E1 Q' l# M% k
    import pandas as pd# C3 E" O4 [) \6 J: n
    1/ ?1 f* Y9 V. \. @
    2& o2 e1 `# \- v! o1 G- h8 f
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。* T. s7 N( n4 T) r- r' U) }
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
    ' Q0 z7 h( j- R6 Btimes=pd.to_timedelta(df.Time)
    1 u! P' H% D/ E) m: N3 H1 W4 Tdf.Data=data+times0 L, l( x0 C) C5 p
    del df['Time']
    . x: H* S9 I; [/ xdf=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
    % K- a; C3 c8 L/ xdf* C, f5 L9 {# W0 W& C' u% L1 I8 B
                                            Radiation        Temperature. R; e: d( q, o
    Data               
    ! |3 {: m7 \" v( s, \8 }2016-09-01 00:00:08                2.58                51& F. E' Y# P+ f4 ?0 Z5 ^8 ~
    2016-09-01 00:05:10                2.83                51
    2 Y& b2 j- S! X) p3 f) R" Z2016-09-01 00:20:06                2.16                51% {/ {0 p% G6 D9 B. _+ y7 G8 A9 a0 |8 y
    2016-09-01 00:25:05                2.21                51
    ' f9 F! T& Z; H# N$ I2016-09-01 00:30:09                2.25                51. ?" o% a% M  N' }/ M. t4 q# c
    ...        ...        ...
    ' o9 z  M) B! N  v# g2 U+ N' B( E2016-12-31 23:35:02                1.22                417 N  U$ p2 l1 R. o8 v; Z+ _
    2016-12-31 23:40:01                1.21                41
    - @& z0 I; T$ U2 Y7 i2016-12-31 23:45:04                1.21                426 b; F: R3 }& s+ r' Z
    2016-12-31 23:50:03                1.19                413 q& i6 v5 F7 @% V2 P
    2016-12-31 23:55:01                1.21                41
    : \+ p& V- c" m: x- F% O5 a3 t, e, {1 s) A3 j. F
    1
      H4 Z' c$ B5 a! Z* H. X5 |3 O* i2
    3 m' t4 q; k. F) t8 B! T& U3
    6 F3 U1 C/ ?( u  C: I, q4+ d- h7 X6 H, h% Z
    5' U- l) v" C) _4 s. N+ S
    66 g" ~" j, l6 ~% C0 N) {
    7& D$ Q3 r9 [, J# Y
    8
    8 U+ {4 w$ u$ p/ V8 \90 F$ A# L( t2 v" @
    10
    % i$ A+ w  e: I) W' N$ D11; @* `7 z; @6 p/ t, ]9 i1 f" z
    12, ^* z! D9 N* q7 h! N
    13
    " v) c  {. m: K% d3 d146 H& E' u- Y  n2 f
    15) M; c9 l& G$ p$ _. v' J
    16
    6 L* b+ Z! x3 b3 ~6 O  B/ g. f% @& {17
    # t' k& c7 z$ v0 F6 S- a# }' ^18
    ! N+ k" C8 ^0 _/ {, ^19
    # B  `( c: j0 n7 @9 x% h7 P每条记录时间的间隔显然并不一致,请解决如下问题:
      \0 ?" H- I, h' f7 _$ u找出间隔时间的前三个最大值所对应的三组时间戳。. t$ R) b, T$ ]; K  z
    # 第一次做错了,不是找三组时间戳
    - j) o$ k2 _; a) M- Ridxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    " w2 |- ^% u% I9 D6 C  @df.reset_index().Data[idxmax3,idxmax3-1]
    ; L" V9 ~, z+ V, `% k1 w& L; O6 v4 \
    25923   2016-12-08 11:10:42& i: V! Y& H. i7 q8 A) J/ `
    24522   2016-12-01 00:00:02, f$ h  y$ g) ^
    7417    2016-10-01 00:00:19
    0 Y" f; `9 E, J! j; ~Name: Data, dtype: datetime64[ns]/ L: m' a7 `9 p" p4 G0 F, R/ r
    1  h  h! q) B8 |( y2 p! F' R7 W" R  ~3 M
    20 d2 x4 h7 X/ R
    3
    + B/ E% X( I" ~% w' ?( ~. A4
    3 p: u* R" ?! D" S& X# J55 P/ \; `* U* L0 c/ W4 g. S6 u( a
    65 X7 r. Y8 [7 l7 O* |
    7
    0 S" e% s- W1 s: B* ?8
    % d0 W. }, L7 ]idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]5 r; l, G  x/ _; U( t& l
    list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))0 X; A. S, S/ Y' z9 F6 a6 S2 S

    . I3 ?* P0 O" x( ~& A, h[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),6 S6 Z; U; Q, Q9 Z$ R
    (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),( @, u. n+ |7 S7 m: D4 }0 C+ |+ U
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]
    0 U( P" N1 u8 t; _' b1
    ( |9 _! m) W+ `- f23 x. O( H! U+ S
    3% o( A5 q) H4 q6 N% w
    4
    - G% y3 G. ?/ n4 m5, Q' Y$ P& W/ {0 j
    6* m0 N( r$ d4 Y- j; J- |
    参考答案:: ~& E% P! \" P9 n/ t
    ' q0 n. z4 L, Z; ^& i# Y' B  f# v8 b( x
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()" K! [2 }9 Z1 T7 j
    max_3 = s.nlargest(3).index
    , G' m  p- M& S$ ^df.index[max_3.union(max_3-1)]
    ) Q* ]* h" ?  [3 R4 F7 ]" x$ p( h+ n, z/ R5 ?( K8 H
    Out[215]: ! Y5 a9 \3 v8 i- R+ Q9 Q
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',: C8 I5 M" Y7 q2 w5 l7 Q
                   '2016-11-29 19:05:02', '2016-12-01 00:00:02',* O: c6 x! Y* k' g& ?
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
    9 f& o" y7 v$ _& Z, ^              dtype='datetime64[ns]', name='Datetime', freq=None)
    , ^; e. B2 Z! G8 J6 u" L4 X1
    : W' X( h4 S) E; V* L- r2
    7 D2 m+ G$ v& V  I4 D3
    : N( T. N" D- m* g( q4; y% ^  X6 R+ v6 u  O4 T4 }5 d" {
    5$ q% R; ^5 `" Q  A, B5 E
    60 Z8 b$ e$ v- V
    7
    % m; n9 p9 Q) E0 P4 |/ |, V8) _5 H/ K) v8 h  f3 f( f# Y
    9: }6 {% J0 n5 f: J/ O- w
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    / _* N; X/ j0 s( h# {' ~# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间% y2 E3 G/ D# \. Z* R% x
    s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False), I, e0 b+ u2 y- t: V  J
    s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
    3 `) V6 e$ O( n( _' O- V
    $ d7 f- l" i0 z" @+ l(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)" f$ M6 S1 E9 I
    1- B. ?2 E$ v# ^1 E* q% k& m1 B5 p
    2
    / m* y6 D! n0 Z3 L/ t3
    ) Y7 w3 P. |4 j. T4
    7 X, R3 P. g4 F8 B: Z3 w5. v7 y9 E- r: o. l4 y) \
    %pylab inline
    * g. B1 }# `" o7 U: r! E_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)" w* q0 L! |! q, x+ f: E
    plt.xlabel(' Timedelta')" e: L: P5 k- H
    plt.title(" Timedelta of solar")
    . K( R  Y( L+ V( _1: y$ s" |7 ?9 h# ~  ]9 L
    2
    1 R' k9 E9 |8 Z3
    9 ]) T4 C( J. ?2 J* |9 H  b4  s0 S" a' ^4 X( m4 q5 m  T

      p3 U6 R3 j; A
    2 _) J9 B0 _6 c+ L) W. Q5 ?# B求如下指标对应的Series:
    7 n# t+ T" R0 @9 f$ D温度与辐射量的6小时滑动相关系数' U9 d  n" T0 J
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列! ]- u2 \8 R/ \2 V8 a  y% D
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    / W' }- X$ @, `' ~, Ldf.Radiation.rolling('6H').corr(df.Temperature).tail()
    $ D' o" t" }$ e7 {# @  x0 q! T% C4 V  B8 Y4 D1 ^0 [- G
    Data
    7 j  o2 M# f/ s+ y; J* _2016-12-31 23:35:02    0.416187( m# x0 _/ D6 ^4 C5 i
    2016-12-31 23:40:01    0.416565  B6 o, }. E3 m5 [% o0 `# A5 L
    2016-12-31 23:45:04    0.328574- r# B6 A+ m, G' N
    2016-12-31 23:50:03    0.261883* J7 C/ b  u' O2 i/ Y/ Q( o
    2016-12-31 23:55:01    0.262406- u: c" ~1 \8 U$ \: m
    dtype: float645 p2 Q- P5 b- M3 `
    1
    - c2 ?1 _& ^3 w5 `: I9 L- K2: j$ u/ w9 E- E% y- Y# k
    3
    " ?! A) l* R9 G1 G4
      \1 m8 f" q% n; x5 [& \) Q9 u5
    7 A+ J, N! j1 z3 M1 v6
    ' L  m/ D2 l7 U/ |79 f1 q2 b$ W/ C8 m' ~. z, |
    86 i& I0 I/ z6 \9 h+ }/ P
    9+ N5 m; g# g# O
    df['Temperature'].resample('6H',offset='3H').mean().head()
    - j* A5 C0 p, Z9 l+ E8 N
    # U4 ~' P% d) H- l! y2 x3 b& n" bData; }& x9 n  a; h' g
    2016-08-31 21:00:00    51.218750" U$ a* J8 H! Z8 |0 o) o" s
    2016-09-01 03:00:00    50.033333
    ' G, K; H/ Y( z1 J1 E  ?( O2016-09-01 09:00:00    59.379310
    3 W6 r* Q( S( ?2 X4 R2016-09-01 15:00:00    57.984375; }0 l2 I! `, m( b
    2016-09-01 21:00:00    51.393939
    / e- Y$ _# x7 l9 ]Freq: 6H, Name: Temperature, dtype: float64( V2 n5 a, S: c5 ~; b
    1
      C  u1 ?0 u# s1 R( I- R22 \* E& m* c4 z7 T0 j; s1 a
    3& j4 C: [$ d/ D2 L) @, H) j
    4
    $ l# w: s+ H* K* M( K1 l8 e5
    # ]* ^. j0 F6 G3 x6
    , q6 n: A. V8 W3 c! l. I7
    + J$ g8 q: U. L2 h* t5 X$ O/ t4 t8
      ^- V0 b" c+ M0 B3 S' @' s9- l: k  S: w8 B
    最后一题参考答案:
    , X$ N3 i2 J3 X. t" k8 K6 l: ^
    3 ]) `3 n* V* R& |6 C# 非常慢
    9 T4 w: g( T0 c/ ]) g, `. @6 smy_dt = df.index.shift(freq='-6H')
    - }' [) L5 P) fint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
    / j5 L/ Y, f6 O9 A2 @) Sint_loc = np.array(int_loc).reshape(-1)" d2 l% ^6 x6 p5 o* _3 @& `7 a9 S
    res = df.Radiation.iloc[int_loc]4 o+ G0 |- P+ P
    res.index = df.index
    2 m5 k. l+ D4 i# Lres.tail(3)4 t$ q% O" D  K+ N
    1
      S$ B+ W! z2 B: n# ?2
    , r: a  _: I) X. A; M: f3* h- Q7 ~. h1 ~; ^2 n. ?
    40 O$ {0 `/ `% M) B1 t& r, R/ d
    5
    2 v- a0 z) C& p, U6
    ' a6 G% j9 k5 u( @7
    / h4 b! h5 ~9 |' ^# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级& ~6 S0 r3 c5 O8 b4 q) j' u
    target = pd.DataFrame(  ~8 O$ ^" t" `  Y  Y- J
        {
    / c# g4 c  E- ]  G# K        "Time": df.index.shift(freq='-6H'),
    : Y: a% Y3 s4 ^" {        "Datetime": df.index,
    ' E5 E; e7 q( Q    }, F1 d& k( i0 a" ], }+ l: ^  p1 t4 S
    ): F2 ~* c/ B4 ?# b3 U+ C9 M( ]. n$ [

    ' K9 ~$ @; I6 ]2 ures = pd.merge_asof(, D4 G) P; L, d2 x$ h8 c
        target,
    2 H2 E2 \7 q" r! E% o. D( l    df.reset_index().rename(columns={"Datetime": "Time"}),
    ' Y4 J) E1 M- T! o0 ]. @/ [! f    left_on="Time",
    $ r0 H; d/ O5 o$ k  S* W) z    right_on="Time",
    ( b& E' O  Q' c: C    direction="nearest"" ^3 G7 d: B# a
    ).set_index("Datetime").Radiation) @( T6 d& }' |
    ! S% w5 L6 }% R6 D
    res.tail(3)" H/ Y# @  t! T. y) J8 M
    Out[224]:
    - v0 P7 w: K. B. |+ iDatetime
    * L) }: [5 g; f( f; A) r2016-12-31 23:45:04    9.33
    : @5 M( a% Y) E7 k5 h2016-12-31 23:50:03    8.49. G* z) h- w& r) K: L
    2016-12-31 23:55:01    5.84
    2 i  i1 V- P9 d3 g  N' M$ V2 hName: Radiation, dtype: float64# g- m" w6 k, F% |3 ?6 N
    + K9 E% R) v! \! x5 c* v
    1
    $ b3 E% r" G5 n5 p6 \2
    2 g( G+ q6 Q7 t6 T# {3
    6 j8 \  ~( k4 `" G! L47 q; z; a5 c5 s
    53 C, S7 d5 _- S* j; e% d4 D
    6
    7 L2 O) ^4 Y8 b: y( e7
    . o+ i# O6 h9 x8
    2 f; K' K, D( s3 m8 d' z9! h6 ?7 f& |/ V2 T- N: k
    10
    / l% G3 z  B# O, m11
    9 v9 K, p+ x. [12$ X. f. f1 V- r) N, Q
    13
    8 ]9 K( m# Q4 s* B( T8 r: y14! r& m3 _5 c5 Y" F
    157 J" B5 J: I- M+ ^, @( {; q: G
    16
    8 b5 k% M# \3 W  i' c' K* r" d17
    ' v) z5 ]: ?! F0 i  L8 N18" L' I2 |. v! x2 P$ C: }# f
    199 x- t7 Y2 j* W( @1 |2 z& K  G
    20
    + B; _2 f$ y4 P7 H2 [1 j216 U7 q9 ~5 F; P/ [# x$ H+ n  O
    22
    5 @; l8 Q/ F/ C2 e% s8 y* E: f23) @) H0 j. V) w0 L0 m* Z, k
    Ex2:水果销量数据集
    4 x" Y: u' I6 y' J- v  m现有一份2019年每日水果销量记录表:0 F2 L- }8 ~! U$ O9 ^

    / Y4 G& K5 k% ~7 R0 M! A1 s  g9 _df = pd.read_csv('../data/fruit.csv')- c2 D% a, y0 B% g
    df.head(3). f# K4 ~1 ]8 c8 p4 ^
    ! l$ q4 ~/ Q7 N; C+ G
    Out[131]: 8 b" V  W* \4 e
             Date  Fruit  Sale4 H5 c, V5 m, m" j  x9 o7 ~
    0  2019-04-18  Peach    15( C4 n2 I6 Z1 _" N/ h
    1  2019-12-29  Peach    15
    / {; t1 e; _* H! ^- u, q2  2019-06-05  Peach    19; d* N$ X* a7 |8 B
    17 ?0 A$ a3 }7 ~7 S( L
    2
    : ?3 p" |; H: v7 ]: H3+ ~* [- x* q+ k1 S1 s+ W+ `
    4
    $ P, L# m* Z  b: R% S5* m; d3 r+ U; `; ?
    6
    5 e+ e0 X" B# y2 \7
    6 B+ \$ |5 u6 [5 q# h8
    8 q" R& }1 n( N, a. O统计如下指标:
    9 z( O8 k& L+ o: ^/ V  ^每月上半月(15号及之前)与下半月葡萄销量的比值
    . b7 u6 c5 z( _* j6 d( Q, E# \每月最后一天的生梨销量总和1 o3 M! w" q& |( z. I% q
    每月最后一天工作日的生梨销量总和; Z: D& u. K, Y- W
    每月最后五天的苹果销量均值
    ) W& D" x3 A8 R: Y按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。4 B! l; ]5 {% s5 g9 g" n
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    " ~6 \. v  U7 n1 _" Limport numpy as np
    # ]+ u1 [( Z# t, w1 P2 ^: Zimport pandas as pd. |1 S5 M  s* g" _6 n/ `+ I
    1$ W. A0 Q$ ]6 Y4 Q
    2
    # N: t7 y- S5 N4 W$ t: G; [4 z统计如下指标:
    9 ~- l2 k' ]6 p3 }: K每月上半月(15号及之前)与下半月葡萄销量的比值
    " X- m6 e9 E7 W8 p, v每月最后一天的生梨销量总和
    , G8 I4 Y! w7 V9 ]" }每月最后一天工作日的生梨销量总和6 J* T: o7 V2 z" p
    每月最后五天的苹果销量均值
    ! @0 F6 F5 y0 x# 每月上半月(15号及之前)与下半月葡萄销量的比值0 A* L4 w; ?2 L1 @
    df.Date=pd.to_datetime(df.Date)1 J* y: ~  M0 L5 V; U
    sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    3 a- G& Q* q; N# a$ E" Hsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊" A! {" T4 `: b& ^  S) R7 \& \
    sale=pd.DataFrame(sale); ]% `  v* r# B  @# e; ]# _/ K+ A
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
    / V! T1 B: P- l7 \) H% A                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
    - E. ^! t: w! q8 T' E2 E$ w: esale.head() # 每个月上下半月的销量
    - }9 V# ^2 ?5 R
    / U1 ?8 E5 d. g5 F8 {( h8 ^! W  Month        15Days        Sale; h' ]. {( T( F6 W8 o3 |3 F9 k! a  b
    0        1        False        10503* u! a6 q6 V2 E, m
    1        1        True        12341  `; C2 U0 e* D# M+ p
    2        2        False        10001
    9 T5 J6 P. d- A) r3        2        True        10106
    ; _+ j" {4 @, `; o( b4        3        False        12814  t' i8 w; `3 M  C3 h0 R: t; H( z' x
    1 B# h& B; ?. ~* i5 Y
    # 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
    $ _4 f- j% D) j- z8 O( dsale.groupby(sale['Month'])['Sale'].agg(
    5 g5 B; a3 h3 S( E* F/ Z* i                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    $ P7 s. R& w  C# Q" a" n. b" i* E; G% e
    Month: e+ |# L. C: B" O; }: Y
    1     1.174998+ c$ m$ g& g& H0 H
    2     1.010499, k, z- Z! J  J. J" T3 h- A
    3     0.776338/ F6 |6 }+ l+ ~8 C& t0 M
    4     1.026345
    . @/ S/ v' g0 W4 b1 B: t. Y( N  Y5     0.900534
    1 o& D- f, n$ Q7 p- n4 ]# @- F6     0.980136
    / A. Y/ Y6 O! x7     1.350960
    - U$ r/ F! l) I8     1.091584
      J$ `- |: o6 `" S2 p7 }, y9     1.1165082 D" U# i; Y8 ?$ x3 W" O, P  {: L
    10    1.020784
    : M0 C, y/ c" ^; W/ I- k. h# h11    1.275911
    ! Q. I" J# D. `( s9 \: P12    0.989662
    6 g" u) ^" q3 w; f1 n  `5 {: y" YName: Sale, dtype: float644 ]4 {0 j3 I0 ~" t
    0 s7 P; ~- U" C0 a
    1/ }3 Z6 z7 R4 D) s: K
    2# \3 g% n' w$ n+ ~, \5 B( M* R: C
    3
    % e3 K- {2 t% E$ ]5 P0 l4& D8 m9 W$ L& j+ G
    5
    ) ^- M2 J3 D" i7 T" {5 V" d6
    1 R! M2 I! q8 N) B73 W% z1 J  n- P5 n4 u# H1 ^6 Z1 Y
    8
    7 q7 T6 ?# h9 Q& T% Q9; s# t0 P4 Q2 R
    109 M6 s& Z$ n9 S; R% _2 e6 H
    11
    : h$ @6 f9 V6 L  Y7 L12
    4 m0 S4 s* D" B: Q3 T( j% r# e2 }13
    # S- ]3 d2 K  _  }# j8 i14+ T. f- e# `( M/ D
    15) h" T" `4 H4 h4 n$ k8 p
    164 C! }& K3 G8 h6 r$ J; O
    171 |0 V7 u7 v: r& {1 H+ m; g
    18
    5 P4 I1 K2 V- ^5 Q19
    ) j. Q4 y. n  `+ W3 K( F* h20
    , C4 Y" S" P- }21
    / x* Y& l' q. I% E22
    1 S+ J, _/ Q; ?$ A23* p/ h! R4 O9 x- h& x- J
    24: E8 w* F; \1 \
    252 _! w, x/ T, }. L1 J2 Z
    26
    % \+ ^- @9 Z6 n27( V1 J4 Q! T' g! }
    284 v4 Q! X0 r( J& M4 h
    29
    # d' y4 v# L1 V& g: t+ s! G& }30% G6 m% @5 o  ?/ ~( |* w! _, N
    31
    . f5 i" l# L9 s0 W' M32
    " ]" f2 I; }- s( l  \33: d. k* x$ M4 R! w! N  o: i( Z
    34
    ; U: K9 l4 Y/ s4 f1 P' Q# X# 每月最后一天的生梨销量总和
    , t8 c' g; h, @! [3 x# g& E2 adf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    ; H% i& s0 B9 H! }/ V6 c/ `! D0 r8 n
    Date9 {( g# Y2 \7 `
    2019-01-31    847
    6 v7 k: p' V. x3 i' n, F- {2019-02-28    774& w  k) r$ U1 P4 Z1 m
    2019-03-31    7615 E( _4 c4 c: Y" m
    2019-04-30    6487 S4 ~, C# m% @7 X4 i1 t
    2019-05-31    616
    . V. n  K  w; W0 P' H) o0 A4 M1
    # V% }6 f+ m- c# K2
    5 D8 c' A: J: c$ F2 c5 J1 ~/ v3. Q5 h- Y- _+ a  k
    40 V) s- \3 _/ [
    5
    $ l7 q9 k$ B6 T4 C6 h69 J1 Y; ~1 H/ G# ~9 h% [5 c
    73 B5 _# e/ O# N( ^0 `
    8; s5 J+ o0 D9 w; I
    90 x. D& M$ m5 U0 Y# ]
    # 每月最后一天工作日的生梨销量总和( j/ r( Y* k! T+ R9 U. E' ]
    ls=df.Date+pd.offsets.BMonthEnd()- v/ U* B: ]9 ?$ C
    my_filter=pd.to_datetime(ls.unique())
    0 H2 D* P+ d5 d1 B, Jdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    $ a. X$ U6 y/ G  N/ o8 a, ]6 z9 g) y# ^" J: ~8 i
    Date
    0 q' _% u2 r9 B0 }3 h4 f+ k2019-01-31     847
    / R/ n6 z5 ~9 q# y2019-02-28     774. B; Q2 L4 J+ B2 J/ {( K" m
    2019-03-29     510
    ( n: t& h/ n7 m9 h6 N0 U, L2019-04-30     648* q8 d, w3 H6 `: O+ L0 ?
    2019-05-31     616
    + `% i$ K; ?; H1
    . n* f- {  u. f  T4 G21 p1 e0 L% C, m# i7 l  G9 d
    3' X' V2 A& p2 |- a0 ^4 ]0 Q
    4
    2 r3 K4 K% J4 e1 Q5
    ( N% T1 M/ z5 k$ p6
    ! g  g) ^2 D# ^, Z" _1 i/ t7 d+ ]7
    * N0 {) `, o$ ]/ {! u2 o; n; a8
    ! |9 p1 O+ O1 k6 m" w# e% T9# ]- E( F1 B7 V' h& r7 l9 P6 ^1 n  m  n5 U  e
    10
    5 |& F# N- D  x+ q+ s9 A11! |$ p( @3 r3 s3 S& S
    # 每月最后五天的苹果销量均值
    * e0 j- k1 d0 y1 y: |, e( zstart, end = '2019-01-01', '2019-12-31': Q$ G+ J6 n4 X9 A+ u* ~9 t
    end = pd.date_range(start, end, freq='M')
    ' E1 r. f7 J+ e# Xend=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差, D- \; s$ Q, Y5 N. D0 A. Y

    4 W$ t: _3 l, G  {2 dtd= pd.Series(pd.timedelta_range(start='0 days', periods=5),)- N2 e( D! T9 c' q& O
    td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
    ' ~( Q+ c1 O; pend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表% G/ i" l' x# C* m
    6 X' a) d; [9 t7 f
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    & I) F  E3 n# D. v1 j, y3 I  Bapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()" i5 ^! d; l' u  m3 z0 }' \
    1 g: @) @# B5 D  y
    Date) t4 |# o8 U( ]. j& R. X! X4 n
    1     65.313725
    , H8 C4 x6 Y/ [9 a. s2     54.061538% t0 R) F& X# y% y# `$ |7 s* W
    3     59.325581/ l4 x, g; X' Y6 c
    4     65.795455
    5 {) i! p) \% E  Q5     57.465116* d$ j/ A1 h3 U/ P0 i9 m
    4 [2 s% A& w3 w* X
    1
    1 v2 o8 {; A, T, a/ t/ V+ s2" f$ C4 t# ]  e, W# l4 ^
    30 e1 e3 W9 i6 x! J4 n! p8 \
    4
      {3 R2 q9 G4 u4 m. c/ h# w. q51 f+ w# H  @  n, ?; Y0 u* w8 H
    66 E/ Z: l9 D! t( I" L% c3 v
    7" G' P0 s4 H, W
    8' X6 k& @5 B' j" i9 Z9 J
    9/ J* F7 i0 F6 f% k2 @! V2 V
    10: I8 I0 t4 Z. E
    110 l! v5 H1 v! [
    122 T  r$ S3 s6 T+ v4 U) ^2 a
    13
    ; R+ F3 [9 k/ a% k147 S3 p) ?2 ^8 V. K
    158 k) B: h5 \5 I! ?7 G
    16% ^. O, }- h* b, Q9 A
    176 X( n. |% e8 c9 |/ J  ?* S- v- Q
    18* a1 a$ c' S' R
    # 参考答案:
      P1 O+ C+ |+ o/ k4 itarget_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(; r) q% X5 y1 A
                ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
    - v3 H" j; n) F, I4 I' E2 I" j
    * X4 N0 U) |9 b  o, T7 Q3 j3 X$ J  Z+ Mres = df.set_index('Date').loc[target_dt].reset_index(
    6 F9 G" D4 i( X; a5 u  N( l) O            ).query("Fruit == 'Apple'")
    6 `; [3 [" y9 C) n; N0 D) ]. ?' n# u% ~7 L! J" `
    res = res.groupby(res.Date.dt.month)['Sale'].mean(
    & t8 }$ {( X- S, P0 A/ i0 [' m2 c% s            ).rename_axis('Month')7 l5 h" t- n& R) o- f
    8 R6 T% s9 Z" s. u
    ( \) H) E. U* p2 E+ |) m2 d
    res.head()
    , C. H/ N* N/ h8 W/ \Out[236]: ; ~% R- k! l+ o% @. g
    Month
    ' |) i; O4 z, `& S  \/ |1    65.313725+ H' G4 H  H5 ^- R. g
    2    54.061538
    - K9 R5 K% T$ ]' w5 Q3    59.3255818 K) H  O0 U4 B% r1 o
    4    65.7954551 W  ~* |$ [/ b! v) X
    5    57.465116/ V4 H& p3 L) ?  G" a9 z
    Name: Sale, dtype: float642 r; C  N" l2 }" ^' E* G

    % g: j! R) ^3 ~) c- I8 J( }! V4 y1
    , `6 f3 l" p) c1 x( J, Y1 [2! ~6 L: [! {& {8 l6 @
    3" `1 a* O6 B  m9 q) M
    4
    ( w, [& K4 F7 Y' k; F7 v5
    % ?9 ], {1 h1 B0 n8 ]6$ E3 i0 s/ W3 U$ R; |( h' l
    7
    8 B( U) Y4 T  _" P3 y2 n8
    , J7 a! \2 b5 `1 |+ u4 B9
    # I# \+ n) t. E0 b+ f103 T; ]5 C2 n5 f* C
    11; h- ^2 R4 m7 C$ m2 b6 e
    12( ^6 l. d+ L' C9 C* J2 ]
    13; s( e* z. j7 W2 k8 x) Z
    14
    ' z3 L: U6 K- K* E1 B; q15
    3 S* E% c- w  @7 o9 N3 E16
    6 D% d$ e8 o) c6 B& P) G17
    * B, i. h$ a8 h: l182 ^& m( I6 T3 @
    196 I, z: q) t9 S  ?* K, @. q
    20( h2 f1 w/ V8 O! l9 P: f' a
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。7 S, w! P6 ^) I9 E  J" ~6 L, q
    result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.
    ' M1 ?- T7 w, ?. a                                        dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
      m2 d- D* \9 K- J                                       
    $ T- [# ~2 h$ |* Vresult=result.unstack(1).rename_axis(index={'Date':'Month'},
    . A& J4 h8 K# K+ N1 @- I                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.% q$ a# m8 p& G' v
    result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)* C3 K. |5 I2 z- z% R
    result.head() # 索引名有空再改吧
    , O8 _' D+ d* B4 s0 a# X
    % A* n+ H7 |- h( C' k          Week        0        1        2        3        4        5        6+ L2 ^; X) b8 R4 f
    Fruit Month                                                        1 @- z. u) P0 {' l' C
    Apple        1        46        50        50        45        32        42        23- c& C! v/ w# I6 G1 {/ C& e
    Banana        1        27        29        24        42        36        24        35+ W# Q# ^2 I# V. M
    Grape        1        42        75        53        63        36        57        46
    ( N, s+ w0 k, y& QPeach        1        67        78        73        88        59        49        72
    : v( w" Z9 ]" N/ N. f' z  `Pear        1        39        69        51        54        48        36        40
    . `1 j' _/ V/ S; ?1
    # k5 O$ \6 ]" B4 J) z* ^2# E* e# ], s7 p' x6 S# u
    3/ F1 n) N- n3 M* p+ i! @
    4
    $ c. Z8 G# \0 e& \+ P6 [* N' n5( L/ y" E' v+ n  y+ ?( `
    6
    : ?. Q# m4 j0 c3 \( \/ a7- m+ \% K6 T" P- `5 G& ~/ `
    8
    2 }8 a6 z# [0 b9
    / k5 Q% _( I$ b9 f5 L$ W10& G, \7 m5 T1 R
    11
    ! o7 i; ]2 ~( w12
    5 k5 v1 ^1 a" Z  m2 u6 n13
    ' x3 N% d$ Z; f- n14
    % o1 S$ F' x1 }3 W15
    6 C4 \, F- m+ |3 Z6 }7 A1 g按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。/ Q! B9 R: a% c9 t: T8 H9 I
    # 工作日苹果销量按日期排序0 g7 [8 e' c6 ]7 ]0 S* G+ z
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
    . N1 ~6 |. x3 I" ^select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总
    5 k4 H0 n/ i" X4 k  Wselect_bday.head()
    " N' z! k5 i2 M0 s0 {
    - J$ ]- J4 w* O9 t( o7 C* ZDate6 N( k/ D9 K) a6 u
    2019-01-01    189
    + V" m5 `1 Z5 K. q8 D2019-01-02    482) a; p- p2 g# L+ T0 \9 k
    2019-01-03    890: z1 @9 j3 ]) n: c% a3 n0 m0 e
    2019-01-04    5503 I) j( M" {$ j8 d, R3 e6 |
    2019-01-07    494
    ; ^9 ?$ `# W4 W
    " Y1 q( h$ k7 J$ B) m  c9 H# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。  ?( V( L* D# }/ [) D( s& |
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
    6 ?" s6 v8 G; \) }
    3 d' `: ]/ U8 v* DDate
    ; C9 B% C0 d: v; C; E2019-01-01    189.000000  e& ^4 p% w$ n# W7 O/ c
    2019-01-02    335.500000( s5 \! G( _7 ]4 Y5 r* m6 z% ~
    2019-01-03    520.333333: n) J: ?6 M: _( Z6 Y7 B
    2019-01-04    527.750000
    ; C3 U! t& w0 {  r" k2019-01-05    527.750000
    3 \6 }, x$ f6 w- n2 }
    # N6 A$ c% y8 T————————————————  h2 p% b- a( b# Y' x: G2 o
    版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    - d( ^/ j# G3 ^# q7 A原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913" T1 U2 S+ S& V. t/ M+ w, X

    $ |- J: o9 w8 m( T- J! x' t2 s* H- j. G4 b4 |8 A% j8 }  A
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-7-31 02:39 , Processed in 0.624664 second(s), 51 queries .

    回顶部