QQ登录

只需要一步,快速开始

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

      W- N# N  s$ y1 P- d8 X7 x
    4 R; G! e3 Y3 O+ O# L! O2 h0 _  V. j: z; z/ m2 @5 r
    文章目录! `# z2 M3 y8 c
    第八章 文本数据
    / N+ D- F  a, l- _+ ]1 q8.1 str对象
    ) W9 M  e; T9 E. }4 e: o5 F" e1 |% W8.1.1 str对象的设计意图
    9 G+ h( Z" ^& C1 B8.1.3 string类型
    ) |. g! _$ E' @& G) U8.2 正则表达式基础5 S* P. w5 K" n7 i+ p" G( H6 }8 C
    8.2.1 . 一般字符的匹配+ I4 b) _4 S% h# |7 e$ y' j  `
    8.2.2 元字符基础' G7 _6 `* T0 E! F- X0 O5 l
    8.2.3 简写字符集8 V5 h9 x' _5 W/ Q3 ]9 _# K
    8.3 文本处理的五类操作
    * N$ O* l: B- Q1 @% i8.3.1 `str.split `拆分
    9 c9 w/ s! r) P1 L0 [  b4 q9 a) J8.3.2 `str.join` 或 `str.cat `合并
    & L. r7 s3 f5 a. L9 @& A6 ]8.3.3 匹配
    * G1 \) b) a) V9 X7 C9 e8.3.5 提取0 y: V! {+ P5 e( ?6 z
    8.4、常用字符串函数
    ; U3 _  g2 ], y8.4.1 字母型函数
    # ]. @% z$ j( E% a3 K: k# g: l- g8.4.2 数值型函数! y: r: C7 g, y& u/ ]
    8.4.3 统计型函数, j* M1 a" k4 k. ^9 O: B
    8.4.4 格式型函数/ L  \" @1 t% @' j4 u) u! B1 L1 x. S
    8.5 练习
    7 T+ p) q8 `  U: ?3 Y9 S! n" w0 UEx1:房屋信息数据集8 d, C1 g, n+ ?+ k) A: q" C" w2 B
    Ex2:《权力的游戏》剧本数据集! q2 d; V( J- E7 g% p
    第九章 分类数据
    - Q# e3 e/ P0 D6 [( h7 s( f' R9.1 cat对象# `3 L$ T! N! M+ o% A
    9.1.1 cat对象的属性+ g1 s8 |3 Q  ?+ k6 z
    9.1.2 类别的增加、删除和修改" i' ^. l. a8 X+ r1 r- t
    9.2 有序分类
    ; x& T, W% @) X) f! c4 p) j9.2.1 序的建立
    % c! O8 ?! q. T) Z9.2.2 排序和比较' l% k. }, @1 M+ o2 c+ b0 h
    9.3 区间类别2 L$ X6 f0 b# J  C% v
    9.3.1 利用cut和qcut进行区间构造3 `, r/ V9 i; }1 U' _& v
    9.3.2 一般区间的构造' d+ L, C& Y% i8 u0 }! ]' S
    9.3.3 区间的属性与方法$ y* o- W; U, f2 a" h
    9.4 练习
    % m, m1 ?* M8 i: r0 Y3 h- X8 S: WEx1: 统计未出现的类别
    , H5 f& X5 K* I6 k5 gEx2: 钻石数据集# K9 L- d" p# l% J, R1 w
    第十章 时序数据
    8 {* [) h4 o1 x9 f0 |10.1 时序中的基本对象
    ; M- P% [6 i5 X* R/ f10.2 时间戳5 ^+ z: L' [: B; J: w0 h
    10.2.1 Timestamp的构造与属性% `9 L1 @1 u' h) j3 k2 a
    10.2.2 Datetime序列的生成
    ' c* |' u, G5 b7 e10.2.3 dt对象3 e' E7 c  p2 Z) b8 k+ X
    10.2.4 时间戳的切片与索引8 ?$ A* h! ?7 S* I
    10.3 时间差" j' z9 j: u8 w1 U- f% ]
    10.3.1 Timedelta的生成
    + T8 x! m% e% ^- ^1 b% [2 e10.2.2 Timedelta的运算( E  l: t" A5 P4 e8 g" F  B
    10.4 日期偏置
    ! ?. X9 K+ Z1 y9 l5 x6 `10.4.1 Offset对象
    1 H, P$ n' S/ A2 S0 {2 L10.4.2 偏置字符串
    * h' ^: ^4 m, b$ L10.5、时序中的滑窗与分组
    # g, s: ~8 s/ n' {9 i$ p! s; R10.5.1 滑动窗口$ t, j3 j# w. ?3 o* C4 I
    10.5.2 重采样
    " u2 j5 a" }; I5 q3 }) Y( T10.6 练习
    # z( J) G7 c! tEx1:太阳辐射数据集5 B$ ]/ M1 N* z% F
    Ex2:水果销量数据集, d% r/ i- E' _9 Z1 L
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
    $ L5 s. m+ A  e" G4 h' r传送门:
    5 ?& ]" y% m! z2 a/ L& Q) y  V9 H! T3 m( R/ }8 L" N( H
    datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)4 \5 M3 V; g. f. e# ?5 @* B* B0 f' P
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
    ! M* T5 l7 L; V第八章 文本数据
    - s  T; H$ I' b2 o8.1 str对象8 G2 J, D5 @& T+ O0 R2 k2 a
    8.1.1 str对象的设计意图& y$ K: F* x" k5 l. r. y  {
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:- v' T3 l5 ^9 \6 o" V
    , I& N3 ^$ z  W* u7 D$ g
    var = 'abcd'8 w% A( n6 D5 J/ s0 z
    str.upper(var) # Python内置str模块, a; \& r: {+ y
    Out[4]: 'ABCD'- x, J' c/ c) n( C

    8 n' T/ p7 b. H7 Fs = pd.Series(['abcd', 'efg', 'hi'])
    0 P5 e; C1 \9 D/ Q# a
    & N( j+ T# O) `% w5 ?# d) Gs.str
    * a, a) y- n3 ~; zOut[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    / l- k3 z- h2 l% V/ x, z8 r* E+ R2 e2 w: N+ V' o) C
    s.str.upper() # pandas中str对象上的upper方法9 e0 E/ m9 w) t
    Out[7]:
    ( X" f# g/ [+ w% g8 C0    ABCD
    + h! Z) ]3 G7 Z4 Q3 h; G1     EFG
    / W  Y% s1 K; P' A" l. h9 l; K, e2      HI$ Y! b; l8 h: G
    dtype: object
    0 O6 N: M9 M8 L* p* [1, I$ |; g# g. E  Q6 e6 ^& l& r- u
    2, X8 m3 G( J/ x# H. W1 X1 R3 p
    3
    7 k1 H& c, l5 O+ N# N1 b$ t4+ K3 _9 d' J4 a
    5
    8 M( q8 G9 o; u1 B7 N  N6* B# ^; W1 I- Y& J
    7! ^. B3 w& I# [* n) Z5 v
    80 ^7 b% U9 A" ^- c* Y
    9
    4 |3 B# O9 Z0 F5 C7 x# W8 ~10
    7 V  c5 F* z" M; j11' a. a8 T/ y- c
    123 J& D; n& T* t0 r. K4 S
    13
    4 G6 Y  {1 `& \; |) P/ n14
    5 [2 ~  V0 O3 X0 @6 Y; k$ K159 X4 H3 S7 S* Y1 W" c1 r6 C# A
    8.1.2 []索引器
    1 {( l8 [! i, d* S- R  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
    ( b. \' [) Q9 S2 ~9 U: g8 v3 S* W  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    3 g' j" B0 g0 q/ }! z! R7 ~. i$ ~
    ! J  B+ {3 O: S% H8 X; {$ ms.str[0]
    , |" `( c8 ^" o: J4 q9 Q. x5 T% C, d5 iOut[10]: 2 L4 a3 _% J. A: h3 G& G' m% g0 s& C
    0    a
    . K5 i1 V" u, V1    e
    3 R% ?: f1 J% ~  X9 p, M9 z2    h. _( e& m/ b3 h; O. S
    dtype: object
    ! |. S, }5 Y0 W
    & i% U9 B! |, Y1 B. |s.str[-1: 0: -2]
    . S' X" H; }7 ~, B/ mOut[11]: 3 b0 E$ d5 v% T4 s2 {
    0    db0 U  U; G, P2 T0 E' u3 V
    1     g& @+ l0 {& d9 s. X  Y5 W: g
    2     i2 q- |# A) Y7 ?
    dtype: object0 {2 c" e% y# k. f" _4 w! y

    - N& C! d! h8 h+ z7 G8 o  os.str[2]
    ) a6 w# V  _* x5 r, J0 nOut[12]: $ F+ Y7 R! a/ ^& s; @3 P; D* W& b# }
    0      c9 p# a* d& c, H3 \
    1      g
    ' ?3 ?; O9 o- G& F7 S" f3 [2 W0 S2 ?2    NaN1 w  ]2 o: t1 R- E, W+ v
    dtype: object
    / R( c& C) V! P0 W& n- R( A0 D& c  [
    17 J# e1 p, g. W; u( R# z
    27 d4 w% L3 `+ O& q
    3
    1 M' C' B1 w6 K' _! R4 K6 k4
    : ?7 V/ o$ E6 I% ?3 r* E5/ f1 x0 P- l/ h& {' m5 O
    6
    5 J1 o0 C' `! H* f. t* |6 k( z7
    5 t: S6 b) L6 h) Q$ X; A( H7 W85 G+ s6 q# `- Q
    9
    " c; ]% P' r- P5 H/ X, ]10
    ( H& B& L# o8 B- B1 k11
    5 b! P6 ^1 q1 l; _  s12
    ) Z% o& M9 J( N13
    - Z5 ?% k1 r8 U; @) R14; D' c8 j7 ^3 U  H$ M
    15
    . p" W9 K5 E( ^; i* O' ^# `16! U0 X' U, v3 Y. I" I) S. o
    17
    9 i7 `5 S) X% X+ F9 w# P18
    / T- J( k' E0 b4 |' S9 C19& G3 A9 y, m* T. I+ J/ R
    20% M0 E5 N1 f  m$ L7 ~# V( ~
    import numpy as np$ g7 V6 e* s7 {' @; K
    import pandas as pd
    " c0 H/ ^, `5 R" T
    ! E+ V* J# d/ i# ~7 y# U7 ps = pd.Series(['abcd', 'efg', 'hi'])/ J: n2 L% T) W! ^& w
    s.str[0]
    6 ?7 u: f7 K* Z) T- w/ j" P  f1
    5 s+ j/ Y5 O9 a2 y% a2
    . w# W  X$ T0 y0 H3+ j" Y* X9 I# A6 @
    47 Z3 @" O# V6 J& z7 F
    5
    9 O( B3 B- `3 ^" `0 d' c0    a* y) N5 i) H5 A" H* ~3 b
    1    e
    - `% R" J2 K1 H+ A* H$ H' y  V2    h
    # S7 @9 T: {- M7 S! q( n" ~dtype: object- V0 E8 c0 _0 e! [: z
    1
    1 z- `3 E# v1 V! t  V; M2
    / t$ W% A) D# K, u0 L( I37 \& K% e' O3 @/ m6 a
    4
    " ~/ [) g9 x: W* u6 C# P9 u5 @8.1.3 string类型1 c( U. T& J. U* K5 G) \5 w
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。  I, G( @" Q2 O( H( j6 Y5 |
      总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
    5 P" O" W; D: ]! D7 W" Y6 o
    , z  y1 r, x# _9 o二者对于某些对象的 str 序列化方法不同。4 i  a6 Y6 n' P' N: u2 l
    可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:5 x  D, c# }* S+ J7 x2 Q6 N
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    4 Z2 h: r  }2 c+ d' I# H6 cs
    3 j5 v7 y- @& Z& z9 |( g- \1; }5 |1 S9 N2 n* Z$ M3 N$ z4 S
    2" v8 V8 l! ?  E5 k9 n( j1 b, j( C
    0    {1: 'temp_1', 2: 'temp_2'}! I3 ]3 r/ ?/ n9 K/ N
    1                        [a, b]! h& o. e. a, Z3 e4 i% |+ j: M. d
    2                           0.5
    7 F9 B# B) T% m3 b; [- I- `3                     my_string
    % T6 X2 o$ {4 @3 T5 `0 Ydtype: object
    6 G7 _, _: F$ I8 {0 n9 l; V1' w, `2 ~8 t1 J
    2
    ' A. M+ ?+ L4 P( Q4 s33 _8 |3 j0 Y9 }1 R  b2 q
    47 z% T3 W' H2 F6 X9 Q; w) A
    5
    $ D$ f9 ~8 Y. ]s.str[1] # 对每个元素取[1]的操作
    : A; [& k* W+ i0 d: x1
    ' D+ u% x/ a; i3 E0    temp_1  H6 a6 I' ^1 W( ~  X0 `, t. q8 l% F9 y
    1         b
      ~+ X" v+ n+ c2       NaN/ X2 Q! o9 w' S9 L5 H! x
    3         y
    . J( c( y' v7 t) w& u7 K9 x7 jdtype: object9 m0 A1 h5 f+ G7 {+ H7 ?
    1  C8 B6 w+ W& E
    2
    2 i! g7 S& ]  ^! ?; X2 u) A3
    7 @- E/ G4 ^( s* z$ E$ d7 W4
    $ k3 w% ^2 b) Z) F% b- s5$ G# f, D+ b8 s
    s.astype('string').str[1]
    2 X* U8 {7 W, \1& Z: }4 y& [  V1 c/ E+ m
    0    1
    ( M' ]; N* k  }1    '$ A2 R' s: O3 Q3 F, A+ z
    2    ., q* x1 M- h' v3 C# B( S
    3    y
    9 [3 m) P: R  z. j# [; N# zdtype: string
    8 t" y! Q8 n1 [2 F0 F1( d  v: e3 P  w; b0 r
    2! J6 ^9 `0 g# F& d
    3- h( M6 P$ U+ E- Z$ t3 j
    4
    ( a5 p  e: a* u0 E5: C* T6 W& N# [4 d2 p8 L: S7 `
    除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    0 ~3 W, X# G7 [! `2 E. X0 m; L! B9 [% E# U* ?, L) f  D6 ^
    当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。/ A3 |. A$ m; W' N
    string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。3 ~( T$ n  L9 }2 t3 t" G
    string 类型是 Nullable 类型,但 object 不是$ ~, {2 a5 G" g" q2 P
      这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。$ \9 X6 T) d9 v
      同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。; l* B: ~: ^6 Y. g
    s = pd.Series(['a'])
    ! I) ^8 v+ _% d  z' c2 f* S& @8 u$ E; s1 f/ [& N
    s.str.len()
    " C% C! D2 \( D/ bOut[17]:
    + S, A/ c& V3 y9 ^4 |0    13 u8 U1 K& e4 V
    dtype: int645 {, Z( `, v5 A( [. \$ M* S0 m2 I

    7 n; K) j. |" h9 Ts.astype('string').str.len()- p$ V4 X1 d2 ^
    Out[18]: 9 R# [" Y+ N" |2 l+ [1 G; W  n
    0    1
    0 K, U- [2 U, _8 a( C: m2 j' Zdtype: Int64
    1 ~# S, [) z) P. V9 t; e3 w
    + ]- f) r+ a' P7 ^# ys == 'a'- c* _! R) d3 y9 |7 H
    Out[19]:
    0 L! C- m7 j" R6 k; R0    True
    7 h+ ?, a- X. Y7 B, k7 a2 y; Edtype: bool
    6 J" d# H7 T& I; \% g% D4 Y  f4 _  w- V: t. K5 Y
    s.astype('string') == 'a'( [; d. Q6 d) C+ j0 C
    Out[20]: % R* @& ]! k& E
    0    True  m+ L" H+ C7 A! G2 N! h
    dtype: boolean% P: j' |7 a/ _4 k- l2 V; F

    7 q2 r6 E3 F1 k- U% Xs = pd.Series(['a', np.nan]) # 带有缺失值, i1 e4 n6 b6 W8 @6 v0 n: w6 v. H
    ) n/ k( b6 F5 F3 Y7 F4 `1 c
    s.str.len()$ N$ {; O$ M, j/ n- |3 A0 @3 T
    Out[22]: ; H8 {% }' e7 x
    0    1.0% d. A* t( G# J4 ^: s& a* }  ~# U
    1    NaN+ Q! {/ {4 H7 J# K5 O
    dtype: float64
    7 s6 n2 e5 W: s  V' E3 @+ ]/ v$ {: Y/ G' t: l: [8 Q9 s& T* h  `
    s.astype('string').str.len()
    : }# x8 O8 X+ ]8 l( }) O+ JOut[23]: 8 }5 V3 h! R+ A3 g! m4 N
    0       1
    0 }5 l3 F% G$ A9 W; p1    <NA>9 P4 p. e$ @1 K- T# x2 }
    dtype: Int64# g$ P1 b% T! B4 I

    ) N0 W% M+ T7 i9 ks == 'a'  p7 \7 Q4 k! r7 A
    Out[24]: / ~6 ]' F5 K3 d: J; k
    0     True$ o. E+ C9 i+ Y+ a9 Y; z
    1    False9 q/ I. X6 v, A" q' S% i
    dtype: bool
    . F. P- D+ g* q' V$ L& ]9 \
    6 R2 i; R3 B1 V  W' u0 _s.astype('string') == 'a'
    / O- V& _( L! R: |Out[25]:
    0 f8 {; S" y# Y* v! @0    True* }4 D: t3 ^" H6 j# e6 Z$ z
    1    <NA>
    - @" ^* I9 d! J# tdtype: boolean0 R7 G) e" B0 c

    5 l; N: |$ r, h* I/ t13 h# o" L# o8 T$ v% t/ f7 v/ h
    2( D4 Q2 O9 |9 v1 [( z8 A
    3* T1 z% J7 U& k3 U. X" X
    40 G  k0 M/ a* R" W, ^4 V( b; ]
    5( u& i0 f0 _: x* a- b* G5 P
    6
    - r5 g  F, h6 x* q7. f- u# S/ L6 l6 @$ L. K
    8
    3 A+ t7 n3 M7 _4 _  `9- x  A+ a. p& g) x! n& M
    10
    6 n! I7 U7 P0 C2 Q1 ~11% ~' {  q3 O: o1 `7 }
    12
    5 X. Z* }" ]( B1 Z- O9 D13/ c. P& n) ]$ J' r( l
    14
    - v' ]% e" X  k2 b15- j  O2 T* [$ s9 Q2 Q
    16
    " T- C( t3 I/ O/ z+ U1 i% V& ]$ S17
    " o1 y- x8 S& c" g) P18
    & S! q, y9 F/ l19
    ; p: J4 R) m; ]2 w20
    1 U% v" q& H: u21. D+ `1 c6 Y, u& ^: J; _
    224 J5 k. {3 e1 A( J; L
    23# h7 ~. q+ o0 r
    249 w; h( r  S8 S0 M6 `/ s  {
    25
    # z! Z7 Z3 J+ {26
    ' G2 q' _1 w# T' f: P8 `- w, R2 }27
    9 W# ^5 k- E& F$ g9 N8 Z28/ T+ a- ~# O) R
    29
    ! T7 J: L& b# w2 d& |: c309 ]  c$ \) L1 F0 N# @# m9 q# Z
    31/ F1 q9 W. c  K
    322 w1 _& t& k# s- O9 y" `' \
    33
    * e/ @' y; {# ]' _* m& g; E342 ^; l3 q- W& S; U, Z2 w- ]
    35
    9 [/ e% X) ^2 D$ j  ?* R( }- K36
    6 u7 y4 }% j( Q  S% z37
    ; Y+ T; q0 j+ V. ^4 E6 s2 M. {, R38. F3 j/ g. Y, ~
    39) c4 I: u0 [% x3 Z7 ^5 w( W' G- T" X
    40
    9 b# H" H/ T2 I5 ~41
    % q7 [8 H8 [0 V42* G% R2 E4 H6 a- f6 |
    43
    , h' W! V+ Y. ~  D44# K0 O+ b$ [8 C) h
    454 Z7 n; S. N0 l( x0 K
    465 m6 m4 K2 c, Y- b: }9 E
    47
    ( D& z/ F; M! a4 y# X" }, |8 X  对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :2 s1 ]$ m$ q9 K3 y

    1 p# {/ h# L! f) m1 S) c' W% Hs = pd.Series([12, 345, 6789])" {% s% {" @5 h% V4 u
    ; q  n& N4 V8 j6 z# l
    s.astype('string').str[1], W3 F' D6 Z. l' f4 H% L: O0 ^
    Out[27]: 3 n. S" D# I) b* p4 P$ c0 }
    0    2
    # ~2 c& o4 b7 M7 ^( C$ r1    41 {! H% @0 Q6 `& T4 X* `
    2    7
    $ Z9 J. C0 `) J! W, P. N) f& s% |/ jdtype: string
    0 v% j( v& r, r1
    $ x- |/ o( c! z# u: l* V; F8 q( J1 y3 o2% G( Q: D- J+ c' _' a6 y& ^& g
    3
    6 t5 P& K7 k5 I0 M0 l% e6 ~+ @4$ M- R: W7 h% ?) u# F; ~
    56 I+ [- Q8 {* e( R  ?
    6. f; W9 j4 i& N; E% s7 C5 K* x
    79 E; [, K3 Q) d8 T2 j
    82 \' m* t' h+ m, e+ ~6 c
    8.2 正则表达式基础
    ( W4 B0 I  |6 B! ?  k这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书9 V& u$ X0 `7 @! `7 s1 F9 v# ?
    # R5 J" X1 K5 f
    8.2.1 . 一般字符的匹配$ \3 @& B  }; @# P( J& q
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    % B* o, f2 u  C0 N3 Y! R0 R" z
    ' l( v* _0 e$ k1 A, l6 b, oimport re% a8 j) a3 v. q+ o! d0 z$ ]4 S! @8 a+ P
    5 [6 _, B3 F+ Q
    re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配! w7 e& i# n, ^* B' \6 J/ _+ \
    Out[29]: ['Apple', 'Apple']# P8 H! p+ _5 u2 U
    1
    3 j( b5 d/ M0 z8 }- l# k2' J  }; o% |& j* O: ^
    3) |4 p7 z+ x  N) w
    44 v0 a% Z( V5 ]" D
    8.2.2 元字符基础8 t$ g6 L/ X8 |$ F; V
    元字符        描述
    2 L" d' p; M0 B# Y- f.        匹配除换行符以外的任意字符* ]- U5 u) b3 v# N9 i) Q
    [ ]        字符类,匹配方括号中包含的任意字符) ^# q4 e: }: P
    [^ ]        否定字符类,匹配方括号中不包含的任意字符2 y  g) v  \$ _; d4 _6 C8 J
    *        匹配前面的子表达式零次或多次( P3 |: y5 r- R; {! ~2 y
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字1 O. p# C! u* l' Z' \
    ?        匹配前面的子表达式零次或一次,非贪婪方式* U( X; u& _4 `/ t
    {n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式1 l- N9 m+ t  S# t, F3 e* s. [
    (xyz)        字符组,按照确切的顺序匹配字符xyz
      K) }. T6 q/ y+ t|        分支结构,匹配符号之前的字符或后面的字符: t5 h2 I% [* T
    \        转义符,它可以还原元字符原来的含义4 J; h$ t! \1 c
    ^        匹配行的开始
    + P9 i4 j) ?/ B) o  t$        匹配行的结束
    * k/ k  O" ~  m3 _1 w6 a4 |import re) K& o4 R9 H+ b
    re.findall(r'.', 'abc')1 U0 g3 T. C5 |0 n
    Out[30]: ['a', 'b', 'c']( K$ x3 [* c0 d7 u" H- E; ^
    3 u& Z) _8 ?) }- `8 y3 b, M$ E
    re.findall(r'[ac]', 'abc') # []中有的子串都匹配
    + |- @1 e- N/ d; u: l/ Y7 W9 xOut[31]: ['a', 'c']
    , s/ T7 y# c6 ]
    / \8 M# R# d  P: z: Zre.findall(r'[^ac]', 'abc') % b+ a8 h7 K) H/ i
    Out[32]: ['b']
    / O! ?. Z) d. k# N9 G
    - T; t" t0 m8 l: }! i2 [' K1 T6 Fre.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    " R# p, U3 T( K: Z1 U: \Out[33]: ['aa', 'aa', 'bb', 'bb']
    9 k7 p" R& n; @$ g' n
    ! ]' w& S2 Q0 o5 [2 dre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串1 \+ b% _) G; L+ R/ K/ ]
    Out[34]: ['ca', 'bbc', 'bbc']; J# G# H+ E3 d. ?  ]) C
    ; e' N8 B9 m; r+ W/ @+ Y9 D* y
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。2 r2 f& S! V% @( [+ {4 F
    """! q* P! V) c' V, W$ v1 N. F
    1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。$ f) p: K* h+ t- k! b
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边9 u- B1 a: R) i; |4 a1 i: [' ^
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
    ! K: V6 ^! ]7 c% c7 W3 i但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a4 E4 @" |# }# l  J. ^- H
    """; B  ^' u# H0 M: ^; s: t

    $ `, i! L' I: N9 k7 Hre.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。) Q  k. {% Z, E7 e
    Out[35]: ['a', 'a', 'a', 'a']
    5 ^% O  W) p" d6 ~
    6 T. ]9 G# d0 ]0 ]* ^+ w  l# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
    : h$ E5 @; W( i, S- g1 ?2 c# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
    & x" \" v/ I3 \6 A* L2 ~0 h( rre.findall(r'\\', 'aa\a*a') 1 p4 @/ @! P( J+ X; q) d7 Q
    []3 ^  d$ }. \! |# D) z% R$ k2 ~! f6 d
    2 J' l& b1 Z; Z' y( Q
    re.findall(r'a?.', 'abaacadaae')
    * v4 Z3 L# X2 {Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    0 d8 Q3 r6 g$ C/ ~) O" ]6 h& @* i% x( n; z. T: s# w
    re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
    5 c2 i  T7 p0 u2 s% i4 n8 M[('width', '20'), ('height', '10')]: b* [# ]: L: y2 ~" l% T

    - \8 K( h8 B" h0 J1 j8 F' ]1
      F6 n) }5 X; P; Q1 d. r1 N24 Y6 P6 m, D3 |8 h+ F( Y% {6 o: b' s6 q+ k
    3& _7 k' ~$ C! a+ M
    4* k$ Q! B6 x$ s8 z
    55 w2 d% s, A: V
    6
    8 A) G% u' |1 M2 M73 ?8 }$ m1 X0 O) F% m0 w3 K
    8
    8 O% Z/ w3 N  |& `9; P- s" Q* o9 |
    10/ h  O* M0 L4 c% T6 t) {
    11
    # b* p! G" D3 B  R; S12$ M3 P0 ~; P! J9 f" o; D
    13$ n5 v, r) ]$ W
    14
    + f+ n! l0 q+ I) q1 h( d; w2 V1 X154 A/ J9 u0 ^$ r; b& d0 j
    165 u; T2 B& A$ f
    17
    ( V# F/ l" `4 W. e, r180 J5 s0 v& }0 F8 u
    19
    ! p; {) N3 y: V/ q9 s! H1 {4 A206 \+ Q  e7 K6 w4 h; {
    21# M* k5 O, w# n1 D( }* ?
    22/ n/ N6 u. Z% \, ~# z# a& t2 E
    23
    ) M) ?+ D3 Y4 f2 ~  F7 C# m24  b* _  y( R) w3 ?7 i$ ]/ Z
    25! H" @7 V- F( @5 o6 {2 P6 ^8 r
    26
    ; {+ j5 h* c" f! ]0 B) I2 j, ?27( A# ]% v+ N. i  N3 [" L  Q
    28- y( W+ J/ C/ \) w  T2 u
    29: l' h5 e; X- k/ V% m" t( `. P
    30
    , o- B; L! x: l1 T+ g5 t2 b31
    ; U& P; n, |) k1 K  t! E$ o32: W& ]/ v6 Q8 n! d! A8 R
    33
    6 H+ y8 v6 ^' X$ C4 Q! s34
    , F( Q% q5 l, D* q9 p* [0 F) U+ P354 p( Z0 ^7 q" G9 j4 A
    36: h* \$ W, u* q0 w7 c) J' o8 V
    37
    / e7 i5 i' ?. P8.2.3 简写字符集
    0 a; t5 ^, N0 R  k: ]则表达式中还有一类简写字符集,其等价于一组字符的集合:2 Q) j. z/ Q8 X% w! V

    4 {; f0 L9 c. \  U' p) S7 N6 T简写        描述" c+ @& o" N! S, W
    \w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
    & m" m; }$ _8 p/ V/ b! e2 Z\W        匹配非字母和数字的字符: [^\w]
    % L) z0 n8 E. y5 I6 C0 B\d        匹配数字: [0-9]
    , F% i; {3 B0 @\D        匹配非数字: [^\d]
    5 [  v- j( G1 A$ v( u" i+ }/ ~\s        匹配空格符: [\t\n\f\r\p{Z}]1 |2 X3 x; i5 P
    \S        匹配非空格符: [^\s]4 `" m* Z' v5 Y9 v0 p) A5 k
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。6 W: d1 |1 I# @" R
    re.findall(r'.s', 'Apple! This Is an Apple!')
    7 Z% i9 x; r6 A* O! rOut[37]: ['is', 'Is']
    " {6 Y$ h2 w5 ~; D% y: L0 @/ @" m/ _: J; J' }
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次/ y1 V, A) h. @8 @. C
    Out[38]: ['09', '7w', 'c_', '9q']4 S# o1 E3 K& E7 e- Z
    $ A1 P  {9 V. h0 a
    re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)6 L- c7 w* ?' T! U( V9 U0 @
    Out[39]: ['8?', 'p@']5 Q( C# h8 T8 j- Y; s2 }
      W3 n$ F6 t  t7 n& L
    re.findall(r'.\s.', 'Constant dropping wears the stone.')+ k2 q0 R1 L1 a) }7 [$ C6 z
    Out[40]: ['t d', 'g w', 's t', 'e s'], l$ O9 X; M' K! \; m: p
    : M, |- O# E' p- h
    re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',% ~5 X* J: W( V2 X$ C) Y
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
    % r( s5 Z2 D; E. A' v; J$ U& ^. D  M! F4 B: j. b% {! n; ?
    Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]! S4 T( Q) q9 t- x9 q, E

    $ B4 v( g( k7 p( {3 G3 R1
    1 B6 n5 X' c6 @+ K1 h2
    ( O) R- y0 f* d3 _: w0 A3
    / O- q, }4 v* D1 e+ @* ?5 z4
    0 w8 g+ ~" F" w* r5
    ; d( V( d" V- N' u9 Z; @! A67 }$ @  y( F8 v
    7
    5 n7 z+ S; ^5 T. H86 H) H4 K8 K1 p- e6 ^
    91 g( R/ N! z9 m9 x* S+ [
    107 k# m( Q+ ?0 w6 c7 [. ?4 N( H( z
    11: X; A; x7 S* x% o
    12
    3 D& c+ n& w+ D8 {8 v4 T13
    - N# t: ?8 ]" \  W14
    ! {2 Q& M3 S2 _+ b7 k15
    6 j4 d: h- m; A) f16
    $ Y  p4 k$ V$ D, S+ B8.3 文本处理的五类操作
    8 S5 {) S4 i0 z+ R1 J2 u& N- d8.3.1 str.split 拆分- c! b# G' j) H+ `8 I! W+ n
      str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。1 Q, R! C' l6 @* L& C  z" |

    ' c$ @3 X4 e  ~s = pd.Series(['上海市黄浦区方浜中路249号',
    " n2 v6 k2 f. }  [' l' B0 h; ?. _" w            '上海市宝山区密山路5号'])
    0 O* d. {6 B' P0 m' h
    4 S; J, m7 `+ S6 ^7 Z, J: \9 D8 _/ G
    s.str.split('[市区路]') # 每条结果为一行,相当于Series
    - `9 s3 ]& M4 @; _# \/ e8 O, kOut[43]:
      [- i  `1 t( U7 m" ^9 ]0    [上海, 黄浦, 方浜中, 249号]: z+ {: K( z6 F8 p9 x
    1       [上海, 宝山, 密山, 5号]% Y! K7 j& F6 {; m/ N
    dtype: object1 v3 N, j" I  L, \& A0 E

    ) H- i/ w. |6 K$ V$ w1 Ws.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame' y+ `! O! }8 `& H
    Out[44]:
    ; e4 c5 l3 O1 u    0   1         27 _! _% q/ e7 S8 U, L
    0  上海  黄浦  方浜中路249号' W; J0 ^3 p& K+ s8 H
    1  上海  宝山     密山路5号0 `0 b; h6 s+ X+ j4 Y; @; R
    1
    / m* h; v7 h) }; s+ b2% K- g: n8 ]' H/ d
    3" a& h9 o& X$ a9 A% B
    4
    0 V- M( h; v4 r, a& e5
    * t5 j' d% O( t$ ^- `0 C( ~6
    7 O* l/ u& H2 c2 k7' Y: w  k9 M. o# D
    8( Z+ i$ K! g1 p" C; Q! l
    9
    - R( B6 R3 W) i* A100 N/ {& p6 t* d! K
    11
    / t2 |6 x$ p# n% A9 ~12
    1 A3 _* e( D2 q* G13  C% G8 S1 z/ [2 Y/ q2 y
    140 {9 y) z1 o! {+ c% `. |
    155 G, u2 n3 W1 [$ \/ A
      类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:! c+ O9 Z% i( ~* Z& A  G/ ?
    : N' b$ ~  ?0 K. z6 e) p; l
    s.str.rsplit('[市区路]', n=2, expand=True)9 C% \( W1 D, H6 ~
    Out[45]:
    # h% g4 v0 k5 }3 ?% d5 f                06 m" z, V8 V# H5 S2 K4 X2 _
    0  上海市黄浦区方浜中路249号2 L& W3 r8 S  y; ?0 i: `0 g- Y1 f
    1     上海市宝山区密山路5号* D- r! s6 A' w; b; n) R2 r
    1$ L/ g4 W6 H: o# A3 l) X" b# e
    2' ~, n; N/ ^/ }( C
    3
    - K: H2 e! @, v, S: e0 p4
    4 Z  o( L9 B; f) k5
    2 g/ ^1 ]2 k- u* x2 y8.3.2 str.join 或 str.cat 合并! n7 K5 x; p8 D0 F* j
    str.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。6 q( B- q* `4 V  l1 B; W
    str.cat 用于合并两个序列,主要参数为:
    " j; ^. a) r8 b* w: s4 e4 gsep:连接符、
    , T5 t$ @6 M$ n3 K% V" b0 z- Y8 ~join:连接形式默认为以索引为键的左连接
    * d5 h; C, K  D1 O8 j. [na_rep:缺失值替代符号7 m9 q9 {( }* C* F# E5 ]' V2 j
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    4 r! }) U0 L+ r% x" H8 T: t: u& u3 ^s.str.join('-')
    ' Z' p9 M5 c* n  ~Out[47]:
    : {1 b% N+ H) ~- L1 x1 D. Q* A0    a-b+ k& u" q2 L7 \4 Y- P
    1    NaN
    : q! A& j" o8 z) Z/ ]2    NaN
    0 ]2 z; d7 |- d$ H. kdtype: object) ?5 t* o8 Q  F" x! I
    1* \5 M& o0 @2 U2 b7 w6 E% w9 L
    2; ?* j  r; }( h7 s- A: W; v
    3  ~. X* x0 ~, y
    46 l: L& G5 w8 a% k7 i3 A2 P% P& a! e
    5
    ; L6 g$ T, u8 j64 F7 b' y, ~6 v8 m
    7
    & `& ^4 x5 x6 ]s1 = pd.Series(['a','b'])$ _2 v, }7 s, e4 M% q/ U% m
    s2 = pd.Series(['cat','dog'])- B- i% N+ c% x! s1 s' C2 D
    s1.str.cat(s2,sep='-'): ?9 d* I  a" R, Q& }: x/ E
    Out[50]:
      Z9 r7 e) n6 k0    a-cat+ l+ v2 k  L9 I2 ^9 @) t' c  S
    1    b-dog5 d4 p, y! m- `" d6 W# n
    dtype: object
    3 }: b0 w  {. O, X, N
    ; b. [9 W! \$ f# c- }2 }1 @s2.index = [1, 2]/ f/ F& ?2 T; o3 k$ [0 _
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')* z+ {+ }9 g4 E# t% s3 h, e
    Out[52]:
    . M$ ^) K; j4 p- n0      a-?0 B+ `0 T% J/ p+ S7 k( p
    1    b-cat
    - x* T' k( ^, [% p2 Q! O+ K; f2    ?-dog5 o$ _" f# e- {7 B
    dtype: object2 Z$ f- n0 K" S/ z3 r
    1" o2 P. @: h# C2 r' M/ d
    2
    . ?% `9 ]/ Q- ?  _: Z3& `; x8 a7 W5 E2 _) p+ E6 F5 [
    44 ?; n4 r- T$ u; g4 s
    5
    6 G& z: \- ?  D* h6; Y( U; y& K1 C/ k, _" W
    7
      o3 l. D! g5 x' z# p8
    + C2 U1 r# ?6 p+ Z97 k6 p# b! d; E$ I  s7 d
    10( X# ^1 n; n- y& _1 t
    111 D5 r. Q3 ^! d  }- z
    12# o( G  N! T' p& F% P' s$ j# H7 k
    13
    $ D( j8 T6 p5 V/ G  ?8 Y14: a6 Y% p3 g/ E
    15, Y: P8 z1 d# a5 @" o8 C. ~9 \
    8.3.3 匹配
    7 q- ]2 r; b/ [str.contains返回了每个字符串是否包含正则模式的布尔序列:/ Q0 z. E! ~5 r3 Q* H
    s = pd.Series(['my cat', 'he is fat', 'railway station'])
    / H- I! h, m) T$ m9 j( u5 w6 Qs.str.contains('\s\wat')
    " g' e- l  e* r
    ; P! K4 G* Y$ Y! v* N0     True
    ) E3 R: b$ E, e. g; G* A1     True
    . C* H; F3 x, r' K* s* w2    False
    4 _, L8 ^3 ]! ?7 rdtype: bool
    % z  q1 K$ D: Y  ]8 w15 Z1 e' [; J' G" O
    2+ j# R2 U- U3 d6 p
    3  a' x' o5 ~) ?- @
    4
    , M) \1 j+ {5 c! }59 G1 _4 N4 ~% r$ J7 W. J
    6
    2 l* |; b. L& T1 {) S+ k& h6 w78 u+ D; a1 A. P$ t, b2 R9 i8 j5 ~
    str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    $ q( o" v5 K- b! A3 |& q0 V% N* v$ e6 Hs.str.startswith('my')
    : \5 S. k, P" t& M4 ?! {4 h4 Z2 ]) Q) \
    0     True
    9 J7 {7 |( v  s/ v1    False
    ( L- ]% m% G! D6 H- X2    False! d7 `" i7 H- u9 K" n4 ]
    dtype: bool; q: A1 y) t' K7 [% h  f
    1
    ' s8 \: ^& m( N. J. i$ f2 Q9 n2( V5 m! d/ l! {1 W5 M7 a
    3' D0 M' A7 r! c6 t0 C
    40 O4 X6 T9 j! B' d" j" z
    54 w0 b. _1 ?' e
    6% K2 U- {7 L7 }& I, s% }5 W
    s.str.endswith('t')
    7 [  x  U% k3 `0 d% S
    . K% j. i) Q3 Y; P2 M0     True  g" h. c, N& T9 I
    1     True
    # a6 ]: |* U% l: h2    False1 C+ D% t9 D5 r* Z3 C/ j; O, b
    dtype: bool& [  n7 @" k) r4 z5 o2 D
    1( ]  Y. M- A5 Q  B& r( \" ]
    26 c8 X0 ^2 @7 x$ u) `+ o& Q
    3$ v& \9 c8 X6 [' f
    40 R! C  t3 R5 E$ x) I- n
    5
    ' `8 x3 O$ R1 m* w( ^6
    1 W: ]2 V" s2 E! ?) Ostr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    , T& }! j  F4 vs.str.match('m|h')4 b, |/ t2 t1 e7 n1 h
    s.str.contains('^[m|h]') # 二者等价
    / Z: |, m( `. Q, e/ Y; @; G4 F; ?" u# g
    0     True
    ( r* ]; A. Y) g  j1     True' Q6 T' `4 f/ k4 K+ M6 i
    2    False- @# y+ i; P# x0 G
    dtype: bool2 V& o1 p0 U) J; b
    12 A6 R7 p7 [& S: o7 ?: S
    2# _- N+ W4 e, M* j
    33 q" E; h! h+ O* m( ^  w3 N6 k
    4
    # T- L' X; P; i' r1 I5
    1 b: q- E4 Q+ j- b7 l+ Y- P8 S61 Y2 {/ v! E+ W' v8 b
    7
    " t7 @, O. J# V4 ^s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配. _% _6 k% w& Y  ?: m
    s.str.contains('[f|g]at|n$')       # 二者等价
    " [; }- F( Y$ S
    4 L, C! c* S, a: n0 t; P0    False
    / [' [) U- f' _1     True
    * ]2 i" F  H# J; l2     True; g, C# |3 _- c5 f
    dtype: bool4 G" o) g9 m9 x+ e3 ?" u
    1
    ; G" f9 t' l2 E8 e, r2
    , r% J5 U/ L- s0 B. U; r# H3, y9 _( E8 t' u+ {: f
    4& A% y2 k2 Z7 _+ f+ |5 M* }
    5; u7 Q( u4 R' }3 }( s
    6
    2 p# u2 D1 i& Z$ K; Z8 K) \7
    ; x; f' f. q% e* o5 Bstr.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
    # k! w; U+ R1 J0 t. ws = pd.Series(['This is an apple. That is not an apple.'])# p+ |/ k7 L7 R) _- h

    2 L" O- [% P: Y' Z+ r* ms.str.find('apple')
    ! n  Z+ C. Y( ?/ M# v9 q5 x3 IOut[62]: ) E/ X& b; v9 h: ^0 m9 ?7 ^
    0    11# R7 O: G5 e8 Z* i, _/ I0 N3 s' f4 `
    dtype: int64) G1 G5 |6 g, `! H( @
    . e! h0 w' K' Q5 s) M
    s.str.rfind('apple')
    - p" ?( C, q& ]! p4 nOut[63]:
    - L2 w7 d) X; {0    33
    1 @3 O; S+ o" Mdtype: int64
    * J1 Y# l7 s5 ?. H! v; ~1
    5 h! V. _* i- S8 h, v2
    % z- u9 S- t) d+ j2 @3
    6 G9 V6 w# x, B% |, [7 q: V4
    / l, A$ _6 G% f" y& Z2 M5 m5+ b2 |. h# I5 m0 K7 J' h5 f8 Q
    6, m. M$ ^$ v* \' X7 c" T
    7
    % U4 q! N% F- g& K$ O7 V$ }8
      ]$ Z, S  G. x) L4 X6 J3 h. F9
    # {: d, }- a  B7 o! G10) q  {3 e  t$ S/ L
    11  t; W) p( w  _4 H6 g1 b% h
    替换. Y7 z  z& s, K. |/ D( X9 C8 G# M! {
    str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。/ Y2 Y% W+ Y; i* G& L  E& R
    s = pd.Series(['a_1_b','c_?'])
    ' `. [$ j  d) H# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?9 a& Q; g0 A2 B) [! K
    s.str.replace('\d|\?', 'new', regex=True) / ~3 S! |  x& Y# j5 B2 S% E2 N

    + ]5 B3 y' o; Z% h0    a_new_b/ {0 L8 D* E( o, E: H. u4 d" h
    1      c_new5 b$ l  p2 [  t0 U; }) t) ]) o. R
    dtype: object; O" g1 u6 T; V* D( w
    1+ J. T; O6 y' S+ H% s" i7 K
    20 q) g% y! j* k* v' d
    30 j) Z& u4 t) @; S
    4; j9 H$ x0 R# N- n* E
    55 D; M. }2 |/ b$ s
    6, \+ M/ w4 g$ y( i2 a
    7
    " t2 W5 j* S: I- }  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
      @5 l9 q6 U  y/ C. ~" O6 G$ }. G% b; C3 j* u+ v& A' E7 x5 h
    s = pd.Series(['上海市黄浦区方浜中路249号',. W3 O3 e0 p5 t" H4 k: v# S: {3 J# U
                    '上海市宝山区密山路5号',! e3 ]/ M4 M! X/ n, W- K, M* E3 N9 u
                    '北京市昌平区北农路2号'])
    # ?- l" n: I3 w# i  V- lpat = '(\w+市)(\w+区)(\w+路)(\d+号)'  p6 B8 A) X$ \' ]8 |  F/ S
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
    * J4 ]# l5 }% S: udistrict = {'昌平区': 'CP District',, I$ C, y2 m- c. m
                '黄浦区': 'HP District',; g  p( f" Y+ O& j7 ^+ F
                '宝山区': 'BS District'}
    ! }: [9 ], ]! Y' c% W2 U/ v. p+ e9 Wroad = {'方浜中路': 'Mid Fangbin Road',/ v, t- l4 B4 A, c/ C1 _
            '密山路': 'Mishan Road',
    # D! n: T1 S0 n# f, i        '北农路': 'Beinong Road'}
    % a& t2 c+ t/ w" J8 Zdef my_func(m):& r# \( {* L2 G& @
        str_city = city[m.group(1)]
    0 a' D: d# s$ H! T# E: T8 G    str_district = district[m.group(2)]
    ' Y' H# U; @" j5 r& i  T# i    str_road = road[m.group(3)]
    * g8 m* G7 H& P9 }8 X    str_no = 'No. ' + m.group(4)[:-1]" {! Z1 x8 u; @4 T$ Q6 O
        return ' '.join([str_city,. k9 |: I  i0 j3 A% ?, p+ S- ^: K, e
                         str_district,
    : L) t) a  u3 j& K2 d# g: c- l                     str_road,) l& v, S4 W" P3 }
                         str_no])% l7 x4 A0 d0 ?$ C! V
    s.str.replace(pat, my_func, regex=True)2 }& @2 p) S- Q

    , [5 J, G9 D/ X% v( M1$ |1 [" T4 p: E$ ^  {! q8 S
    2
      U1 S6 y' b; C( ~3 b; R' Z3
    ' t  N1 p" N% D1 i" X6 I4& S- Y) c+ S, }: ~  y. b
    5( J6 z( p. e4 o+ V
    6  F5 \+ \- f' _5 j1 s
    7
      r: l/ S) n1 F* y  f) i8
    6 y4 f8 w& K. T. ^. A6 v; p& b% G& e9
    ( M3 H: D: i7 P9 I10
      V; K* w9 f3 o2 v- Z8 y0 E11- T1 D, W* U0 M1 n
    12
    # w& E' S/ n+ X. F4 r7 l13* B( k4 r  o: t5 d9 K  m3 v
    14
    # n7 G: [* m0 V+ d3 ^4 L; h) c15
    0 t8 P# p# k  C; h  [3 u2 h4 S16+ L' X8 o* `0 M# C; Q8 q. c3 {- P
    17
    5 D. _$ u- \3 P/ B. g18
    9 q. L, {" V  _" r; M" i( t19/ }, ^  `. X# z) b
    20
    * |3 I2 h' ?0 V" a. J' ~. z$ C217 }9 b' _* p: _% k( _
    0    Shanghai HP District Mid Fangbin Road No. 2493 K7 k# A2 o# r& l9 `% L0 k: Y
    1           Shanghai BS District Mishan Road No. 5! w; }/ @  z" {( ~
    2           Beijing CP District Beinong Road No. 2/ E& |! _' O2 ]6 Z( I" W$ @, h
    dtype: object
    ' I- g' T; Z3 w2 L9 q5 O8 N1
    ! J( b, l  Y7 a* M2
    & r9 u  W( [3 t1 T6 r' h! L: n+ g3
    0 U6 ]7 V, s, h9 X' m4
    1 ?- B/ @/ S' q这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:3 {1 G* c% N; @: r5 S1 k

    ; x; {2 O4 b( f/ S) W: M7 f( i: w# 将各个子组进行命名& ~1 j) A' ?) _" C+ k" a
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'1 f7 _- x; Y9 x; w$ y9 i: Z& V
    def my_func(m):
    ( Q1 t# B4 o5 [& T    str_city = city[m.group('市名')]
    ' O2 m. f. \' n* w    str_district = district[m.group('区名')]
    & c7 F7 q" A8 M& C8 Y( l7 v. l, K    str_road = road[m.group('路名')]
    % Z  C" [3 L( ^# D: d) j    str_no = 'No. ' + m.group('编号')[:-1]$ q8 z2 N* @+ z5 A6 [
        return ' '.join([str_city,
    ; }/ S2 G1 i1 A8 K+ o4 g                     str_district,
    , y) D/ ]" S( K# k% {5 N                     str_road,7 }& g6 O7 n! m5 |: r- @
                         str_no])- q( U# c/ L7 r* n& G4 b' @! \
    s.str.replace(pat, my_func, regex=True)
    . Z; E6 t9 J" `1
      p# v2 x0 Y$ u! Q! B2 r6 `2
    4 m1 h4 F- p" ?+ i! M5 Y38 `  W2 X8 x' x8 P5 J9 H2 B
    4# _; E4 {3 V7 p% _: `) w
    5
    % O* Y1 }0 e. N' ?" n6) G3 F7 s4 O, u5 a* P
    7
    $ _; J! m# m2 {% d8( ^' ?( L" v( d# ~1 w
    9+ v( ^) O4 {) I# a- P, p4 h4 G
    101 I' o  w4 w' |2 h" j3 T
    111 W$ s+ ^. Y/ L. O# X! K
    12
    ; l6 R' j+ a' K3 c) Q0    Shanghai HP District Mid Fangbin Road No. 249
    2 Z4 c0 _0 c5 V. k& l0 A* {/ \5 c1           Shanghai BS District Mishan Road No. 5% k2 A, ~/ h$ i9 Q5 ]
    2           Beijing CP District Beinong Road No. 2$ h8 C. J: K- d% W( ]# v/ h5 o5 j
    dtype: object; Y8 s6 ?7 D0 Q( r9 K4 M
    11 ]" ^( S4 c5 N: w. S
    2' R" c* B' }: D
    3
    8 O5 g- G" r% I3 T: c' S' n5 Z45 Z/ z5 q0 s  P7 a: {- C
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
    7 L0 a: ~' U! k) s5 i! A$ G8 n( F% r: o: ?# B
    8.3.5 提取5 Z  h8 g8 ?3 S
    str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
    & `. J2 b! ?- g( Vs.str.split('[市区路]')
    4 g1 z3 U9 H7 l' NOut[43]:
    : L) I) e5 w. ~3 a5 d' u0    [上海, 黄浦, 方浜中, 249号]
    & u" U, Z  K9 V% m& x1       [上海, 宝山, 密山, 5号]
    / V3 X$ c- H2 B1 \dtype: object
    / V' Q8 o- V% ~' {
    # I5 c+ j1 c/ m. D0 S, spat = '(\w+市)(\w+区)(\w+路)(\d+号)'& S1 f0 Z% y1 [( }: W
    s.str.extract(pat)  q( \/ I' W7 l/ ~# `
    Out[78]:
    5 [8 s# h- D- q    0    1     2     3' q$ P9 x7 W" s8 ?0 k
    0  上海市  黄浦区  方浜中路  249号
    $ j6 O! w* A) u( U& ~2 Y' X1  上海市  宝山区   密山路    5号$ D" i. F# ~: Y+ b5 g7 b
    2  北京市  昌平区   北农路    2号8 N: a# {* ]/ r$ a0 E9 \' c3 z
    1
    ! R. x/ ^  s* [( t1 v+ _28 c8 v4 ^$ t8 A% W4 e' J
    3
    , o# I6 A' _$ ?$ g4  c+ F. u) t2 `+ e" @0 z% b
    5" G" M/ ~2 [; P! C, d
    6
    * L' t% ]1 F- Y6 R3 u7
    + j4 j! C& D: ?7 U3 Y8# ]% r6 C: e' e8 w1 w) }, ?
    9
    + e" _- O$ O& @$ _101 b! m# o2 h" z8 ?4 A' L
    11( t8 c+ R* n+ r; i* S0 R$ \2 J: G2 y/ K
    12: V& [: K9 i9 E! R% p1 C
    131 W; K! A% q5 q# ]6 c+ t
    通过子组的命名,可以直接对新生成DataFrame的列命名:! a! H/ I  y6 w2 k

    + o# X$ ^' V/ v" u- C. tpat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'! c6 d4 R( q% L( x0 `# }( B
    s.str.extract(pat)! M) w, n' X" Q6 }1 J
    Out[79]: / r/ A6 s" a4 x! E' E
        市名   区名    路名    编号
    & I6 j0 X3 Z! ~0  上海市  黄浦区  方浜中路  249号: B' Y( ~* A9 j& v$ J
    1  上海市  宝山区   密山路    5号
    ; F1 `# N( X: r. @# M: T6 Y2  北京市  昌平区   北农路    2号) N2 b, j% ~5 P) M- G
    1
    . ]+ ]  @7 t/ l, M4 v: \9 J2
    ! F& D5 P# x7 y5 q. ~) O' N" z3
    $ R+ e" w2 ?& g2 L4: A: r; I: \5 ^; j! p7 j; \7 F% S& q
    5
    8 V8 p* a- F* ?% [8 ^3 l' B6
    4 @* q6 s' y7 U5 @7
    : i' `+ O  a6 M% Tstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
    4 o) T) T% Q0 l( U6 Ls = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])7 W3 }, k! `. w, I0 d& v2 T: O
    pat = '[A|B](\d+)[T|S](\d+)'% S: D! [) e8 W3 h2 @
    s.str.extractall(pat)4 u. H# V1 i, A# @- |+ r" J% h/ @
    Out[83]:* e+ Y. f# |( g% V, Z1 M& _
           0   1
    0 M! ~* T! ]2 W  C& X) O! K     match           g8 P! Z5 ~- \) n) S1 [
    my_A 0      135  154 y" Q1 ?  v" U5 F0 n' S
         1       26   57 Y& T! S4 Z. m+ t
    my_B 0      674   28 O4 `& x% D8 W3 d5 I: W
         1       25   6
    ! \1 z, H5 g5 \# F( L1! @1 K: S( L1 @1 w) l5 R) L% Y
    2
    8 Z0 ?+ ~$ l" W& K( G3 H3 ?: ~- p3( h6 P) `! F% @* h$ B+ [
    4
    $ c, M7 [' w  n8 v6 \6 P5
    ! k, Y8 e; r  d% l+ P9 ~6
    5 ]1 `% e, Z2 M) p5 u5 E7
    ) S, |$ s. ?& e6 M# ]8- K% Z( w7 E) F; V( w- T2 J, _
    9# K7 j- z+ R  @7 N2 k
    10
    5 h* U: X% n5 }pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'7 S9 c: A2 P% ?& y
    s.str.extractall(pat_with_name)
    % V/ D1 J7 ^9 ~; \. YOut[84]:
    8 H" `8 y: s0 U1 \/ j           name1 name20 D2 P) {# q( M- X6 W$ }
         match            " t( v: g3 F8 p$ ^; {. Y3 j
    my_A 0       135    15
    5 y+ p6 l' x- z# G     1        26     55 t5 N$ p  h, s  y- o! w9 b8 m2 ]
    my_B 0       674     29 d! o- Z4 k8 b. M/ b* ^. A6 i7 I
         1        25     61 F3 V, h" R# m2 O" {" f) z1 c6 K
    1
    " A' E6 \: {  y/ i% |) p; K4 }* x29 [' N7 k! \! f  p
    32 c2 r7 F( Z  ^) x. b
    4! @' @  n# R" ~1 b
    5( f* \6 ]( n3 x- _3 s
    6
    ! W' x7 E1 D) N3 n% C* L6 A1 R71 v( r" l8 L! z( C* H1 p5 y7 a
    8
    ( c6 I2 z; A- v97 V. G+ E; }9 F1 [
    str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。, {: _' g% [. y
    s.str.findall(pat)
    , f& @9 r/ a4 r. @1$ k0 R) b6 b6 }
    my_A    [(135, 15), (26, 5)]
    4 t5 q( M4 a# ~+ hmy_B     [(674, 2), (25, 6)]
    0 R& v. X$ S! a, l! v* ^" cdtype: object
    " _  |. U& ^8 }0 N9 u; a1
    # I# ~( F' `7 K3 R- D24 J0 t5 ?6 M1 r6 [' z
    3
    1 N  B5 W' m8 r8.4、常用字符串函数
    & j0 [2 B5 T2 S3 N# p4 }/ z" K' ?  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
      \, ]. ~  y3 @: K
    2 t! t- H' f9 }  [7 `8.4.1 字母型函数2 s3 w% u- B! ^+ \0 ^! D
      upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
    & G2 W0 p& f. A2 H; f
      y& g( [6 H' H- ~6 _9 n' q7 @3 Ts = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    1 c3 y% m# w. ]0 i$ B% ?+ ?- r
    8 O  C( ~0 E2 a1 v* s$ as.str.upper()" {: g6 p" u6 ~7 Z& V4 ]$ \! [1 T0 ~
    Out[87]: * \: }" P5 d2 ~. i9 c- t" f3 K. ]
    0                 LOWER7 n$ `0 m8 v' U! x
    1              CAPITALS2 J- ?& M+ K+ @6 T  F0 }- @
    2    THIS IS A SENTENCE% Q$ y% t) F3 J& M' M  e6 F; F
    3              SWAPCASE0 }+ k% L  b! e* x( g
    dtype: object
    $ I6 `. F" q1 O9 h
    & P$ J; q# j  q# b; B# c" Ks.str.lower(): _9 V( W) ]% c2 @, E$ z/ l
    Out[88]:
    + C8 l& u1 I- G; t* m, e, u0                 lower
    - f0 b. S" U. I" l: D6 O1              capitals
    ) ?3 V: x2 _- a* _2    this is a sentence
    2 R& l* z7 B; p- O+ x+ t- T' a3              swapcase
    , f0 v6 _8 E% `) fdtype: object
    7 a; M" _3 q3 `3 r* n# @1 I" B2 [0 |! t% _5 t2 G4 c
    s.str.title()  # 首字母大写
      V- h2 u7 G/ o& M* b6 m4 @: V9 x% COut[89]:
    + U* _9 _& M. e1 T) `$ {2 D0                 Lower7 b4 @; H" d: d
    1              Capitals
    5 R3 X  j  N0 w2    This Is A Sentence
    / |/ z  E' ~9 M9 s8 u% z: l. |3              Swapcase
    " P: E9 A5 W, }) u4 ^4 k! J+ z! ~dtype: object
    4 S- _/ Z. W( |- F; G9 u( Y
    2 v3 I5 h1 P5 D6 o. K+ ?* v$ b8 b. xs.str.capitalize()  # 句首大写
    8 ~6 L- {1 W, J- v6 \Out[90]:
    & h+ ~) }" [& i3 `2 K# t' h* V/ Y0                 Lower
    2 O, I. j, r9 ?; s1              Capitals
    ' J9 D7 m+ }# |- l! w5 z2    This is a sentence
    + p3 k/ L* }! A$ n4 o6 E% N/ i3              Swapcase
    ! U% r7 `/ W% J% \5 s9 Vdtype: object
    7 A5 V2 b: j- U% h: P( j7 A' Y7 U& O9 r( U# l; Q& E
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
    ) B6 s2 D0 m: l* P8 Q" [Out[91]: " b2 o( X. T. _/ w2 f& R
    0                 LOWER
      z- U5 ~5 h- H) i$ O; P2 D1              capitals* u& U( S/ `4 l) {$ ~
    2    THIS IS A SENTENCE
    9 T/ t; n( R3 J) C: e3              sWaPcAsE
    $ P. |0 G3 C6 b& U& r4 r  Ndtype: object% `2 z! V9 `9 e% @% z
    ) ~6 F1 C1 g+ \9 d3 t
    s.str.casefold()  # 去除字符串中所有大小写区别
    4 m* U- \' A& W9 k9 g7 G( s$ R* {" {/ o6 D2 z7 p8 m% j( A: V
    0                 lower
    9 r6 n' x% p, S- F& s% q1              capitals3 ~: \& b7 Q, A- \4 j0 U0 M
    2    this is a sentence1 W, ]0 E* m1 d- N! W
    3              swapcase9 K7 ]3 ~, M- E/ ~9 Z3 L
    2 X5 \! Y" k2 z# t! z
    1
    5 {4 `5 \/ a6 J) h; L& C9 M% Y1 E% U7 i2
    ) A8 D+ }9 d! o" c* ]; g4 v) R3
    & i1 o1 t' n! o+ Q( B4/ G+ V& v" i- U0 n
    5
    9 ~$ v0 o3 U1 q) t8 W64 t, y4 i  B: g% {8 F6 Q  v
    76 ~2 f; I9 g  ^) r
    87 x- M, }& x2 D3 v+ @6 l
    9- D  p/ `, Q9 g
    10$ l. w  N2 `0 d7 e/ y3 T
    112 C- z: X$ q& R* ?; D6 L2 T# E8 I
    12: Y+ k: I* j2 ]4 C" M
    13
    & }9 Q3 S9 N6 n7 p$ h+ a14- m$ O& n; u6 V$ ]6 u) z
    15
    4 t  v3 w$ d) g. v16& k; Q4 a1 E. f0 Z+ T
    17" s# L! D" i) N6 ?6 L9 |0 _9 a
    185 ^, C5 E) y, [: e
    19
    5 F) S  W$ F2 I9 U20
    , {# Q( `1 c5 G' f/ N1 L6 z+ v3 t4 `21) V0 P7 o# W3 W' S# f; @
    22
    ' A4 V7 u( v1 ^# n! S% r$ n23
    2 E2 c, B) u; s5 p' r) K24
    5 ]5 m* g+ Q% S! ^( Z25" k  o2 M$ U- Q7 Q6 K. k% u
    268 K5 @, p7 }  o1 I) A! `7 w" H
    27
    + t* Z! e  r1 u4 d: z' M, T28. L3 y# S$ k  L
    29
    ) t& g$ l' `) m- c9 q305 c9 o# Y- x) Z
    31- m# m- c! l7 N: c5 U8 U1 B! w. T
    329 B7 p3 j  C+ l/ E- V) M$ b
    33/ y- g5 C5 |% o3 @
    34
    $ M% B$ ^3 d" e2 {9 [, I35
    $ Q7 x# B( Z8 g7 ~36
    ' z5 h9 q0 w% }( P+ }9 ]373 E1 k. O1 a/ _& L3 {' _
    38
    8 Q4 n+ c5 a# y, c- C39+ t/ r+ p# F% c4 }
    40
    ) V8 y' V) N: M5 m5 o: A; U! X410 j0 ~2 q. b8 M7 o
    425 c8 I4 h9 t7 k! F$ W  ]4 K
    43
    3 w/ s- k, I0 R3 g44
    - {. ?3 l+ i! A1 a45
    * X  v: X7 h2 t  R5 P5 F46
    $ p- H8 J+ t- T% V6 B# G477 X3 e  @5 ?8 q' q" E+ f: l3 k9 q
    48- L" U6 Q) x& b: i( ~, ]+ n
    8.4.2 数值型函数7 j+ w* N( ^+ i* O
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:! k, A3 g$ K4 Y0 I

    $ l5 E# m2 {( {/ [. |errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:4 e) U$ f# c% D6 G: [5 s
    raise:直接报错,默认选项
      `& C( b7 v2 D2 j0 u# Gcoerce:设为缺失值4 g" X9 t+ N! {, I' j* S
    ignore:保持原来的字符串。9 _( e' f0 a) U8 E9 _  O' z
    downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
    ( G' r( m7 C' g  i3 qs = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])' P, S8 j4 B2 _/ }9 `
    0 w! x1 B5 s) Y
    pd.to_numeric(s, errors='ignore')
    # j) B0 e1 e1 N7 nOut[93]: 3 y8 P. T8 G/ }
    0       1- R6 _0 X. w  W
    1     2.2/ y1 `) x2 \& R. ?  P- d1 _
    2      2e
    . S7 q8 p* a" r7 R+ T2 _3      ??
    . C7 l4 t1 f" u# c4    -2.1& w: o% j" D7 ?
    5       02 @; r. E( F/ v/ Z
    dtype: object  s# G* x& P1 s/ o' m; z7 ]+ Y5 D
    * k  q+ x6 l/ l: p# P
    pd.to_numeric(s, errors='coerce')
    & H1 R2 k; s3 [- N4 G& E/ ROut[94]: & S2 D! c# O' p2 n) z
    0    1.0
    5 L. q+ N/ L1 w1 [, Z1    2.25 d- i. J3 K& t  F8 X# P+ ]5 n
    2    NaN
    2 Z3 Z, r5 W% v4 m3 O9 {% Z3    NaN
    " Y/ K. }# n+ `& o4   -2.11 e( j& X6 Y& w$ t
    5    0.0" N. W# z# f5 j
    dtype: float64* d2 n3 N6 _) x0 s0 ^

    1 `+ B2 h! a+ v; {" a1
    " S1 i3 n% K# @% b) ]6 t2
      M! T3 F$ A3 C; T39 x8 a* i* ]* Q0 n
    4
    ' S2 G  j$ n. G+ y1 ]' z( C4 K. K5
    + ~) ^! r+ f: s) m: w: l  K8 }6! s- r% q5 j/ W7 @* P" E* c. w3 \# j
    7
    & t. }) F( ]: G9 |3 f8
    : I' t/ _. a( I9 A+ Q9
    ) C: }5 t! b; w+ c102 I! c+ f8 N3 w
    11
    ; w, h9 |0 m# N' ], {+ |# d, y12
    ) @" v: C; [- F# s! a( P13
    $ N/ X# V5 |2 g( z( V14# L% d6 d0 v" y7 i5 A
    15
    . V' r( i3 v! m- G0 c5 P- h16
    " A/ V# S/ _9 ~" t) @) `17
    1 ]( ]. c5 `3 m; x3 `! A3 r2 B18
    % G! D4 N; e& g8 \! y19" c( q; _( o/ p
    204 m  h- _, F- e( b% k4 P$ P
    21* A' L9 c* S4 [, u
      在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
    ! F, U+ r' W5 h7 L0 }4 s. h% u% k7 b5 Q, r) _
    s[pd.to_numeric(s, errors='coerce').isna()]
    ! j- v4 r& `/ v4 K; g7 N1 D0 R; rOut[95]: , g( q& ]7 m% Y& l, K' j/ ?
    2    2e7 N7 W; F. o& j
    3    ??" w6 T$ |9 q3 [0 P/ m
    dtype: object
    6 Q/ N1 H4 m4 c: }: L9 E1& E5 I7 t6 _+ `
    2, Z% Y+ m5 L; w$ N) P3 _
    3
    " N+ Y/ H2 [0 |. \! Z# x45 L% b# q% R8 r: r! y- B7 F! E
    5/ T- `. `0 {& p% b3 N* l! Q8 V
    8.4.3 统计型函数) {  F- x+ C8 s
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:
    1 U( m7 R0 y# I
      D2 C7 g, K  Q+ R# I2 Bs = pd.Series(['cat rat fat at', 'get feed sheet heat'])
    + q) d4 c) a3 R0 Q+ L) h
    * D( d1 g- `) w+ @/ p4 as.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次, f2 s' l* H! w0 _+ h' {- U" s+ L
    Out[97]: 1 W( i/ M# D# {) f1 O/ A
    0    2
    0 v9 {5 Y* u" n1    2! m8 N) B( b5 z# Q7 p
    dtype: int64
    4 o4 s9 }) P. T
    5 \/ ^2 C* Q+ zs.str.len()
    ( S( X# D- f5 e1 n$ e# @4 h1 q( |Out[98]:
    " T9 i; m- b& g# ?3 O0    14
    $ `' m' Q6 p" H& @4 E7 d# A, W1    19
    : u3 f4 N2 {' q- v4 N. Kdtype: int64
    4 x( E  ~# D5 J' t* U$ _& O17 c% h; @/ b/ i0 O
    25 w+ U2 p& O1 a( s
    3
    4 U9 f) A3 G9 [% T# x4
    / f. O' x7 L! f+ J1 i5 ^1 t5 i5
    0 F) ?2 p& V$ f2 K6
    " Z" |. \' N$ }, M" v1 v7
    # m3 b7 B; V1 ^1 R) o87 E8 }) q2 x9 R5 U0 V
    9
    ' |& t; j5 z% e4 I, [8 P1 x108 M5 G4 U. w) f. C6 D4 J& O
    11
    / I: [) z* @3 u' f0 Z3 c12
    ) ~- M$ i$ f. G" w, r131 f" H- M( w/ I
    8.4.4 格式型函数
    . R, d# a1 P4 q. a  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。' t- p! W, K# S
    9 _# j7 x6 A" f: ^: i$ C* E
    my_index = pd.Index([' col1', 'col2 ', ' col3 '])
    ( k2 J# d8 L4 h2 w0 x/ p
    0 K% r9 [7 e' O, `6 X5 o( kmy_index.str.strip().str.len()
    " Y$ k; k. M/ Z5 X7 ?8 `6 aOut[100]: Int64Index([4, 4, 4], dtype='int64')
    & y: I' A, @$ C0 |! |  j; k
    9 L7 l  Q0 `" S- O! bmy_index.str.rstrip().str.len()" x, @' i/ S: H9 m
    Out[101]: Int64Index([5, 4, 5], dtype='int64')
    % e$ w: q; ?( B0 A) A. t) P: `+ d! R
    my_index.str.lstrip().str.len()
    0 p6 f2 q# @9 P: H: u* TOut[102]: Int64Index([4, 5, 5], dtype='int64')! r3 h# b3 L' N7 k
    1
    , L) C  d  [' M( W2/ W' j( }; V) b9 T* M
    3
    $ B7 U  M+ F# \4 W4# H* P) C; Z1 c4 _
    5
    1 h& a% n# s# s& u" T" T! [. p65 \- i  c2 r5 C
    7
    - a9 u" _) H! ^9 o; D3 n) ^% r* U8
    4 O! p: Z( o8 ?: Y. k' k5 F# `96 `8 @. S* T& f1 j: [1 w7 s5 R
    10
    / A7 ^1 Y; ^2 r( x4 y7 w) {8 B9 Z  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:" h* Z5 @: }, L, ]

    $ x4 i' Q. _0 X1 ?$ c% qs = pd.Series(['a','b','c'])
    + X; ]' y1 X% r( f# X( ~- |4 i' r- R" T) F8 d; C
    s.str.pad(5,'left','*')1 m2 |5 e8 |0 m0 q4 W7 `& ?/ p
    Out[104]: ) B5 {: ?$ `2 |5 c" [& i
    0    ****a$ E7 X0 s. }$ a9 b! x/ o) S
    1    ****b: p8 V% w8 T0 r9 x
    2    ****c
    1 u7 }! ~9 k( B5 Q4 Q9 S1 m" Sdtype: object
    4 N# p: p9 _. H1 c5 P4 l4 q- N; I& ]6 U, L7 i( E+ D2 s2 I
    s.str.pad(5,'right','*')9 \9 @7 d2 }0 e" N
    Out[105]: % d3 F; W1 x" b
    0    a****
    2 `  h1 H8 G8 b2 U5 f( q  B9 \1    b****; q5 t* s- |6 m: n5 Q2 Q
    2    c****
    8 D1 z1 h: X- C# `dtype: object
    - k7 S" l% {1 `) }' Q1 h- v* Y9 \( {$ k' G  F" i8 Y: I
    s.str.pad(5,'both','*')
    ! a) D4 i7 k& ?8 oOut[106]: / t1 i! X7 Q1 P2 H1 S4 P
    0    **a**
    4 e: B1 e  D# o& y4 {7 t1    **b**2 ]6 u2 q) b9 O7 ?0 K: E
    2    **c**5 z+ s% }: N' s, \& d& ~
    dtype: object& @% [0 i( y; m5 a9 B

    % ?, q8 y! t6 X! |  }# l1
    8 A3 r8 r  }8 a1 a2
    * [% }- q! G2 p, \! u7 U4 V3 e2 F34 U2 j4 P& m5 Y4 e
    4& p; c+ H4 m* m9 H
    5
    8 c6 F2 Q* Q) [; R66 {& e" P  c+ H- g$ \) Z) Z+ Z
    7
    9 D% G5 u+ B+ i, S- T" M( [' z8
    # k8 o2 ^/ P; o  S. l9" F2 ^$ g+ q- U* y8 a2 Q+ Q
    100 S% Z3 R9 Z0 y- z& [- }
    11' N9 P* T4 ~5 J) _
    12
    ) F5 _" U5 g) e- j+ s, [; a! G13
    # h6 t- M: [$ S14/ n# F$ Z, h6 c$ |( s) \/ }
    15
    * I9 j7 Y3 Q! B) ]3 K16$ V) W! m; c" ~# W
    17
    + t* B3 G# I2 t  O3 M! b18
    $ e% G/ L1 h; ]19
    ' f6 L* \# `" C7 j. N- i/ u20
    * |4 i! v  M9 w) r7 g21
    % N" F; s4 @" c- G22
    / `5 U  v( g; _7 |  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:4 |5 o( c  W+ T6 j4 J( r  d0 H8 U" \; F

    8 C) l# W4 S1 I4 F/ ss.str.rjust(5, '*')7 ^4 b" g3 ?, K: e3 Z% P; X0 K$ A
    Out[107]: ( K. _* V2 {) t& R" d
    0    ****a) W+ s0 b/ \0 z3 X$ H0 y
    1    ****b
    $ K9 G6 f$ ?* c6 X- Y0 C- ?5 `0 O5 A2    ****c# a; J( \# \" S/ \
    dtype: object1 J" l8 U& g# H+ i# t* M
    , t& t4 H4 L( E4 Q; {+ a1 h2 m8 F
    s.str.ljust(5, '*')* s3 h$ @  A5 c) a. N1 c4 U
    Out[108]: 9 Z% j+ p' ?+ ]# l% y+ m
    0    a****/ Z1 ~( u6 f7 |- L' w: i) @) g& ^
    1    b****# X% h1 \, X9 a" w/ E5 V: t
    2    c****
    $ {+ R. _3 d& e# T& z; Z- [dtype: object
    . h- ?6 B/ \( B. p$ x- X  E  f  t: C- F6 O6 O7 a7 J. ~' B
    s.str.center(5, '*')
    ( ~& r9 z! F. ]Out[109]:
    % n- g3 W' U3 W1 l- M0    **a**
      o; O+ ^6 C0 p) }9 H1    **b**
    7 O6 j5 o' n1 l& @2 [: ], v2    **c**$ \. W- E9 ?# s8 O$ a  q  M
    dtype: object
    8 c# P$ `  L* i  p  S$ D0 a6 }" j0 P: h, w
    15 f0 G  `7 r/ N! G( U5 r; m
    22 ^4 P/ [9 b! q) M
    34 F- n8 }6 u; K, P0 f: t
    4* p" J7 \% F0 N4 ?& e% H
    50 q$ `$ l4 E0 e8 X5 `
    68 t2 r, M; m+ q! J
    73 J' N+ b& N! u4 E
    8* p/ @2 L  o4 ^3 x- F
    9
    + [$ e) i8 d5 h6 b10% |  z* K8 s  |+ z3 m2 S2 E
    11
    ' H2 J" s  f( J8 e124 n6 x! n. n6 n8 Z7 v, |2 X1 ^: i
    135 u" E, D% W. \. K) b
    14! p' c! I" u5 ^6 k6 b
    15$ z3 s' Q6 A% B, L( G- x6 k
    16
    0 Q5 k5 X2 V2 u2 o17) U7 x% J! C) m; y: r* A& K
    18
    ( Q5 t1 O* f2 D1 s* O19
    ; d! k+ ?# c: d' q  B8 x* t/ h202 [1 Y7 P, X) j! o
      在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
    5 s# r: @' z: @4 R3 n/ U; i7 P% y
    & ]3 a* w, ^9 R+ A2 Ms = pd.Series([7, 155, 303000]).astype('string')
    * J# ^# M+ `4 ?' D4 v4 P4 s: ^: X/ ~) o, `0 X
    s.str.pad(6,'left','0')
    + H1 R7 B& d$ U! h- QOut[111]: 2 B8 Z. f/ h& i, F- L
    0    000007
    % `5 `1 g% R- T- e( U+ i) o1    000155
    1 i2 M, Q, }4 _" c2    3030004 {4 m. x& `8 R6 s9 q
    dtype: string
    " E2 w" T0 }6 [: s7 }
    . i6 L' i- m! B: Is.str.rjust(6,'0')/ V" P7 D/ Q& x+ u  _
    Out[112]: + ]* v, s8 U) E0 o. P) b
    0    000007+ q, }, Q/ L: z& U% K8 g# T
    1    000155
    0 a9 p2 _- L/ t+ V$ I, N2    303000
    % Z: k$ Z& z& E% h2 h: idtype: string  ?0 [9 W$ U2 _3 o3 v, y- h

    2 }, I& U: m0 J+ U% w4 q- a' hs.str.zfill(6)/ v  R- Q. k' ~% P! }7 I8 H
    Out[113]:
    7 `& f) y9 Q' \* G5 E0    000007
    2 F- P9 m' |% g4 Q3 d  x% z1    000155- p& n# u7 b# v& d# O
    2    303000
    9 R0 i& |. X' y: _, Q. Vdtype: string7 w6 X9 c/ C4 A( |7 x/ Q! U
    4 \5 h: W! Q6 d! e3 `  V
    1, k9 k/ ?' f! f9 H7 j7 v
    2! O; |' z% \. z3 `8 T
    3% t. m2 w; n: D, g! t
    4
    " E6 N! ?. D* Q, s4 O5
    + A0 c/ O8 h- G  [% ?% ~6
    # z% j3 f5 Q4 x4 W1 z) [8 m  ]7; B6 t' v; G0 C$ b( F
    84 K1 \; M  A7 }, Q( v# v
    9
    7 r6 @. u$ m3 {( [* N  Q1 }. Y10
    6 [8 G( g9 k# b  w11
    * \% V3 P' p7 |+ |- [  X5 B12
    4 p. q" y+ H, F1 l2 ~13( t6 P/ O0 [( E$ Y7 M: B4 W
    149 ^) G' g6 W2 f+ q0 m# K
    15
    0 x" v* `% b% l5 x16
    0 u; a. n7 X3 N$ M. t2 ?17
    % P$ k' V& M& ~  r) }3 m4 q18% E/ g$ ]5 _$ N
    19; N% Z/ I9 x6 w! O/ |! v
    20
    9 F7 c& G( A+ y2 G21( X5 ^7 W# y3 g2 W; ?) Z- C
    22
    , R4 _. a' n, u& Q8.5 练习
    % q. A, T- u$ H; j2 e% cEx1:房屋信息数据集
    8 z% G# {* r' E* f9 r5 K7 C现有一份房屋信息数据集如下:& T% p4 {1 B/ k& d7 q# Q

    0 I0 w0 O2 M9 u; E% t$ B3 S/ qdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
    4 d% }( G( X* H! }: O( G" ~df.head(3)
      {7 n4 q8 b) R* G0 G7 KOut[115]: , ~% h8 E$ z9 U* c: |
          floor    year    area price
    & [  }9 S. t& x' Q0   高层(共6层)  1986年建  58.23㎡  155万  x+ [5 k: y5 U5 G
    1  中层(共20层)  2020年建     88㎡  155万
    8 J: h) D  ^% x1 z. z3 ^3 a2  低层(共28层)  2010年建  89.33㎡  365万
    6 v6 B2 g% J" m4 Q& ?1" G; v; }, [4 x( [8 ]; n! m1 K
    2: X% b6 k/ S" L% }2 \8 g( T
    34 n3 ?& Q- Z6 Y. g: y& J
    4
    . C6 j) R. [4 m5 T+ A2 u57 T! E0 n8 Q0 j1 ?1 x$ t$ k0 d
    6* j' ~/ }' A% |8 F, b/ [( O
    7+ u' K8 C  z* Q- W
    将year列改为整数年份存储。
    : K7 v7 t, m8 t1 i: P( D将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    3 ]% \; w; J4 z: g+ T计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
    5 o+ _( j& B+ r( p将year列改为整数年份存储。$ [# \' a9 H4 y- [, V! d0 i6 ]
    """
      Y7 F& ]  {+ J% E+ T  v& ]* E整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。; W0 Z* B0 u% T( O
    注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,! [: q5 @2 m2 C& T- w4 T( F
    转成int后,序列还有缺失值所以,还是变成了object。8 Z. [# W) o. n" H  j; u! s/ z
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
    3 [! U  w8 _8 w* v. C% B# k"""
    ) d: X* w, v7 \& v( x7 ndf = df.convert_dtypes()2 I9 x& r4 f1 a8 ?9 O: ?1 T% P
    df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
    . V3 x$ V- T; N, F- V# S  sdf.loc[df.year.notna()]['year'].head()
    " C/ W. V/ B4 d# a
    & P! Y8 ^' ^( v' i, \# U0        1986
    ' o3 D' b. U4 ~* ~1 F; W$ ~# U1        20209 C3 o. |" {% i" r$ ?
    2        2010* Q7 Z' S" l! z  N; S# f
    3        2014
    2 D7 a2 V( b; G4        2015
    / m- G! I# k; Q# W) Q6 eName: year, Length: 12850, dtype: Int64  d5 d" @, j2 @' }; _; }
    2 |# `! @3 @( M  D4 S. b4 E
    1! F6 \& c6 x* X* Z5 t( l/ }( W7 r
    2
    1 |5 N/ Y) j% s2 j8 X+ J3
    8 j2 v; d" \2 o! }  ]2 h: a  ~& {4  A) G* w) I8 [0 [. e
    5
    ' d0 D8 }  `0 n# Q. l! p( C60 g5 g$ Q0 [3 V& j
    7
    ( \% G# U. S( G, w% D  h81 X, G/ p1 z3 a/ ~) h
    9
    ! V  F: M' a( I- Y10! s3 a1 G5 {6 {- a/ d; Z; p
    11
    # L- o5 R) S3 C; T0 O- W, p: r12* l6 Y0 b1 b1 N( I
    13
    8 i' x* L. Q& G  X) K# M: @! K14
    : X- F  d4 g& J: J, B9 n4 n153 g8 @; j1 _" w& B# k! q1 I
    16
    7 l- v, a- ^4 y, _# T. X参考答案:) p' N, n3 Y& A9 M3 @

    ) ]/ A: M+ i/ M  F  b$ R( n4 ]不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么( ~4 c5 q/ }; e' X3 |

    8 f( h/ [$ Z& a- L( I# G* U& G6 Kdf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') ) C" ?; A, a9 ^& Q  z
    df.loc[df.year.notna()]['year']* U0 g* i( U, p
    1
    + C" C/ @9 A' z! `/ y2
    $ Q% Q- Y' @. M) [5 q+ f# F. V将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。3 S4 N* D8 V; I, u  m) g% K3 _4 K) }
    pat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)': ~+ s' ]" O! `8 N: N. v
    df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次2 |/ T7 O3 G- x) ]/ x* `7 m" j/ u
    df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型. u* a9 x. b" R) [) ~" o
    df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              . N# o2 F: n6 D9 f
    df=df[['Level','Highest','year','area','price']]
    8 A; R7 h9 r3 R/ ~df.head()/ Q2 [8 {; ]4 t" `8 R
    4 a! `# G" x6 s) |3 n
       Level  Highest        year        area        price" t+ O) }' A- F6 }' K/ u! A
    0        高层                6                1986        58.23㎡        155万6 N" }+ Q  m6 {% n5 ~" H: l
    1        中层                20                2020        88㎡        155万5 X& K. R# X6 w. e6 F' E3 m( P
    2        低层                28                2010        89.33㎡        365万
    6 L3 o- J# {3 S1 v9 U3        低层                20                2014        82㎡        308万, `. p; n- X" `) E
    4        高层                1                2015        98㎡        117万
    2 r( M6 W' J0 @$ r) W) f/ I1
    8 p; b' E! P0 c* U% x' L2) y& `" }. K4 p. S4 i4 I: K& q, |" @
    3- b4 _; `1 X; Z% H
    4
    ( R4 K, W% ]3 {% y5
    ; h! M! L$ q3 ~6# B+ `2 c7 m% u$ e
    75 s; m6 {: r+ a& i: F$ h$ S$ x
    8
    # _9 e# U6 }6 \, T- }# Z9
    % s& E( Z2 k) q7 U103 a7 {0 O+ d: H- J& l
    11  m' L! }$ ?' y8 x8 f
    12
    ! W2 f4 {5 p8 z9 M0 C" n4 U13: b5 m# `8 w9 e$ l0 t4 o) f+ t0 v0 i
    # 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
    0 b! O$ `2 J# Y0 _; D. f3 jpat = '(\w层)(共(\d+)层)'* K# d* b3 z: ?' H+ l- h6 h1 Z
    new_cols = df.floor.str.extract(pat).rename(& o: l2 K. n) z  J' u7 a
                        columns={0:'Level', 1:'Highest'})
    , a8 ]* N" s% Z4 P3 Q. x) u, q' C, q' X: g* ^& u+ _9 G* o3 T
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)1 U# A* v0 Z  j! m2 _' v' _
    df.head(3)
    + E/ X1 I* D4 e+ Y8 j0 M) K1 [& G; o, r) M, m( _$ B
    Out[163]: / p2 i: l" o, h2 m) ]% P
       year    area price    Level Highest
    6 J( f7 h+ h0 ?% W) D3 `0  1986  58.23㎡  155万    高层       6! B0 i& G& Q- p! u! {  p4 e
    1  2020     88㎡  155万    中层      20/ c: M1 K! z8 c% H6 ^
    2  2010  89.33㎡  365万    低层      28
      A2 t2 L: p) u  E8 V- G' j1" |' E* m5 |) d- e( [. g
    2( t9 A4 y. H+ P, W3 V
    3
    5 }0 j5 G: |) K4, j0 ^# C  c: \4 Z# e$ B
    5
    ) k1 _# V$ d' K5 z9 x* a6
    : P  p  c9 r0 P: D: x) g7
    ! P+ Z# k. l0 v3 V1 y, H8  [( ?3 Z7 h' L" R3 v0 [
    96 h$ {  W3 b" ]! B+ }" _
    10- m0 A% Y2 ^, G% A8 S- m
    11
    # ?+ F" g! `; V0 C4 I12$ w9 F  T. q7 @: J. H! x3 G# M# ]
    13: D& ^$ O( Z( U5 Z: F) M
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    * n, h0 b4 U. ^  Z"""# W2 m+ D. p3 {7 d
    str.findall返回的结果都是列表,只能用apply取值去掉列表形式3 ~& d+ K! Q7 c; G* t3 N
    参考答案用pd.to_numeric(df.area.str[:-1])更简洁7 O- g& v' h* i) v  H% R8 y
    由于area和price都没有缺失值,所以可以直接转类型$ S3 I/ c6 O' C& ^0 C
    """! ^0 ~6 Z5 B( z* ?
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))0 v9 H0 f5 w! h+ w+ j- C4 `7 `# p5 ]
    df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')5 b3 J$ O7 o1 Z; I2 S
    df.eval('avg_price=10000*new_price/new_area',inplace=True)
    % w  F+ F& [, F3 Z" K- y7 ^! z+ c) F# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
    $ L. @; U# {% p7 L5 \: g, N# 最后数字+元/平米写法更简单
    ) K  z2 y" s, v0 S9 v! sdf['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米') m- o0 O7 n1 g9 N
    del df['new_area'],df['new_price']
    " H% B+ u, j" J" p# `df.head()
    * z3 L0 }' Q+ N4 G- L
    , O0 Q* v; w1 g/ B   Level        Highest        year        area        price        avg_price
    / j3 t8 ?5 F: p+ t0        高层                        6        1986        58.23㎡        155万        26618元/平米
    ! V* |. U4 j' l( v1        中层                        20        2020        88㎡        155万        17613元/平米) h) _) A9 _" `/ d8 p! W
    2        低层                        28        2010        89.33㎡        365万        40859元/平米
    8 O# w  K. v" ^7 D3        低层                        20        2014        82㎡        308万        37560元/平米* k! e: t4 V. R2 Z/ _- {
    4        高层                        1        2015        98㎡        117万        11938元/平米7 w3 C9 [# p- `" {( E: ^
    / e: L+ \! m, V% V3 }' T+ z( l
    16 ~0 ?" \7 M9 M( R1 j# K1 U: d
    2
    . H! z! u  {2 ~4 Y1 J3+ j3 ^% {! P- ~/ [5 d2 i3 o
    4
    6 j1 ?& M* ?7 w, d$ A( \) u/ j5
    & b4 X/ C6 K. e1 \" [6 d$ h6
    : q* M4 h, k" c7 G( l; Q" E1 i7
    $ p1 |' t- f5 e! y2 ~$ t, A7 z84 k$ d+ I: Z0 S  q
    9
    & K" h5 y6 }8 Z7 @- p& [( V! H9 O: ~10
    - s% ?  W4 {) J5 f$ K# v  I% ]11
    9 s; E; S( f4 p& S12
    2 k8 a* A' O& k5 E  p  S/ @13" y. _# h/ Z% ^, N: D/ {- Z7 @$ E
    14- B1 \; I! R& `1 t* G$ }/ d
    153 U) o/ q5 ~/ B
    163 p$ v4 y4 g4 L& S4 x& `4 Z# _9 Q
    17, S9 u3 h+ Q* ~8 P( ^7 m
    18
    5 c* @. }7 Q; ], \* @19* _. _) y& w! O0 D* X# z2 S
    20( M7 w8 ~1 ]3 f" R% v9 W3 H9 U
    # 参考答案( \* z5 Z9 J! h; l; }- Q9 j8 R
    s_area = pd.to_numeric(df.area.str[:-1])2 O; l" r7 x3 c& [1 x
    s_price = pd.to_numeric(df.price.str[:-1])
    ' j& o- ?) h$ T9 edf['avg_price'] = ((s_price/s_area)*10000).astype(# V6 v( ]8 d9 k& z* n
                        'int').astype('string') + '元/平米'
    : |! C& R3 E9 O( @! u' U
    ( X% A6 r! a  f1 P5 U# I- ?" |9 [df.head(3)
    3 D- b9 L" k6 A4 b* J# w! E' ZOut[167]:
    + {3 X9 X$ x1 V3 O4 o& K   year    area   price   Level Highest  avg_price
    ' K2 t" k  e% J! J0  1986  58.23㎡  155万    高层     6          26618元/平米9 \0 o: M! {3 P# ^9 ]! {& \
    1  2020     88㎡  155万    中层     20          17613元/平米
    9 E. ~, z7 @: U' T( h0 G  g, A7 ^8 B2  2010  89.33㎡  365万    低层     28          40859元/平米+ _8 D* V# a/ M& g0 m" |: \
    1" X0 ~/ l* u" s. q, ~
    2
    0 f. w3 N$ ~3 `$ Z& v31 O$ {) K* f' H& }2 ?: [6 T& L# B7 h0 S
    4
    5 v9 W+ |: r4 z4 U& E" o: U8 L5 X2 b5: m& F( B: m& l9 n/ z$ e' I4 f
    6
    - o* t5 E: I* \' w; L) A7% k* h5 P5 g$ P9 `% x+ |" r5 ^) l
    8
    9 d# [; j: R; _" s' R" Z9# q! J9 ~" V3 W5 X7 S0 b# o; u
    10' ~, I: _6 F: L$ M
    11$ e2 D9 S( R1 V2 K2 N! h7 ^
    12
    - s* b% h* T" s( X, P1 eEx2:《权力的游戏》剧本数据集
    * H* b5 Q# n; |8 w现有一份权力的游戏剧本数据集如下:% A0 C, ~2 P; B, ?/ A

    1 L; v# G* ^- q5 {df = pd.read_csv('../data/script.csv'); l' |9 I4 r& _, D+ k
    df.head(3): I! Q5 a- v& {! m8 x7 C
    ; \: \: y9 d6 I4 j+ x/ c
    Out[115]:   ~; P" H  L2 K- ~; a1 v
    Out[117]: . j0 N1 G3 {& q" T4 H2 _$ |: f
      Release Date    Season   Episode      Episode Title          Name                                           Sentence
    1 m' |& }6 n+ ^7 O2 Q0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    7 R& M5 Q7 G! p- d8 ?+ `4 ~: i% v4 h1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...2 B) H! p# g5 t4 Z2 @9 q! ]
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce . V, O& V% C" q, ?% [" U8 b
    1
    5 p+ H' i5 u0 q- H2; e% H% U% Y$ U# w: b" R
    3
    6 R( Q! J$ r0 i" O4, \" q) q8 E' n
    58 U; ]6 M  o  q, c% U& K- U$ V2 N% e
    66 d2 I* R8 o$ z! }
    7) g5 a2 x  v+ e- t" y) |* y
    8: W3 j# S8 D* U( o3 @) D. C
    9, u+ c0 M% F- F
    计算每一个Episode的台词条数。
    5 L, N, i& e8 Z. @2 _' ^, ?以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。+ o4 b$ \8 ]) ~" ^0 \* w
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    ; M4 H" E* o* P7 m& u计算每一个Episode的台词条数。4 t) M3 v. b1 n5 o  w5 Y
    df.columns =df.columns.str.strip() #  列名中有空格: M2 B% ^9 ]) L& }3 p( G; l
    df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()+ N" Y+ q1 P9 J& e- F- i

    % n6 s2 N* f7 G2 }/ l1 u. R% rseason    Episode    O: S6 G3 B; n: r* w( v( j7 `
    Season 7  Episode 5    505+ b9 m1 R8 ]& w" s5 V2 Z
    Season 3  Episode 2    480  T" A, o5 s" P1 y
    Season 4  Episode 1    475  q* O4 H& U" |+ k
    Season 3  Episode 5    440, `5 `4 ]  z: a. _+ c( R
    Season 2  Episode 2    432/ s9 o- K, M' l) a( o% H) v
    10 T8 Z. j; _/ ~3 B
    2
    8 }8 ^  a, D5 I* j: C2 z$ Z, W3$ S, S; n9 g# V9 _/ I# M, [
    4
    $ A8 [) q  \6 h2 o* P, l5, A# R8 S' b( s5 F2 u
    6
    & y6 O) k1 q7 }: C7. }9 I8 J( R  @0 D; f: q
    8
    / f, S* ]! \  w4 B9
    + r  x  E* o0 k: @以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。. o  P8 D, ^; _3 N( R) k
    # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
    % E+ g& X/ s0 Adf['len_words']=df['Sentence'].str.count(r' ')+14 o0 O  G, m' k# k3 D0 C
    df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
    ! A9 W& {& G- Q$ A
    9 D- A/ D5 p% E9 Y: XName# Q( Y" Y$ J$ Y4 P9 k
    male singer          109.000000
    , [& E) s2 I2 W7 M% v+ bslave owner           77.000000+ _8 C) ?, C* v9 l
    manderly              62.000000
    / c- n( p, ^4 slollys stokeworth     62.000000
    5 d$ a$ a6 T% C  f# Ldothraki matron       56.666667
    8 j/ n3 e7 o, J$ y$ J  d( J$ pName: len_words, dtype: float64
    * \6 K7 s5 J( U4 E- A7 Q  S8 a9 N1- |, J* B3 ]( z0 q  E. k. ?1 g
    2* B( W' X1 u- k. z# i  _0 T: d, P  t/ e
    3
      C: g* Q! I  J% E42 {- _: X7 @4 \5 q; Q' T
    5' W' j- n& ?' f: o! g, M$ ?
    6
    $ n  H8 E/ q3 A7 x7
    * Y7 Y/ i- K9 \8 _- ]8
    ) d" j4 d' v% y+ R( @; P. b9
    4 }& ^( H  I: t, x10
    . Y+ V* m3 M& w) \11
    9 Q+ h( f2 c! [( v8 T4 n% J若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
    3 G) p" R7 e7 H% x0 K; J- z* Qdf['Sentence'].str.count(r'\?') #  计算每人提问数
    1 G/ D! c) _6 u1 }ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
    , R2 e9 u: v& Z* d2 I4 Odel ls[23911] # 末行删去
    3 \5 D: g8 N6 Z$ c$ idf['len_questions']=ls8 F7 n2 f1 r6 t1 [. _( K0 _9 R( h
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
    8 t5 R, m$ C3 C
    , g2 c! D$ l$ k: `8 L( w0 `Name$ Q* S. a" K3 G$ @/ @! V! a
    tyrion lannister    527
    ! {4 B' d% r3 l/ e% f* Z7 ]& z7 H) hjon snow            374
    % @& a5 J: V7 |jaime lannister     2836 `" w+ r. e& e2 r/ @, h5 f: ^
    arya stark          265/ p$ N" }8 V3 w1 v- h
    cersei lannister    246/ _' G  _; a5 A+ ?
    Name: len_questions, dtype: int647 w5 z- @0 p) `8 j2 P1 [) \

    % {+ H; q% a: {2 Z  h# 参考答案7 X0 G0 Z5 l; Y" I" `; Y6 [
    s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
    9 G( x; E4 m. p$ _1 n* Q/ j/ Ys.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()+ U2 e& J" T7 ^9 D9 g! U

    2 ]; S9 a5 S: }- [+ f! J7 s1
    3 ~  @: f  w: L& h% R2
    $ C. w, y! ?* p3 y* S3
      X- u/ C5 ?* e5 t4  }, S( I1 l9 l! O) M
    5
    5 P8 S9 U9 i) ]6# i/ D9 ]: j- H8 \$ ]8 q! j0 B
    7
    7 y( v- N# n6 u84 k: a2 N0 \: S& p) E
    9& H2 Q! g3 N3 B
    10
    ) d5 J$ a* h: U  r. i3 t11# \6 _; p+ C/ R8 F! u
    12: d# r6 c1 Y' I! W
    13. u1 T3 c9 Q) `+ T: R6 u
    14/ h# B) d4 C0 x2 w/ y6 S
    15' L# d1 }, s  e  R/ l& k1 {  r+ g: {
    16
    7 f7 o& S# O  P8 F+ o17
    / A% e- @/ Q4 D3 H& H3 j% f9 x第九章 分类数据
    7 _$ N9 u, w+ J) iimport numpy as np! x* V: a, }  z! u& r' g5 A7 \- P$ g
    import pandas as pd
    3 X* `5 a7 L# I6 Q  L  B# I1. R+ x' I8 K: j/ Y* `( y9 Y9 x+ g
    2( X8 b: p3 l4 _% J# f
    9.1 cat对象- B8 `* k. e6 `
    9.1.1 cat对象的属性
    ; \9 ]4 e: [7 m+ b8 }  在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。8 }  T; r: N& N9 Y; f
    9 {/ l& d4 Z$ \5 l
    df = pd.read_csv('data/learn_pandas.csv',
    . K5 P+ v, E5 I- C+ q     usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])2 W+ r) d5 w" n
    s = df.Grade.astype('category')
    ' h' O) [9 g. {" |" }/ t1 m/ N/ B& `) k' u2 a+ K2 C
    s.head()
    : k3 H: a. `% D' w3 k( NOut[5]: 5 s" V8 x( d/ J5 T  X
    0     Freshman
    ! c, f7 ?& z7 o  r0 [! I1     Freshman
    4 x- r: Q& k& r3 C& L: ]2       Senior4 p- e  z2 x6 z0 T3 K$ |
    3    Sophomore
    & H1 x  v  w  x; X  f4    Sophomore
    4 \0 R, t" s, _7 B4 ?Name: Grade, dtype: category
    / J; D+ v. E* rCategories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']9 ~8 W( \+ }$ z( [& k8 y
    1/ R! i$ I& d8 \
    2
    * y" f0 V) `+ |- Z+ C39 Q1 O8 i& L4 y) ?
    4
    : Y- Q' Q: [1 b0 E57 `, Q& ~# R. R2 x' R
    6
    ! ~: G. w% t% d. {& F  |7
    % W' b: D- X9 @+ |5 p8  h3 @: b4 E- D* u
    9; C$ F% R1 y# h2 p. J3 Y3 N
    105 O% u7 b( W" r7 x
    11
    ! Q& D' m$ L. |0 Q12! S+ M# P+ O' t9 _6 R- x$ `
    13
    1 T, S2 q- Y0 {  在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。1 i: I7 t" u0 U0 }

    2 t; k7 m6 }/ z+ Z5 as.cat
    . y5 m4 Y; i) l! uOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
    5 s8 B" A  R- d3 Y& {1
    $ x. |- |3 q3 u. q' x  V7 w2
    , p& v( H$ K. k7 O( m# h) Z; _% L( Vcat的属性:
    4 a: X) n  T! f+ a
    ) J5 N9 c5 _7 K9 p( ~: Q( Y+ L! ?, Xcat.categories:查看类别的本身,它以Index类型存储$ g2 r7 @" J. Z' n
    cat.ordered:类别是否有序0 O' H; B* p. V
    cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
    0 L  p& |  }% P: S  Qs.cat.categories
    # R. {4 |! C  i+ ^+ kOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
    5 @% I  b- w% c" b# `2 p4 n. n7 K5 N( I9 y: |$ d9 O- n. Q
    s.cat.ordered: C- d6 Q  E$ b3 b& g
    Out[8]: False
    3 U- w. H! _0 A* y& a. V, Y3 ?, T8 J3 T( P) `' `
    s.cat.codes.head()3 r. M5 ]3 h; t. M$ ^7 u* C" @
    Out[9]: . D# `) O, F* I6 W
    0    06 \0 w7 J$ o1 F0 T, I. J
    1    01 M! S% l: u, y, h
    2    2  l, [& s7 K) Z/ F+ l% W
    3    3: R/ u7 V1 A! }" A3 V5 i% V
    4    3, K$ N# o* E" O9 l2 A
    dtype: int8
    - Q, u& K+ f0 i- q$ E# D1  w5 {# O/ h# A/ v
    2
    1 M* W/ v# r1 Q5 B. n! p3
    / p# x1 s5 |; r& x$ @! Z4 p3 }4
    7 R# j# X$ f7 E/ Q, D- \55 |7 Z4 V- ^8 U) [7 b* Z1 L
    6( v+ `$ R) A9 h3 w$ t4 r1 L) h
    7
    ( D' h4 n' {$ A5 {0 Z( r8
      D5 J" X7 x5 }. H  Z9" ]9 `: g4 p' M9 @
    10
    + u  i1 v) K/ U  G# F11
    $ P; b5 T$ y. N. E7 o% s& `, g1 R12
    0 R" ^. d6 Z1 e7 x: e/ s! o13! R+ t' e9 Y/ s7 ]; p& B  b
    14
    # d" e- [" u5 a9.1.2 类别的增加、删除和修改
    # G' p" V$ F" h; _$ m  通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
    3 A5 [6 N( }7 z3 i* a9 m9 u. N; {0 [4 \* K. K
    【NOTE】类别不得直接修改
    . l! W1 u. s* t+ H9 N" G5 U( {在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。2 g3 P7 k+ K- p4 ?' `3 r' F
    2 K/ k- ^9 r; q) ]# `0 P7 N
    add_categories:增加类别8 j0 w: G9 ?8 j- c+ v" I
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
    $ |0 v8 k6 d  s$ L- k/ os.cat.categories
    " j' V: f. U- A: L6 q9 X* w% N2 J
    Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')- N* h0 F4 i7 M1 X# H+ t& U
    1, X) T/ _1 e% k8 @4 |
    2
    2 z; R+ ?. n9 }% O0 j( O37 G/ g7 d# e9 M7 p6 Y: G9 @
    44 ~" X, `7 _( @. Y3 p
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
    ; P; n; [2 W( qs = s.cat.remove_categories('Freshman')+ S6 ^. c( U& h! s& ~: M% F3 n7 A! h3 R
    & x6 {' U! w& L* ~; N6 ~
    s.cat.categories
    ! _) S$ C6 P! l$ y5 sOut[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')+ z0 V* m- A9 C. G$ }: h) r
    + W6 U. s2 p! p/ e
    s.head(); [! Z! W# u. e  F
    Out[14]: 3 F% I  R+ d% x, Y
    0          NaN
    2 X1 ^! w% p! r' S) F( R/ Q1          NaN
    1 K* q3 u/ G4 n7 B5 p3 W; i6 a* J2       Senior
    , P" n5 o! v3 b3 n6 C3    Sophomore
    ! z0 A. S& K/ `4 |4    Sophomore, j, _" T: Y1 S% }% P3 O
    Name: Grade, dtype: category" G9 N* v2 Y' x% W
    Categories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    ) }- M% S% _. e) n, C1& g* a1 S" {' {: r0 M
    2
    : Z8 ]: a& o% G/ a/ P38 e0 `/ q( v) q
    4
    9 ]- L" b- |+ o: b( ~5
    7 @, t( e& t6 t; v64 g. ^9 y! Q: ]
    7/ t/ h) L- a. P
    8) w$ U& r7 _: n5 V
    90 h. [$ f' P0 T6 ?. T
    10
    , S4 s9 G8 U1 t6 V7 p4 ]1 v4 V11
    % x5 A" h6 O; I6 U7 F1 J& E& J7 n( ~12
    / B9 r6 h  R( s, \) w13" k" h- O2 S7 V, U& b
    14
    & O) ]! v: p# g" U; ~set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
    1 r' ?. p0 B' x8 O* b9 `8 ]7 n3 ys = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
    8 p! G# c6 \5 B6 {s.cat.categories
    : }2 K4 T$ s" {8 N3 m: t# A  g& `Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
    8 b& t0 G' f# }, P' P' \7 s' t
    7 U/ j1 _, z( p' hs.head(). G/ J$ R% D  W& i2 _
    Out[17]: 6 Q/ B) Y/ D( u& J) Y8 A. w
    0          NaN
    % ?1 A* ?  G2 ]9 G1          NaN
      h! V2 n  Z$ c, E' M# s- y- r2          NaN
    : a  q" V6 Z5 p3    Sophomore
    & c. u$ k& _8 D8 R3 ~- ?" T4 i9 a' S4    Sophomore
    ! k, \# `5 @5 @Name: Grade, dtype: category
    , e$ z2 e) a7 I) t& jCategories (2, object): ['Sophomore', 'PhD']
    # |# c4 N& E6 w+ Y! {15 k5 M/ H. n0 m% r
    27 H. n  N' d+ w0 y
    3
    . g6 Y& b1 L  H$ q" L) I6 D4) z" u/ N3 ?' O, J  W
    5  W/ ?1 {- D. w* x" `
    6
    $ i1 i3 }2 S/ n. c; H- C7 Y$ x: F! X* P7
    ) ]: f, k% {' s8
    * h) R6 j3 s5 G# t& q$ _& s3 W9 R9( v; v: g5 X# G; {( p3 L  n
    10
    + F7 Y" v6 o" _( z% k5 n5 T/ }11
    8 f6 D5 P# A$ r, }! H* \$ g" }12) q$ I) p. |# ^7 }2 _( F
    13
    6 F. H- D7 O1 C3 L  G$ Oremove_unused_categories:删除未出现在序列中的类别* p$ i0 W: q" ?
    s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    . u( s0 f) z9 s! S, ns.cat.categories
    - D" c' R7 j/ k/ R* \" H# |9 _" y) z+ o: Y
    Index(['Sophomore'], dtype='object')+ [+ _5 Z3 ]5 q/ g7 ^2 l# \1 B0 D
    1
    ' G/ h: v4 x' B+ `5 @  O7 l4 Q2
    + E, ?" ]. G( G3% W5 O$ r. k+ K2 \
    4
    " g  ?- M9 v7 m7 trename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:+ J' _: Y' X5 x" D6 L3 ?
    s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})1 @7 s  [3 G3 f8 ?
    s.head(). F4 Y* l. P. c% J- B  s- \3 I
    ) I) [( v! m  }
    0        NaN
    0 [2 Y% F- A* d' A3 K1        NaN& y! I2 f) ~: V$ A
    2        NaN
    : f' D- ^- l! p/ C/ C; X: H3    本科二年级学生. d+ A: |+ ~7 d* Q7 S/ J
    4    本科二年级学生4 x8 ~" u) I6 t3 U$ p
    Name: Grade, dtype: category% B- b& _4 n" V) Z* Q
    Categories (1, object): ['本科二年级学生']5 j7 _; O! [' a; j0 o
    10 g2 ~4 |  g1 @$ ^+ Q
    2! }2 _% I( p: p7 ~9 Z
    36 F/ t/ p: H' F( S+ n' p# g0 b" C
    40 p% W7 m* d% L9 O! x1 V
    5
    + I9 [% u, ^, @  W7 F" J6
    4 y: N) R8 [$ U! O: ^78 M( }+ K( ^6 ?1 g: n" x
    8: w% L/ m& K% V* Y0 I( C0 v( P8 l! F; j
    94 {5 z- j. r0 P6 C8 R
    10
    # \& g3 K4 m- {+ t3 x. |" v4 k9.2 有序分类
    5 e" w: q' J3 V/ ~) [9.2.1 序的建立- d- r: K# y! T% y4 u9 A
      有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
    / x: X1 o5 u" a0 R5 O8 {8 ~+ W1 w- h
    1 Z7 M+ ^! E* ]. c& \1 ls = df.Grade.astype('category')
    " }4 `4 M/ S2 u3 P7 m' Fs = s.cat.reorder_categories(['Freshman', 'Sophomore',
    + d" t$ L2 P# W* {                              'Junior', 'Senior'],ordered=True)- ]6 J. s0 m' @7 N& M
    s.head()
    5 q( D0 }7 Q* Q# Q9 R2 n1 E: `- K0 QOut[24]: . p! s6 _! m# f" k) ~. I% Q: d  f
    0     Freshman
    3 |2 s% \; q. ?' ^" q2 Z& z, Z1     Freshman
    , d4 c: J+ v- u0 i* M* u6 M2       Senior, e1 n, p' k" `1 ]( m
    3    Sophomore
    ( P" f3 v; w! j- c; t/ ~/ s4    Sophomore
    7 P$ N8 O7 x1 d+ n' E7 \  \" [6 bName: Grade, dtype: category
    ; E* w$ W. U( e) O+ cCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
    " N! m6 O( c3 ]* Y' u$ x: y5 i* @9 x& |* J4 {5 W0 O
    s.cat.as_unordered().head()1 s  W: v: A7 q  L' L& q! ]
    Out[25]: ; j, q6 p( H, g7 E2 V$ N$ c
    0     Freshman, t- @; {" }' G
    1     Freshman
    4 z: }6 R  J. S: @/ }+ W$ {& ?2       Senior
    * V. x8 c+ S% w5 O# Y& f3    Sophomore3 f& m' u8 O) f! n; {* v3 r
    4    Sophomore1 v! F, y0 B" g) F
    Name: Grade, dtype: category6 j) i6 i) k( D
    Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
      _% P# P! m# P: Y0 ^% g; e% M. m$ N( I! {9 P
    1
    8 ?; n8 R4 X; o- y28 |; k2 W  Y1 p6 W# p1 x$ U1 |  i
    3
    ) v7 `; H& C& R, b, F0 D  }43 v8 i1 t. k- `
    50 @5 `/ j$ O# ]& m. J
    62 r& i; D  L# X+ i
    7
    6 s; c6 \- x) z' h  B8. A5 f: |8 T. S/ {) ~
    9, f6 J/ c2 w0 X0 l; R5 i; j
    10
    7 J# ]: b' ], S6 i3 U6 Z: C% R116 W7 w* k, A& Y+ s
    12
    " A: N& ?" c7 Y7 \  k2 |1 E5 Y13! [1 R1 G5 p4 h
    14
    3 O8 }2 t( f& b. p  G5 W0 J15- d! m. l/ F/ @; L2 w: M* x% ~
    16
    " h$ _/ c' A% n) T17
    3 W) q0 F! Q+ j% z1 o3 u5 J18
      h4 u# |" d& z- i8 k19
    , g, r( @5 ~$ A20
    0 t# \) W; T: u( w21
    * o- g7 X, L+ Q22- c8 q) G3 n9 ]9 a! g
      如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。" M% I" h0 A% ?. Q+ N

    ( p$ K0 v5 I' C9.2.2 排序和比较
    . i3 d2 K. p4 l1 T6 X: w6 ~在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    ( L5 {+ H2 M; N& E) f
    ( _( F9 K# G5 F! [/ n  分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:. T2 G  T1 O7 ~8 G9 Z6 j

    + e! B9 [# |8 Z, K7 w' g1 Ddf.Grade = df.Grade.astype('category')
    3 G" L1 |' i# n) E2 _/ Jdf.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)0 o4 c/ v4 U  G# L* }- s
    df.sort_values('Grade').head() # 值排序/ f9 M6 S* Q1 c+ R
    Out[28]: 0 O0 U# ~- q( M
            Grade           Name  Gender  Height  Weight
    2 x. M1 E5 z. U/ u0 X3 a* f) j0    Freshman   Gaopeng Yang  Female   158.9    46.0- y* c5 i0 s- d$ g# p7 C0 s
    105  Freshman      Qiang Shi  Female   164.5    52.0" {" c- A4 S; m" f
    96   Freshman  Changmei Feng  Female   163.8    56.0
    6 ]4 `$ |0 H# u# b- E$ I88   Freshman   Xiaopeng Han  Female   164.1    53.0( m+ v. V/ w6 X
    81   Freshman    Yanli Zhang  Female   165.1    52.0; K7 [5 D0 L! I, y# m( v2 [

    ' g* X  k9 P* w, \4 a& ddf.set_index('Grade').sort_index().head() # 索引排序
    ; g& d2 t: b( F4 U9 a% POut[29]: 2 j4 o$ z+ B2 K
                       Name  Gender  Height  Weight
    5 \8 I9 X$ }$ QGrade                                          
    - U1 S3 F4 `2 B/ CFreshman   Gaopeng Yang  Female   158.9    46.0
    5 S7 Z, g/ x+ G5 jFreshman      Qiang Shi  Female   164.5    52.0
    ; s9 r% e6 C. c/ g& R8 s6 bFreshman  Changmei Feng  Female   163.8    56.0( {2 U5 k! ~) W) R& a
    Freshman   Xiaopeng Han  Female   164.1    53.0' M, S4 v& X3 `% y4 U* e2 I$ K$ q
    Freshman    Yanli Zhang  Female   165.1    52.0
    % ~! D. G& a& q- ?6 d
      ^( e! q: U: ^7 ]* c" E1
    6 y( h8 b6 l, }2 w2
    1 ]" O6 }5 ]  N39 @% }) Z, o- s' V% `
    4. H- r+ \% u1 ], R
    5
    / c6 z9 D9 ]# j) A  D) ]6
    ! d' i' q. v0 i4 a) q' [' Y7 _7
    2 |6 a$ W6 m2 H2 s& E83 K6 J, u1 O) h+ \# [
    9
    6 E$ [* G' w! V2 e7 x10  G0 C" [4 f  l5 @1 R1 s! x
    11: {$ Z) A9 n3 P) q
    12
    + Z/ B0 h' e6 B" ?9 L13* v3 t' T7 C0 j1 G
    14
    % |. l. e+ S, K- w8 S1 g. j: E155 q- C  W5 h& v* a
    166 j7 ?6 F- Q/ G1 P, d
    17
    . \* k/ L' T5 e# {! C2 U18" g( e# K+ U; h) [
    19  _- L* S* A1 b
    207 N- {$ ?9 p8 J
      由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:/ k% Y! H- _! H( f6 `! I" K

    - y; n/ U8 ^. ^! Y0 Q/ F==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
    ; t) w$ v/ z& H5 m; K7 Q>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。' d" C3 `1 `) C) F4 X
    res1 = df.Grade == 'Sophomore'! K- b3 j2 t  Q4 [6 T) c9 h: t
    ! d2 t' C6 y4 ]5 q9 }2 e
    res1.head()
    0 K1 l0 i# i& m& }) m4 ~Out[31]:
    0 ^+ T) r& _; J$ Q0    False
    ! d+ x& v6 u. E+ X1    False5 M3 n5 p- R/ i' _2 s
    2    False7 o4 [# X  l* `$ y) k) u) H
    3     True( ?' h% k8 M% C  F
    4     True/ @7 T0 h5 _/ N; R1 |) n" t
    Name: Grade, dtype: bool" R  M: l% V$ I8 `! ~2 p, q
    & ]# i6 R0 |3 K
    res2 = df.Grade == ['PhD']*df.shape[0]
    ) W: I/ l: U( \7 p; N* {# o1 {/ E2 |9 \9 U# x; [# G
    res2.head()$ ~: @' F  f, j" Z" Z" g
    Out[33]:
    ; E0 V; ^) B% `0    False
    # z( w5 }/ [- U' I% b+ _( J1    False
    4 f2 K( p5 d9 N: y/ s( {8 L9 V2    False6 e. P" T0 C% S# j
    3    False" Z: p3 W  X" `* _/ x8 t
    4    False( H, Q7 i' x7 o$ w+ i1 {
    Name: Grade, dtype: bool
    ! R+ J, I: v( m! O8 Y1 M
    5 Y4 b* s% Z) c% S% l4 Ares3 = df.Grade <= 'Sophomore'
    2 [- |( ?( ^9 B1 A( t" y( F% I2 N  y3 H  Z3 W- C
    res3.head(): O1 {" E2 d* i0 k, u* i' K
    Out[35]: ' v' b5 @( f" u1 D% ^: K1 p! x
    0     True
    5 l  d* N" }* Z* N7 m8 H9 Y7 S1     True
    ) g0 _3 i4 E4 l) w: `) j+ [2    False
    # g1 a9 e& S( a7 o" u' f- v) X8 @2 y3     True
    6 S: O  G6 J- T9 {+ G3 f4     True% H& j2 P; C! T( I' e% r. w" Z
    Name: Grade, dtype: bool. u4 f7 ^$ m3 I2 d" ]: \: x

    5 M5 @9 Q% [1 ]( E& R+ h# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
    # K9 ?0 y4 R! P3 D3 X! B! m- N  ~res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) 7 r2 `+ }% S. g
    - M" ?- w' c# {* c
    res4.head(). L% k, g! z; e; _) j
    Out[37]: & W% }5 N9 }) ]% ~8 J5 B
    0     True
    4 _" q% G# O# ^+ o1     True4 ?8 \& }3 _! V- m
    2    False
    8 M" U- ]- z7 W: ^& Q  D3     True7 Y1 B( o+ c: x# o7 O
    4     True) N$ q/ C% a* H" m5 l. q( D  |
    Name: Grade, dtype: bool
    0 }. o" q( [) b, n, g! N, |$ n( K! e! K' J" H! ]( n
    1
    - P. @2 R2 j+ z0 X: }  U+ }2
    0 |$ s7 o2 g5 ]3" H2 Z( V2 q* o
    4  v+ V8 T4 P3 \! e+ q4 \
    56 ^/ C, c, z1 h3 \
    6( D& Z! g# |+ T+ u
    7
    # [/ N1 w! _7 R  h0 q89 I& d' }8 J% g
    99 a$ a, t1 i) ?* \) t; i: i2 z
    10
    ) q( D% E8 r! Y* c* N11
    5 g, T: g, ]  y8 Q12
    9 c! ]/ S: P# ^13
    9 c2 `& L) v( l7 J9 h144 L4 K- e; q. F* g7 e- N
    15% _4 r4 C; y, s' w8 h
    16( @2 K; S# A3 i  C9 ^/ g
    17
    ; H9 S$ m* J% a$ W8 W/ E! ?7 }- A18
    - ]# m9 B/ U* s3 x19
    : c! @' e, B+ P20
    1 j% P! J- H) k) B+ p- z4 n  ^' L0 V21
    : u& x. |: w9 {228 }. q% T/ S& P, R
    23
    " L+ I/ l3 w, w9 a24
    ! q- @* u" q! o, G7 _" ^5 B25
    7 C6 e, V* ]* d' G" `262 W! I  _  s6 V& p' Z/ i, {3 o, C
    27
    ! m" _9 O. r1 J, Q1 ^2 [3 p28
    : u3 W  Z/ g  c2 ?$ d29
    . e- M; f( @& W+ \30
    5 }6 N7 i3 z  U6 H- D7 j1 i311 s( b: l, f* O. P
    32
    * ]! [/ p) M% v) S" Q5 ~3 g4 n33( M# |! S. @1 C5 H' E
    349 n6 J5 R! X1 @0 x1 D5 ~
    35
    1 x) m9 W' p  G8 i# y- V4 v  A36
    ; u, h, i& b7 u0 N" _37
    ) N, r/ H$ w) |& d8 [38
    4 L1 [' i- j7 w. |& a2 i0 A) G39+ N. p/ E; y% P1 `* |& {
    40
    9 i8 D/ S( N- M+ g7 \414 J9 x, d6 _0 \. x3 h
    42+ C2 E" P, v' f. h3 ]" @4 L
    43) ~/ H# m$ }2 N$ H: B
    44
    ( C! a. `# }% ?/ D9.3 区间类别( ]# [* S0 h9 l" n" V1 W$ Y1 m
    9.3.1 利用cut和qcut进行区间构造
    . p& q7 f. L1 B* u0 U) ^  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。4 V* o" Q& V5 i5 j0 F
    9 z  P! B( b7 |7 K3 ~5 ?' b
    cut函数常用参数有:+ |5 _5 S# J9 C3 j$ h
    bins:最重要的参数。
    & G- N/ W& S1 ?. u8 |如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    ( U8 [$ |% {3 l5 h' D2 E$ k也可以传入列表,表示按指定区间分割点分割。) F- E& G, e  P; ^4 \& o
      如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。$ j) T9 t( D, e0 ~
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。4 y# K5 n+ }, ^9 j* p( t  x$ \
    ) X3 ^! p+ b: L5 \
    s = pd.Series([1,2])
    ' ]: q+ c9 e# }: e0 G) b: u# bin传入整数; m  j' u7 Q8 a5 q7 S
    + ^  [3 ?0 ~' y6 r
    pd.cut(s, bins=2); U( _8 c# N& ?$ h( H# O
    Out[39]:
    , g# `. \, ]( `% ?! \# h2 l0    (0.999, 1.5]
    ' [/ Y: y" Z% n! l1      (1.5, 2.0]
    9 W0 Q& M9 a0 Udtype: category' v# f7 K# D  j
    Categories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    3 {6 Q2 [8 z$ b( J  c; k
    ( I# {) z: r$ Ipd.cut(s, bins=2, right=False)" F6 v# y) w6 B8 w
    Out[40]:
    - N) m3 |& ]0 h0 B+ f, O+ i( z0      [1.0, 1.5)
    7 ~) g$ E: B: F0 R1    [1.5, 2.001)
    8 I, L/ ~2 o, d' |" U& a8 U/ Gdtype: category, O' D4 a7 \6 D- m5 A
    Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]/ V2 B  e, a7 l% m+ u& Q: }) B
    2 P, C1 Q* I$ ^9 F5 B  z
    " |) o/ m7 }" E
    # bin传入分割点列表(使用`np.infty`可以表示无穷大):
    2 h$ ~! ?- J( ]$ s/ P6 lpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])" n9 ~  p* k/ g/ R8 T& W% [' M
    Out[41]:
    ( z- R0 Z2 D3 b0 V# _* W6 u0    (-inf, 1.2]4 M8 _/ V! s1 k$ q9 Z; L
    1     (1.8, 2.2]
    " @0 y+ j  B9 g( Wdtype: category
    & v, X$ Y( w# T7 ?! j3 jCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]
    $ t) [" P! \& D' V2 ?4 [1 ^4 i4 N- S5 m8 ~( X1 P
    11 h# M4 Q, q3 |. g! X
    2. [8 ^) w; Z/ X3 `
    3
    ( f1 y  X5 F; \9 v4
    : i& g3 d; T$ J6 H# U8 ~" ?6 e5! I2 o/ t7 t2 i6 R+ b# H
    6
    + P" v5 h& x% `% I' q  p) U7
    5 E- c5 m* o1 _. X% Q: d/ H8
    " g. l) w0 ~; r- b9' s7 }% k/ l; t
    10
    + S1 e8 [7 Q, O) p11: h% J; w! n. a' Z5 V
    12
    1 f6 p' d' r4 `- Z139 ^5 f$ D# \' e; G% o6 r8 G
    14
    9 J. @+ u: ?! b) j- u8 \151 y; F" L" P. f, j& p4 z- f
    164 C9 `& g$ R3 ?# x  X6 `! J3 @6 k
    17
    1 B8 `4 q! j3 {3 g5 `, B, M" S18
    4 e; S( w# p5 Q! Y+ R4 N8 ~19& H# Y6 E4 s  v( j) ?$ E
    20
      l; D, {2 T. T5 J! W9 c21
      g/ V; T0 \. Y' L. Z  R22
    . ?$ ?. T. k1 C23
    % a8 k) {9 s* I! l24* Y9 B! {$ o; K9 K. m8 t6 E
    25
    5 J& W; J6 n2 q: x, I, ~labels:区间的名字
    5 p; g1 A7 k7 \) ~9 u' }/ ?retbins:是否返回分割点(默认不返回)$ p& T5 q8 O" O* z$ t; n: l
    默认retbins=Flase时,返回每个元素所属区间的列表( @3 u7 [- }) o
    retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
    $ a3 w+ s0 K5 L7 C( z% q
    * M2 Z; N) ^- x) }- Ps = df.Weight
    1 E9 @6 S# z) Vres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
    % R! e  r: q6 D0 ?/ zres[0][:2]8 i# d/ h+ M4 ]8 C3 F

    1 F2 O/ }6 C6 T" X# f  TOut[44]: $ Y# x+ w6 i, Z" b
    0    small2 w/ A: D: f+ h4 u, g4 U0 H
    1      big
    % C* U6 _: i. Q) Z/ L. S4 }dtype: category
    1 o( E$ }3 i* D$ i; ^5 V# ~Categories (2, object): ['small' < 'big']- `% m& I6 y7 ?7 M+ f# ~1 N

    - [) S, D2 }$ Z4 R0 Hres[1] # 该元素为返回的分割点* B) F3 L8 D* U; |$ i# [" n8 |
    Out[45]: array([0.999, 1.5  , 2.   ])4 N( E4 D4 j' m) L* ?) g& J
    1
    * A% {0 [0 p9 ^6 V8 C$ w6 b2: y: _) B* z' K0 F
    3
    - X; P8 }' I+ m; i1 V4' W6 X6 I1 C( W; B; H
    52 t. a  s. H9 Y$ a- s1 @% C0 M6 @
    6
    , ^0 m7 E- X- F+ w! g7- x. L: F7 _  d/ e: G+ O/ _4 {4 \
    8
    0 B* r% d1 d7 F, o: r98 @0 j# e7 `! a+ M
    10
    " n* R$ l" X% {9 C$ U5 Y2 h* j11
    ( N5 B" C5 K. h! t12
    2 D1 l( H, J; Lqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。4 p( k; u* w  K7 K7 x
    q为整数n时,指按照n等分位数把数据分箱
    2 f) s0 `$ }  X+ Yq为浮点列表时,表示相应的分位数分割点。+ a1 ^) |8 a4 r- d' h
    s = df.Weight
    7 n+ a6 t; M6 O3 ~# }
    ) N9 Q! I2 M/ L" Spd.qcut(s, q=3).head()
    " {' S4 n4 o. q5 ?  jOut[47]: " F2 ]% {" P1 ~1 d' U( x
    0    (33.999, 48.0]/ D" R" S. T8 p. K5 ^
    1      (55.0, 89.0]
    2 E% G/ ~, M9 k' I, A2      (55.0, 89.0]6 N, y5 U3 E  {( ]9 g2 l/ J
    3    (33.999, 48.0]2 y/ Q8 z, K, w" U
    4      (55.0, 89.0]
    3 e; K9 d7 u/ Q  [0 hName: Weight, dtype: category+ h& b. o, _  [9 E% Z2 n" ]$ z7 O7 }
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]
    9 g) M( X* `0 H0 M5 P9 v7 u# s5 M/ V) N$ y6 g
    pd.qcut(s, q=[0,0.2,0.8,1]).head()
    * }/ g0 ^/ ^$ o  U8 R7 xOut[48]:
    # H3 E/ F# ^) G8 S$ J* d7 L1 P: _0 v0      (44.0, 69.4]
    & }- X" S) R1 K- W: q1      (69.4, 89.0]
    & X! u. E( u& @2      (69.4, 89.0]) m- M- n: Q' j& f7 |2 ~
    3    (33.999, 44.0]  Y7 `; I% v! i$ Q' r
    4      (69.4, 89.0]* s2 K% ?( u% O8 x1 B; {3 m2 R; n
    Name: Weight, dtype: category
    / m: M. a5 x9 a0 a8 hCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]# X0 h) B4 D+ t: f6 e; m7 r
    & B4 U$ A' E. X
    17 l# T2 u  y- @) A/ i2 I
    2
    8 g4 u! O  w* e' }- S4 l3
    4 o5 ^  [! i. W7 }8 U& E+ f( F+ }1 T4' J% f+ @3 O1 w+ r
    5
    ' z/ S7 v/ l- M) ?4 A& E. H60 _' f2 a( o# C
    7. Y6 r7 w( o: Y" U
    82 d- U! I/ n# v+ B
    9
    9 G, a# i  f& E! g/ S0 g0 t9 |10: z* H, {- n. ~* w3 t7 D5 N
    11
    8 B+ E: J( T+ l1 Q8 Z12
    ! ~' [% O; {: P! h139 ?* H/ f; b# f6 U, U
    14' W7 _8 `9 ?' G7 Z1 `) B/ V+ L
    15
    ( |7 j$ D& S3 \' H3 _6 Y163 P( X  a9 n' j  G, ^1 K+ D
    170 _7 ~! I8 s/ |/ Z4 l' _" a2 N
    18# k7 d# G# V8 r" v# [  h/ O
    19
    7 Q2 @" e- m+ ~) J. P20
    % E# {( M8 r" A5 E21
    & A3 n9 @% Z* e& y' I9.3.2 一般区间的构造
    # e% p8 A! R; T  J  pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。& \. G! j1 ?- S

    9 e# t5 Y* ?- g* K) Q开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。/ b( Z+ M) q" E
    my_interval = pd.Interval(0, 1, 'right')
    / ~" \3 B# @- T7 \+ r/ z* S( _4 y  ^4 T3 a- V
    my_interval
    ' W1 t2 U: F8 s8 J! ?& o) \Out[50]: Interval(0, 1, closed='right'). F$ R/ O4 A# O* K! D" x
    1
    ) W9 r& x  f; }* O6 {4 C2
    5 D7 E4 [; r8 ]6 l, R( N. J38 O6 v- t/ Z* }' L7 k- y8 U: J$ s
    4
    , d  c8 e) S# [" y. r6 |& b区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。4 q2 y4 M3 P2 ?, D3 G5 o8 `
    使用in可以判断元素是否属于区间: }/ E# D. I& F
    用overlaps可以判断两个区间是否有交集:
    / d' d2 J  q7 g1 u7 v. S& A% ]0 t  ]0.5 in my_interval
    - x) C  u( v" X/ t  ?: J
      a: x% t5 W9 A3 _. V! w/ UTrue. Z; ~7 _! `! A
    17 d# v- \: k/ {* \
    24 i' i- ]  Y: s( N' w9 K
    3
    # D9 @' M, s4 H  j% Zmy_interval_2 = pd.Interval(0.5, 1.5, 'left')
    ( _/ h0 ^/ x% omy_interval.overlaps(my_interval_2)
    8 i# l2 E; J% ]( \' }, |3 ^! I$ V5 C' x6 ]" ?
    True, y+ B( U/ y; [- g. a/ v2 U
    1% \. t0 _' {7 d% X. H
    2
    $ K  D) O( p) h: H3* C" |2 c4 x6 M6 I5 f  m0 j5 ]0 u" @" X
    4
    ; @7 y) F- S: S$ p! n% t1 t  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:3 I5 j% [& I( }! i# e

    4 o8 X2 s' |, q) p( u8 qfrom_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:
    " k8 _" a$ u" K( A& _+ F4 ~pd.IntervalIndex.from_breaks([1,3,6,10], closed='both'), P+ \5 v0 v/ [& \' c

    # O2 A# j5 ~1 i$ j- r7 `5 Y4 l6 rIntervalIndex([[1, 3], [3, 6], [6, 10]],1 O  K! J( w) p. i
                   closed='both',
    ; Z* k% ^8 e: b7 I, x! s& M: L               dtype='interval[int64]')
    4 @: o6 C8 k4 U2 R' u( m. f" j1
    7 x- w  F& l5 d2
    , R  e, M% R5 e32 y; }0 Q8 l: k
    4
    : J; r+ _+ ?$ E( g  S7 {5
    & I/ k0 s1 H- r" E2 [0 N# tfrom_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:
    ( C* Z& u; D( H( P8 a; x+ ppd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')6 C" s( e. {/ w1 Z- K9 P# S

    ; Q3 u9 F7 x: w* v( Y$ K' E! o# XIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    6 g5 B9 b9 E( c. s3 W) @                  closed='neither',5 P. A  ]1 Z5 E0 \$ n2 p
                      dtype='interval[int64]'); h! D2 W( {& k2 T
    16 f# \0 V# a$ a
    2
    ( a5 v2 L6 _, b1 Y: |3; |. }* M9 y" f$ k! F' l
    43 K5 J2 p! i7 ^( p' Y) i' @1 @2 Z
    56 }/ |' r& H1 \/ R2 c- Y' Y" Z
    from_tuples:传入起点和终点元组构成的列表:& w$ }5 [2 v9 L  r: B: S# U
    pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    # [2 m: Z5 ]. g$ @& [: R: M8 E* n9 Z# L& k
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],: x1 H$ z" l" W+ ^2 Y5 F5 [
                  closed='neither',+ d& C4 [$ ~+ b9 [7 S
                  dtype='interval[int64]')
    0 c- L7 _+ A2 G, U1 ~2 B3 c1
    0 r% j* m2 s4 H8 R* r2
    ( i( Z( X6 Z" g# [; c& V3
    + r* p7 t, |/ d% {* h2 C- [48 A7 O: j  _0 b6 n0 {+ R
    5
    7 x/ N8 b  Q, Qinterval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:" N$ o8 z0 V% g  J8 S0 `
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
    + C; |; _5 U8 D- c" V& LOut[57]:
    ) ]% A( {( _; @. C2 uIntervalIndex([(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]],, G2 b% O/ E% ^8 s/ Y/ j
                  closed='right',
    ( w# r/ C' a$ f9 k" M# c              dtype='interval[float64]')2 g. R* p& P6 K5 f

    5 t9 l0 |7 C; j6 B: n3 k1 R' G1 K3 n9 Ypd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度! c/ R7 A- p, q: {" q3 s
    Out[58]:
    5 B( `  F; A: T2 b8 kIntervalIndex([(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]],/ }( q) P( N- ]% c6 T$ l
                  closed='right',! s$ R  U2 O- \% N2 k2 G
                  dtype='interval[float64]')9 m& \* f! B# d5 ~
    1
    3 k  n$ J- ^# Q/ [; G2. w0 V- l: v3 e& E1 c) {
    3
    3 a, v8 o$ {  W4$ G! n1 B; _+ s) z) W
    53 A; I; j: o2 E7 f
    6
    9 r) R0 T) D0 h% X5 c7
    3 Q# G0 l+ `  X2 }  Y8 x8
    * k5 q* d. D% E; A' Y9% E; v# n) [. U: {
    10) E: I- c2 h: D0 ?
    11
    6 o: l% T( J9 v+ U( p) I【练一练】% a" p; d6 E+ X4 ]# {+ i7 w. b
      无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。
    ) ]" N# }# {& H5 J7 P1 K0 d  v3 r, K( C2 ?5 G. ?& W1 b
      除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
    / A2 n: V/ ^9 }4 M& M8 k# L+ l
    my_interval
    $ p  S; `' N  k0 QOut[59]: Interval(0, 1, closed='right')
    1 V: l3 G% a: i( [
    2 s# T. O9 d* {* a1 }  j& M9 Qmy_interval_2
    1 ~' n: i* Q8 jOut[60]: Interval(0.5, 1.5, closed='left')4 S  K" ~( P; D: q3 L6 H7 a
    & H5 A! ?2 H! F  l. m# y, B
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')8 ]: z* Z* }& A. C2 q; @0 O
    Out[61]: 4 P/ E/ \( ]& E! `5 x* K0 I9 {
    IntervalIndex([[0.0, 1.0), [0.5, 1.5)],. W7 x. Q$ d" I! x8 {& a
                  closed='left',* N* |, c" Y. c& T" y
                  dtype='interval[float64]')
    - M/ J- Y% G5 I8 u6 C" M; h6 h18 o" ~/ @# {) j; V
    2
    6 O* J  T  f; U2 u3" N1 o" x! ]6 D. D1 q" ~9 k+ N
    4
    ' u, w4 O& w( a6 R5
    , ~' c) X. t: [& i; w6
    - F7 h* r4 d  x" ^% W5 v7
    6 W# V' _8 a0 Z& q9 r/ Z( w8+ k# m# W1 ?' c# W9 Y2 D
    9
    ( W0 U# ~" ~) n1 g. V10, N: Y0 L/ z. I3 j; B
    11" l: S$ v; v6 g: ?* m3 p& v
    9.3.3 区间的属性与方法
    3 J7 C) q2 h1 n, O  r5 Q6 ]  IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:* W# g5 H9 i  A: z

    % ]2 O' h6 Q+ Ys=df.Weight
    . ^. K7 Q& t2 f6 V: o  }# q- [/ |id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
    8 p$ R7 `: p) {6 O/ Hid_interval[:3]7 o$ C0 o  i: g' a( {- y+ `

    3 G" R# Y% b1 zIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],$ o/ s0 Y( U$ J8 Q
                     closed='right',* S! S4 k, Z" G1 G% ^
                     name='Weight',
    ) ^0 f0 d/ e6 d0 I  e                 dtype='interval[float64]')5 S. z) y, f, i5 f- Z9 {
    1
    ) |8 {; e$ `  H2 `( C# Y: M3 n# z: y2
    ! ^* S- B+ c8 }1 R3
    7 {0 R+ E1 M" w* M7 o8 W4
    6 E, ^% a4 d4 C* N7 Q" B5
    , y5 [& H& g! w5 n6
    7 d$ a8 Q/ g4 s3 X7
    $ j" L' G2 W8 L+ R' y, _) j8
    3 n5 D/ z" s0 f' ?" ^) m6 J( C! _与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。$ F# L: E* T  j& e. b
    id_demo = id_interval[:5] # 选出前5个展示0 \2 O1 P5 {/ [0 q3 C  V! W  F

    % c, X6 F* F: f$ {3 B( {id_demo( A" r3 J/ ]/ O7 u+ U; j
    Out[64]:
    * z; e% f+ t% S9 Q7 g. mIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    6 p3 E& a+ l: a. _              closed='right',
    ' A1 P7 U  [8 O' ^& z6 S$ }7 f# L              name='Weight',
    + e& R, a) h- e4 H              dtype='interval[float64]')0 P  K/ E7 ]( F( V  O- i& U

    ) u6 N. V: i0 R8 \# f0 i2 e+ Oid_demo.left # 获取这五个区间的左端点
    $ W9 |& f# v; W8 t8 H  j3 N" T. I1 ~Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    + u  m6 b- \; M& C0 u3 f7 o: L. \) Q
    id_demo.right # 获取这五个区间的右端点
    " n- l- E* ]4 ]9 H" iOut[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')% r  g0 ~8 d7 o! U/ u$ X" `

    + @" X. w3 _- P3 U3 j/ b& ^id_demo.mid
    + j) P8 ?9 V% Y- O! A/ o3 TOut[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')
    5 o; q  \2 d5 p& v
    % o$ v* i) g! B3 D# I( c7 kid_demo.length. r- o  r* Y0 m' j+ P& o
    Out[68]: / E  Q' k. p  s0 l5 s) N* m
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,6 Q, z, b0 {4 G7 U
                  18.387999999999998, 18.333],
    * m% J6 ?$ W4 W, b# C             dtype='float64')
    $ [8 p4 `: U6 r
    / r5 y4 V$ P9 t1
    1 h3 J! r* {- d% y5 i( v2
    8 P5 X/ Q" h# L3 D) [0 Z3* j: O! r. D2 U* o6 g9 b8 b
    48 y# s& u# h2 r# H
    57 v& d( W! h  W$ z# O9 S5 {
    6
    / J# [2 d' F3 Z9 f5 N; e  w7
    / [: V7 o7 Q# R8 H8" E. P  k! }/ l' C" O% m) w
    9
    3 c# _. C: O; G! M. c4 _  k10
    & u; J7 m1 Y- `3 E2 S( y11
    0 [. J$ P6 S  ]12# R, E2 j' _& E) {4 J$ a  ]
    13) p/ v& I1 o% H3 }% u/ F2 }0 M
    145 n2 |, _- R. k+ o: H
    15
    " c, ]6 l0 t2 d8 [6 ?( p9 O16. z; [* x# I+ p, S* |5 ^
    177 y& p( l5 q9 J
    18# o2 f: Y/ f- }% Q5 y2 o
    19
    ! \0 Q$ Z' e/ |+ f8 p" j% F1 `201 l5 p* V+ S0 f0 j
    21& N  G5 Y2 y# m4 `- X) F9 o
    221 O2 e" v8 J* J& V. c8 j; I
    23( g! l2 b. `5 B( i$ Q( E4 d1 W8 V
    IntervalIndex还有两个常用方法:
    5 j5 A" r$ w; e4 bcontains:逐个判断每个区间是否包含某元素$ I2 {, n1 Z  r- V+ e, ~1 H5 |' G
    overlaps:是否和一个pd.Interval对象有交集。, Z1 [; q. ?# M7 x7 O9 c
    id_demo.contains(50)% V# B  f! o$ }8 y3 ^  T
    Out[69]: array([ True, False, False,  True, False])
    & O( W8 n4 O  ^: {. ?1 N
    7 S! t8 b0 \6 \) s2 mid_demo.overlaps(pd.Interval(40,60)), ~6 Y* I$ C2 P1 q' @
    Out[70]: array([ True,  True, False,  True, False]); ]" T8 W* A0 `2 V4 V' H, b
    19 L2 o, R$ P9 o3 j! s/ W! O% w
    2& ^; T& ]0 f1 l' d
    38 A5 |) d2 u  \% h
    4
    $ r7 u8 H: z& {. T5% I$ M% S8 }9 O, ]
    9.4 练习  M: K& F$ X0 t/ ]
    Ex1: 统计未出现的类别& W3 Q9 o) O3 L2 v% J7 G, g
      在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:6 k/ Y/ b9 Z' {7 \% Y! H
    8 N8 e9 {/ A* y5 a! H
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']}). m. n* _8 n1 ]* A2 F
    pd.crosstab(df.A, df.B)
    ; ]0 g# }- y# d+ D$ g, S2 Q" c" {( e: I" g/ ?2 W
    Out[72]: 7 l2 L6 t1 i# J, @% `; s
    B  cat  dog
    1 h" b  a: ]# _& OA         
    , A1 L* T% z: fa    2    0
    7 ^+ S. _* w% X8 Pb    1    02 m$ w& K' f4 {% c3 y* o  R
    c    0    1( n# i( T6 Z7 [; V1 m
    1% R# F: A* a1 g& I; e0 v" t6 ^3 I
    2
    9 v0 z* w7 X* s1 @0 ^" M+ K, \3
    ) ?2 k' a3 L  @9 `$ o# N. i( d48 G$ G+ M3 R; U) X- Y
    51 ]4 |2 `4 Z0 ]/ y+ J; f- J) X
    6
    ; O$ {; I( m( Y- z; W7
    ; ?+ [  w4 Y5 q; r80 Y& p5 `! b, l/ F& h
    9
    5 p% w, I4 s" K+ q6 P3 ?  但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:$ @7 f9 Z2 E7 L' F1 \! b

    6 R  `, K, W4 O8 Q- w9 g, y4 Fdf.B = df.B.astype('category').cat.add_categories('sheep')
    6 S  e) m$ C$ N0 H* wpd.crosstab(df.A, df.B, dropna=False)1 H& x5 g) i$ P! ?# L  G3 Z1 W
    4 ~# o3 }9 w0 }  I% \, r
    Out[74]: 5 b. K8 y9 s9 Q# M$ o
    B  cat  dog  sheep
    - S9 D; `2 v8 W. i3 M5 V) vA                 8 p9 `. _3 c% V0 h; n8 f. ^
    a    2    0      0. j* y* _- T- V- n* O$ s4 |* e
    b    1    0      0
    ( O4 [' a  K% H7 |* X: f# Sc    0    1      0
    2 w7 L8 u- o* R0 W1
    ( o, E9 [2 ?+ S/ a& A; r5 }; t2, S: Z  ?0 r6 q4 P
    3- A8 o  \: ]# J' O4 I
    4# E; F& Z4 u3 w! l( ]) n
    5
    0 r; ^. v4 F. n7 s6( V* J( U8 v+ t
    7
    # j3 B6 S& m7 A2 x8 t8
    * G% g( _# C4 Q) J9
    % ?  s* K7 z+ M) M2 m( G& J, U8 M请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。* J# C1 K- T0 G* q; Z+ I

    3 J2 h+ e) t4 D  D( rEx2: 钻石数据集
    8 ^  X  t. {6 F( m! P# E; T( C  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:0 X* w1 p$ O+ K, ^) n& M( ~% e

      n1 i) u0 e8 e+ r3 _% ]df = pd.read_csv('../data/diamonds.csv') " _# O+ P# a* O/ e; p$ M0 N! C. S/ A
    df.head(3)
    ' U8 y+ \* _9 H* b# a
    ' G8 a( y" Q, W0 [8 _" h$ nOut[76]: 6 {" O3 ?5 A1 X$ E0 W/ v0 P3 h
       carat      cut    clarity  price
    ) v9 m. p4 `* j4 \3 R5 }, G0   0.23     Ideal     SI2     326
    " P' T5 R+ o+ G1   0.21    Premium    SI1     326
    # D0 x" ~) Q. D* J2   0.23     Good      VS1     327
    ! Q8 e% A- S7 j) p7 z* @# d. n) G14 i# T, \; j* E$ s* F7 m
    23 W. ?: U" m0 ?6 w
    3
    / U1 j3 q9 [4 N" y, P4
    # B" H# o9 y  J1 A$ Y/ _" M8 r9 D& P5
    : j8 R6 ?. n+ y- F6 ]* }, B6% O: J% g7 q0 _  O  {' {( F* U
    70 ]* m9 Q" }0 g
    86 A3 ~) W0 Q1 b
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    " U' U1 G% U! q; v5 ]; {钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。- P: F3 P4 A2 O+ A4 V* e9 {6 e5 w8 ?2 `
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。8 I) d. v6 G* h7 }1 a  P( i# c  w
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。$ E6 x, Q8 r" }& p  f# n% K. N3 T
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    " e. u+ t2 I. S对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    ! f0 I% A7 f" P" T1 [6 |) i0 }先看看数据结构:
    7 w- u9 I- N: w4 g4 j, ?* t2 [- p7 r; k; |1 ]% X. K
    df.info()/ F* L/ m2 u+ m. j. A7 _# @2 ^
    Data columns (total 4 columns):" ^6 ]' W. k& o) }
    #   Column   Non-Null Count  Dtype  
    " q; w1 q: G5 h. o% {; i---  ------   --------------  -----  , D9 [- J( I: x8 m+ f, t; p; E
    0   carat    53940 non-null  float64
    * k; W+ E) `* p 1   cut      53940 non-null  object 1 h' \$ f0 u& P8 ^) u% E, [! t8 y
    2   clarity  53940 non-null  object
    " Y4 V7 ]4 }' ], f 3   price    53940 non-null  int64  3 W3 p+ x9 I! i- B# K1 f
    dtypes: float64(1), int64(1), object(2)
    ; i% Q: ^+ L. @+ M9 U9 O1
    6 i! R% O* ~; i2 b3 b8 d7 @27 V$ l0 M" m! X: |
    3
    . M9 e2 Q8 A, X0 v# P! u7 p( f* h4
    8 s. u8 E- j0 O0 ]) x9 ~5' ]  B) }. c1 b* {
    6
    * Q; @( y6 D3 N/ |74 L1 M( Y- Q4 a8 B$ e. A
    8
    ! a3 h' k# S, o, x' G* f9 [. d( C98 W5 O# r- P1 g% O; u& |3 k
    比较两种操作的性能
    ! b- A: Z- f) {! S8 ^! _' v3 u! G0 M%time df.cut.unique()/ G( S0 K" D  D" I

    & e4 m0 p+ l( b0 G' H9 X" [Wall time: 5.98 ms/ V! ?9 d- ^+ _9 v/ B
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
    3 ~* j5 T# y: I1: q$ Q) |+ W9 w% f0 q
    2
    / T8 F! U& J, A$ _# i3
    4 k2 J+ y6 v6 s+ y( L' T4
    , S7 Y" }7 D, c( s! c%time df.cut.astype('category').unique()* Z& H3 w6 s7 B# U  ]
    . l. z2 E9 g, \) ^) t
    Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms
    7 c# X+ P" j. R0 |1 u['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']1 D2 [& j7 v- O. p+ a
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']( r* e6 L( W4 `: r* A+ X8 U- }- u
    1' T7 y) l# d3 R$ R. y8 Z) v" Q
    26 U+ Q, g% R- \: Q+ Q) Q
    3
    ) Y5 V9 m2 s% ^# U/ W; {' V4
    1 i3 ^, b; _# _) Q) C: I5
    3 m- g+ X* |* a, Wdf.cut=df.cut.astype('category')
    : m+ M  g/ x$ ?9 l6 R  j%time df.cut.unique() # 类别属性统计,2ms8 @3 V: O9 B3 U! g

    ( _& a' i; w  g# a) S3 NWall time: 2 ms
    $ ]6 U& }! p3 \( a+ B. s- I2 t& j['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    * C& U  f3 ]3 i2 MCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    $ Q5 N9 |  O6 G8 X# d1
    . e5 f) V( [8 }+ O$ ^2
    ; e( u/ F% k" a+ d5 h7 ]  d, z3; Z+ S+ h. E0 y- Y5 V0 o: w
    4/ k2 c! b6 L7 H6 l3 t
    5- N- t' E5 f- c* {" j5 }( W6 a
    6
    + ]: I( ]2 i, _  }对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    8 H/ S3 F% N: i; |' Lls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
    ; g, v1 b1 @8 els_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    4 ]$ D# N6 D( k  e: tdf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换6 o1 h4 P( h0 s2 T0 I1 F0 C
    df.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True): ~! H2 z' m- K1 v( A( x

    0 x  g1 c; f0 \# \df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    5 T/ q; }1 u( ]+ W- J' e: W% J& ?" ^; |% s8 F
            carat         cut        clarity        price
    : @6 g" f8 J7 v315        0.96        Ideal          I1        2801, T+ Y% V0 W; t. _
    535        0.96        Ideal          I1        2826
    " N3 V# t: N/ ?7 c2 U$ S+ i9 m551        0.97        Ideal          I1        2830
    , k4 M* P8 o: n0 @  Z8 ^/ s) t( P19 ~% w9 t7 V' R9 _0 H# ~
    28 K9 j4 b; L$ S  C4 X( L
    3
    / H  g% |, t! c" t0 m' W4
    " e5 m8 l4 \, H/ v/ N5; f, w& p* f' _1 [) q# d$ ?
    6
    9 Q- K6 y. e! }+ }  r9 e1 q$ ~7
    : ]- C. n1 n6 Z- p1 \& l2 o, w4 N8$ b9 ^% i$ S$ D( P0 V
    9
    : g/ Z  s% z5 M) r7 D4 ?105 _; U5 g% c6 V8 R- ^
    11* E6 I! l7 z8 m7 g
    分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。2 p# f* V* K$ x. S# \! S
    # 第一种是将类别重命名为整数
    . H5 W; l9 O5 x* ]* y. \- Mdict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
    % \2 N$ ~2 a% k3 T7 sdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))9 T5 q2 w! J! k: r% f

    ! x+ S1 j+ {% @0 y3 Hdf.cut=df.cut.cat.rename_categories(dict1)
    0 T# K, y5 _2 gdf.clarity=df.clarity.cat.rename_categories(dict2)
      h1 m" l% m/ I6 rdf.head(3)
    * j- J; f, i" ?. t' ^8 i
    . a4 ~( f8 [) h        carat        cut        clarity        price* n6 f, s, p8 z4 p& e6 k
    0        0.23        0          6                3260 i  s4 M; j5 _8 x9 ]% E6 M0 J, _
    1        0.21        1          5                3262 ^0 ~% ^# d/ }9 v$ ]+ a5 w0 t
    2        0.23        3          3                327' k' x! i: i( ^
    1
    ) j5 q7 Y( _/ T2
    : `% o  ?( i" h. c/ G: p3/ y2 H7 f& _( ^/ k  |4 E- G
    48 o! d; {) o  A
    57 F& ]2 J- m: L( b) u9 b  M
    69 v* ]1 N2 }1 s
    7
    7 r- }5 w5 w9 P2 Z, u. u! {8
    % I8 c4 P3 M# V4 r9
    2 u5 T3 y8 i! D10  G( M  r( _) s  M0 E2 T  I
    118 B7 f* l3 R& x
    12
    - K: P$ B- g- k# 第二种应该是报错object属性,然后直接进行替换
    , C* A% E& `' D2 ^. q' Vdf = pd.read_csv('data/diamonds.csv')( L4 `3 j* ^; g0 s5 ~
    for i,j in enumerate(ls_cut[::-1]):
    4 A9 p" x* T! Y% U6 |    df.loc[df.cut==j,'cut']=i ( r8 y2 [3 o9 P5 _, D  ^: x
    " _$ [5 m7 t" m/ }2 u4 f" `
    for k,l in enumerate(ls_clarity[::-1]):
    6 g& b7 @) H- s, v3 H    df.loc[df.clarity==l,'clarity']=k2 k3 _' U: ]" Y  P. J5 H3 C
    df.head(3)
    2 c* T# t6 X2 Z9 n  |
    ; Y3 u4 J/ U! y$ i* Q        carat        cut        clarity        price
    6 j* U7 V- g, |: ]2 r( v1 J0        0.23        0          6                3266 P$ X4 J7 b0 y# M; o* A! b
    1        0.21        1          5                326- P) w1 _( v  \& b9 f- T$ U7 ]3 U* S
    2        0.23        3          3                327
    ' P8 [! N9 u$ f  z$ L1. s' ]3 d3 g9 Y* x6 r
    2
    5 o  ]8 ]# b$ V# g# b- ~) n9 t3 m3: v0 I" J% i9 t( _  W" S: g
    4: O' n( q5 Y" I: [3 b
    5
    5 l3 D4 N- k% E9 r6
    4 P; F& e) ]/ N6 Q, d3 ]76 n" E# f0 T, [$ R5 n
    8# t1 L, ^$ [8 w
    9
    " N- E$ [6 L. e; K3 @10; Z' {0 G4 N* e% d
    11
    4 \' o' w" v: A6 B4 [# ~, m! ~12! P, G1 P, c; I1 V7 s5 P& J* C
    13% d! f' k0 P% k
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。! G, n+ ]. B( a4 s4 M
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
    1 [3 c% [' @" P; `( |avg=df.price/df.carat; }; [2 U) @+ e, ?

    ) V2 `+ L9 n8 w& J, w- idf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    . R3 r0 F9 u2 C6 |9 s; n) L* l                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]. h7 `: g- M8 b  e
    : n2 |1 }# [+ p1 X
    df['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],6 ~# y  e. A; l9 {: ~" F$ B* P$ R5 H5 ?
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]1 [# X6 K; I' X5 L1 R
    df.head()
    0 U3 r5 e' r# G+ S9 U; Q
    + L  {4 t8 C$ {. i2 o1 t* y8 E        carat        cut         clarity        price        price_quantile        price_list
    ) l, t; U8 f/ I" B! e0        0.23        0                6                326                        Very Low                Low
    1 G- u2 S9 O0 ~8 b, X1        0.21        1                5                326                        Very Low                Low
    ; [" b1 Z8 p! S; f2 R$ C2        0.23        3                3                327                        Very Low                Low0 V8 I, Z- Q/ j. Z
    3        0.29        1                4                334                        Very Low                Low2 w, O$ ^, w- i* [$ h3 K. i
    4        0.31        3                6                335                        Very Low                Low                                       : m8 H) C7 ]4 s" s  q7 y6 ?
    5 R% r% Z. o: ~. n& a& P
    15 J7 E+ Z2 I$ ]' X& A# X1 {2 ]" p/ P1 z3 n
    2
    * ]5 A, W+ c7 B( \2 j34 F# l9 `7 @& ^, v! d6 e( a
    4
    1 v! U  Q( m7 |1 U6 z% S5* x' ?" n& S9 z' o7 u
    6
    * u4 ~( C% [  F2 P! B4 N7! R6 a+ Z' Z2 h* o" b
    8+ @2 t3 N  P" I" A2 [% y
    9
    ) ^" }& Y- s% `' v5 r10) s6 p8 v( v% {4 K
    116 }; r( J1 W! p' o8 ^, R
    12, u0 ~' K6 B( D9 p9 a% x4 N" c
    139 Y% Y' {/ G- Y) s4 H
    14
    ( u0 D$ S* J9 X" I% }15( [& H2 M+ h! F. s$ T7 L2 U
    16
    , U1 N# J4 E8 z% R9 G: n分割点分别是:2 h9 V8 \& f# J+ _5 U9 |& E

    # ]. Q% I' o/ o# T( `# Iarray([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])+ o  D7 o; Q1 z9 o
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])
    1 X( g1 V/ F! \7 a3 a. w18 T8 [5 a1 N( l% g: f: h, J8 a4 D5 f( [
    2* P. m& Z! `) Y" S5 M. \7 N
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    * ^& V9 z, u; l6 i7 H6 _df['price_list'].cat.categories # 原先设定的类别数
    : L8 H" i( Z) n. b2 `6 HIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')2 z8 U$ \" h. c; j( P; J$ i5 O

    , j# N2 e$ l1 t7 {' p2 L0 y& Q. ^df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别
    6 i! ?2 x! z; d' B& FIndex(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现$ h5 S* G4 {* J6 V" _5 S
    1
    # S# F% E) z' a# O* Y1 H' T2$ q5 D2 m! D9 G# q9 h; M
    3/ k9 r7 F, t( ]2 Y
    4/ e% z1 w' p2 I2 b' k  s" J0 V9 |' `
    5+ P' F# @, Z6 o! C" f, K
    avg.sort_values() # 可见首尾区间确实是没有的
    : ~7 k' n% Z5 [! Q) U) x' s3 m31962     1051.1627919 ]/ P; A* r7 j) n) j/ K5 G
    15        1078.125000# @" E! }, x) z4 D$ h
    4         1080.645161
    / p3 L$ _; x/ m28285     1109.090909
    9 K) b& B. N  Q8 p$ v0 H13        1109.677419
    3 K6 Q, O, L7 Y/ m# b$ H% I             ...     $ q+ y, o6 ]& c9 ]; P) Z2 J
    26998    16764.705882/ W% Z6 Q: P# F
    27457    16928.971963
    3 R, ^* q; g% b! o5 ]27226    17077.6699037 N7 f% p% `# [; T
    27530    17083.177570+ H3 S8 d5 ~' J' J6 |0 m
    27635    17828.846154
    9 {$ I1 b- n- F7 R" u4 M" r1
    . I3 M0 G) b$ i( O2
    ! w+ F; F  \, q1 P35 k" Y5 `0 a% W9 z" R
    4! x) Q7 x& C5 y; S$ B1 x7 A6 t! V
    5' T2 v3 X% e* [4 p8 U
    6
    / F! [( i' P' L5 t8 `" Q0 a" a' G79 X5 a% R  [: \7 P2 B
    8
    ; @& C  k- P( M  o) l9! f5 M6 o: Z* ?1 a6 ]. \
    10
    2 U9 F" F1 ^  g5 A' m11
    9 l  j3 X" r9 v# i125 g& |$ v! L! N
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    0 H! n- ^& o+ `6 b# A# 分割时区间不能有命名,否则字符串传入错误。3 V9 m5 P1 |" Z. Q
    id_interval=pd.IntervalIndex(
    $ i5 i4 f$ g3 h- W9 W    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]# j" L8 h2 d. q1 E9 _
                                )4 s9 \; F+ e  ?6 ]6 b
    id_interval.left
    7 H+ N: a2 |' Lid_interval.right3 j4 M! b( H( N9 f; Q# O/ h
    id_interval.length                           
    8 q) S4 V- h' j/ A% l" `4 R1
    * @+ e; C' S3 U. S( Y/ I2! B, ~% P7 L& N  H3 _: w1 x- O
    3
    & }' M* H. O+ f' q0 o4& M8 S: S% @4 d: ^8 W
    5) }/ @# O! G  K& g$ E
    6
    7 H# j7 m9 |' Q- Z+ k" T5 }4 ~7
    ( J% |* _2 a0 |" ?% i第十章 时序数据
    8 E; O; [$ N8 _import numpy as np  A) U5 V+ j/ [+ b: l- A
    import pandas as pd
    9 r9 A1 |1 X% G# B1 a, \19 g# \( X8 S0 ^' g
    2
    # r: g6 {; r! u+ Y; H3 V. k, k; M' v$ ~7 a
    2 D- _- v  c) f; ?  A) D$ e
    10.1 时序中的基本对象; E8 o1 i% z: H* E
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?1 s6 D) s4 V# M, _4 L

    3 p; [# d$ ^' N1 `会出现时间戳(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的简写。
    . I4 a% F: ?% f+ J- M7 a
    . ~$ r7 r3 R) \  L/ P6 O" _/ t! c会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。' z3 M8 W' z6 L- m/ T

    4 Z* q: X5 X& a( P! m会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。9 `/ G1 I. E- k( m
    5 _. R) A" r( C% b9 z: \) h' k
    会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。
    + ?" ]2 ?$ Q! O2 I1 E6 x
    4 E! A9 o7 P' _! q% j  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:5 _+ \. P( n* m8 w, U

      i5 ?4 }8 T' X5 D概念        单元素类型        数组类型        pandas数据类型
    6 l. N; q8 J6 w1 I$ D7 c7 t+ gDate times        Timestamp        DatetimeIndex        datetime64[ns]
    & i, Y  W) h, K* T6 J( c/ T: {Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    3 ^% A2 m! Z! X. n* a0 A2 nTime spans        Period        PeriodIndex        period[freq]
    - h, ~. M$ }$ x3 e8 `4 e& tDate offsets        DateOffset        None        None
    " y; d4 A/ N" ]; p  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。
    & M' f" M! t( {# ]# `5 X: H& \/ g. ?. z. Y: o/ V
    10.2 时间戳
    + V, j7 b  V, i7 J10.2.1 Timestamp的构造与属性% c, W, i- I  b
    单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:( e, m$ A5 X  b9 u
    # p* m* b0 o  ?4 D
    ts = pd.Timestamp('2020/1/1')" S' p$ V" d1 ]6 C+ z
    , Y9 H% K$ H. z/ r8 T; ^+ i
    ts, g5 S' S, N# V8 L- ?. @; ^
    Out[4]: Timestamp('2020-01-01 00:00:00'). }- g0 u% ^" ~! H1 `9 B+ C

    , d+ d, Q9 C7 l( F# bts = pd.Timestamp('2020-1-1 08:10:30')
    + a* R# ]5 n* _4 S; ?; b/ {) ~( t  J8 J' X
    ts
    / _, k4 e1 s  O- F4 }Out[6]: Timestamp('2020-01-01 08:10:30')
    / x* F2 |( |: e" L1" G  C8 B+ Q2 n: P# M
    2
    . U, E7 y7 [! ~& |3
    ) g  f5 j. T1 R4
    1 n" l* B1 k- }  P8 e- n" W* N4 ?2 r7 q5
    : U( Q% Z2 T. y& G: B6! S( y# ~( @" B1 W1 S0 k
    7. w8 G6 n8 v, N) T; G
    8
    . w4 \/ l+ C0 u$ L% m) G" r& B9
    % e" e$ n8 v4 d1 X7 m通过year, month, day, hour, min, second可以获取具体的数值:$ D- q# s3 `0 q  D" o

    / y7 ?& S9 U7 @; y: v0 _! Ots.year
    9 [/ Z2 D4 o; }% ZOut[7]: 2020
    / k6 q' W; n, c- @& i* g8 Y% G4 @- e/ V$ ~. b4 V  w, z
    ts.month( o8 {  e0 V0 `1 C+ |) n
    Out[8]: 15 N: F8 b! \/ _+ s9 F2 {4 I0 J

    1 \% a* k8 V" ]ts.day
    $ i1 B+ }7 j2 b! o0 d: G8 @Out[9]: 1/ z" K) y, W; {7 B

    . O' i3 L8 q5 V9 T  J5 S0 ~" ~ts.hour
    # M  u" R: D# q5 \5 IOut[10]: 8
    # G, t, s) h5 L8 h# v( e, F1 S) i
    0 \- C) h5 d' ?$ pts.minute, U3 O  C; s7 M0 ?: z/ I
    Out[11]: 104 J- V8 R: f; a5 x% }- g

    ( l+ z4 e' ?$ j7 R5 A5 N0 J+ l. _ts.second
    6 E( D5 E8 t+ j5 t% LOut[12]: 30* y/ }( h. x1 ^6 h- x
    2 U1 U+ B3 m, K2 U2 T5 z7 W' R  |
    1  d6 p3 K7 m, `% @$ z" x
    2$ l: ?0 Y  p  s! t+ |( H! T
    3
    / R6 q  \% M7 C# L  U, g+ ~47 i! x1 Y/ h' o/ B" Z7 ^
    5& q( |1 p2 o. d. _. v" J7 v! `
    6( N, [6 }' E. M& _  R4 v
    7
    1 b; Q6 g2 j4 s' y0 q1 H+ ~8
    ! f8 ?( b; v. U9& D8 S0 q1 X; r/ F
    10
    7 L3 N% }- w9 P& E1 \119 m2 s% {( C# O$ `0 B" @# n$ k  S3 c, |
    12% S8 a! K6 t9 S3 W
    131 M8 q. S6 s. Y/ \3 N7 Y2 f
    14  z) U. W% j- P% ]8 }/ z
    15
    9 Q' f: X( M* T16
    3 b& i6 b# J( F179 B3 _) G# F/ r* R7 K, _* j5 G
    # 获取当前时间5 ]' j0 h# X' r/ q) ?' I
    now=pd.Timestamp.now()3 T) D& R: H# T, e- E) C
    1
    / Z- B& C1 v8 }, [5 u% _. h: }2, k, Y2 S( D+ D
    在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    1 s3 g+ ~: c/ mT 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)
    4 j# N; W8 ]* S5 DTimeRange= 5 j0 e, M7 c) g# a# E6 e' Z" ?
    10 4 G- [8 ~/ ~5 n' z+ ?- K" B# y
    9! j1 h# ^, k& a9 R( q
    ×60×60×24×3650 i$ U% N/ o7 E
    2 " x0 b7 F' h* c, q# L
    64
    1 J$ s' [8 c/ m6 _4 K: W8 N/ U3 F  @9 @0 V% L8 {) z) R
    ) p- L7 P3 P8 ]
    ≈585(Years)0 w+ @3 E. E! t

    , k# n& e3 f: t: t  n4 I  v通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    4 J% @7 Q. F5 |: |% m. r# P& D/ T) \; B; W/ ~: P( a
    pd.Timestamp.max
    2 Q$ A2 g! j8 Y. v# \% O2 f, cOut[13]: Timestamp('2262-04-11 23:47:16.854775807'): O# g  E& O" M/ G; C! r
    : \+ T. R7 ]0 g" W. a* b
    pd.Timestamp.min* \' F( R& ~8 h+ l6 o
    Out[14]: Timestamp('1677-09-21 00:12:43.145225')
    9 t: e) l1 E; |4 P; G3 U
    2 ~8 c% _7 r0 Npd.Timestamp.max.year - pd.Timestamp.min.year; C( B3 A' w; Y( U. k7 M8 z8 c
    Out[15]: 585
    9 D9 F9 J; `( ?- O- j3 R* @; f1
    4 k( p/ f2 n  E" [; N) \3 {; q29 x2 V  u; X, x  l6 A1 ?+ b
    3
    ! P- v4 q# C# }/ g4
    + B+ n8 {$ s+ \% v5 d5
    ! S  a* u% s) Z9 Q6
    * Y1 a. S! ~2 ?, S& s/ R+ V7
    , q' n9 u1 X4 M- m- v8+ `% V6 Q8 c: Y
    10.2.2 Datetime序列的生成
    . @* ?0 U) v3 J. ?- K% L. M9 u0 W  Vpandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None," r# [* R1 M5 \/ l7 ~9 U7 I, z
                                      exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)3 O+ z" ~( W$ N0 Q9 H
    1
    - P9 d( F4 q  \; L+ A( S2
    7 L1 j+ F2 q  \, N! N; n1 Y% epandas.to_datetime将arg转换为日期时间。! \5 ?. t% w6 O; i# b6 R8 P

    # K. H& _+ _+ E8 D2 Warg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
    & F2 x1 {" |7 m# l  W* _0 ^errors:
    8 N& \5 F5 h8 h4 a: f7 L8 a! f- ‘raise’:默认值,无效解析将引发异常
    " d! @1 Q4 n) ]6 l+ B7 Z' s0 F. `- ‘raise’:无效解析将返回输入/ S1 s7 R  D5 {4 Z" o
    - ‘coerce’:无效解析将被设置为NaT' z+ `- }% ^6 b9 l( I! ?# E
    dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。, q5 E1 E4 w, l. w$ O7 l% y
    yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
    8 {6 m: q3 r. O9 uutcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档4 J' @# A0 c3 x
    format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
    % u+ o6 [8 ?2 m9 u$ g% Q; Ounitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。2 `' [  M& c; Z; @* T
    to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
    9 h6 y# x, H; _6 E' U+ N2 [# `* Tpd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6']), S' m" g. V9 A' T8 N# d9 M, |
    ( e: W. n0 |' Y9 S
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)# w8 _3 v& R- y
    1  u' F! J$ d0 Z1 |! T7 a5 w) p
    2( }+ l# e1 b8 D4 L2 j- {9 `' `' _7 g0 c
    3# a: b4 M/ v! M$ w
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:" B' g' w' Z+ G- O) X
      n& ~3 y4 @3 |% M
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')& a: N8 v4 j8 O! T; l
    temp- V8 A8 ^# t$ |! t9 |2 p
    3 y3 X8 e/ I, t: G
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)8 |4 @  `" R1 o- g/ Q5 _9 r
    1/ X/ ^% x6 F$ B3 y5 x; z+ H
    28 c+ V: ^6 A6 Q) d
    3- g' a2 u2 T1 z3 ^5 l; h" d
    4% v( g0 ~' W$ c5 S' l/ O- s0 }
      注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
    1 \, ~2 b0 A' D: R  Z
    ! n+ @* V; @4 wpd.Series(temp).head()
    7 |! s/ l% S5 u3 X% s8 Z9 K' q; |0 h7 Z# Q
    0   2020-01-019 n8 v0 I8 _, F$ D
    1   2020-01-03. m% ]6 s. K  c* V7 S( Q! ?. k
    dtype: datetime64[ns]1 K. e& Q* I9 z$ }
    13 B2 r* R/ _9 J8 c- ^6 P
    2
    ( a- D% X" Z2 T# w  s4 Y3
    3 N, o8 v8 {: b! N2 `41 u5 c6 o$ b4 K
    5" E% a- I0 o8 Q. H
    下面的序列本身就是Series,所以不需要再转化。
    ' |, ?, m0 t. y" l; e( A
      e1 C5 N2 C' f2 z# L  odf = pd.read_csv('../data/learn_pandas.csv')2 g$ E$ x4 e( ^& C9 V& K
    s = pd.to_datetime(df.Test_Date)+ O. y8 y' l: Q, I! B
    s.head()
    7 p6 T2 ]/ a/ Q8 X2 y
    6 g& f3 k1 f6 q, J2 x1 y$ b3 Z0   2019-10-059 g# O! C/ U# d: M( a! _+ z/ X* r
    1   2019-09-04
    / Z" I: B! O: T( c2   2019-09-12" u- f; v3 o; j1 A8 ^  S! ^! p
    3   2020-01-03
    ; e3 L& i  I0 L4   2019-11-06; @  m1 Q4 F9 _. E
    Name: Test_Date, dtype: datetime64[ns]
    / }* A0 c9 V# X; W1
    6 s% w5 ?1 C4 `/ Y; A+ y2
    $ X. G6 G/ N0 k4 r( D3' U6 \, t% b, U- j/ D! Z. w$ c; B8 v" M' ~
    4( K7 o9 C) }# m. {2 ~
    53 z) y1 D+ d) e2 @0 D$ |$ N) c
    65 m& g) o; Z7 r# N1 D
    7+ ]4 D1 k. t( j" S* S) J
    8" `* {6 Y. B3 k2 ~
    9& `" D# Q  M, d& X# T3 w
    10
      V' }- o7 A) a2 d: T4 l把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:7 y- x3 s/ `; Y+ y8 V
    df_date_cols = pd.DataFrame({'year': [2020, 2020],
    % A  x" T1 P8 I' w                             'month': [1, 1],
    ( O: g* C/ i1 }& I8 N6 X                             'day': [1, 2],& w0 E' G* ~' \; w0 u' D
                                 'hour': [10, 20],
    ) U; f7 m" ]- E: H                             'minute': [30, 50],
    ( L  w; ]! G$ }% `# ^                             'second': [20, 40]})$ W" y1 o. F% I2 I
    pd.to_datetime(df_date_cols)
    + ^$ p- l5 t- W5 u, R2 d. u* n! G
    $ }& |& j: [$ l2 r: e" R0   2020-01-01 10:30:20& i% G# ?" L/ c. m6 B5 @6 I
    1   2020-01-02 20:50:40
      `; t- v1 B1 A4 o9 bdtype: datetime64[ns]9 G$ U' K! Q5 t3 S6 q6 V, B  t
    12 z( H! L: n, f6 g
    2% L" `# Z$ @3 F7 v0 m7 F9 v4 e( M8 }
    3, g* t8 D4 X2 n/ F  }
    4
    % w' K& G: u/ H# {, F8 c9 H! F. |( l0 m5
    / |3 e2 ~+ Y: T# Q; }6$ K) {8 Z& v1 w8 l- O' J
    7" U, a$ M7 E% J$ u
    8
    / T( J' L0 j- P6 r9
    ) y; T; e1 M0 P. p" G# A8 B' H10
    * u3 U5 z$ s% v7 p6 _1 j$ u118 C$ S  P# i$ t" F  Z: I  Z8 ^
    date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    6 @. B" \+ F7 Z) gpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含$ U* `8 I. \( Y; O) m
    Out[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    9 t0 M( X( W/ C9 g, U3 o9 }! a3 Q3 a/ r3 f& ~. j5 j( i* }: n5 @
    pd.date_range('2020-1-1','2020-2-28', freq='10D')4 O8 v" I5 b! F! _+ O* M' J
    Out[26]:
    5 {; S/ B) X: Z) QDatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
    - M( E; Z5 j+ C* P               '2020-02-10', '2020-02-20'],
    % @$ c! @6 t: ^: `% ^& V  a/ P! n              dtype='datetime64[ns]', freq='10D')
    * E" L- U+ |3 q) h3 z1 K# R% [' R. Y1 q/ F& I: `  P2 K
    pd.date_range('2020-1-1',
    ( r# l  F# I9 j: }* i0 }$ n              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
    1 g# k( D7 K" U' s  J, A4 |% o  {4 g) N1 ^- d3 V2 N9 A" g! Z' `
    Out[27]: & a( u, h* _. q2 @7 E
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',- ~, M3 k' f9 o2 q& E+ V, L
                   '2020-01-24 04:48:00', '2020-02-04 19:12:00',
    - R# s4 C; w8 d/ I$ H& E               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],0 z1 b1 l/ X/ ~* V. P
                  dtype='datetime64[ns]', freq=None)
    " M+ I; }2 ?6 {9 Q1 q; v1 w  ^4 h; C! l
    5 M! q; `  k+ ^1
    * x0 w7 q8 b7 r6 q9 b) Z2
    & p4 e4 u; v9 t, y# g) ^3
    " v- m( G) x6 ?% Z% j( {$ r4
    ) |; s& S" l5 Q+ l, d5
    1 Y' x0 j, Y2 O69 J( }8 N) Z5 B) D# D* f
    7, u- H" U6 k2 V$ m, Y3 R+ F
    8' f. s4 D7 f" g  H! J% H
    9, ^; |' u  v7 n; v) O
    10) O$ t& K0 Y7 r3 V4 X) ?4 J; m+ q1 O
    119 u5 Z0 y7 A: l
    12- D2 F: \* v6 L* u
    13
    ! r" Q6 p. M. {2 o3 t% e% z  l14
    1 w  Y! M" _& z# r; S15
    ! w) Y+ G* c( v+ l# w+ \- R161 ?6 d6 F% X( z. O% \! c& r
    17& S! p, [% u& I7 e2 X/ f- p3 N
    这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
    ) P9 G9 Q1 n8 n5 Y9 p- L1 ?: G3 i3 x6 g
    . ]& \; C6 k0 {0 `【练一练】6 y" @, I, Q9 |$ E% c7 B! s
    Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。  K- s0 }8 Z3 E, c/ Z# U/ q/ ~
    4 t7 X2 r- W7 c) v; M& u
    ls=['2020-01-01','2020-02-20']
    . D' c- `; c$ [- H. J  y2 b* Ddef dates(ls,n):
    " e0 e! Q' r2 w- I. ?+ A) O    min=pd.Timestamp(ls[0]).value/10**9
    & z  R; Z% [# Y  M3 r9 ]# ~    max=pd.Timestamp(ls[1]).value/10**9
    4 M% ]$ b( }' u+ u5 y. {& O$ B  Y4 F, z    times=np.random.randint(min,max+1,n), j+ ?6 B* t0 X6 y
        return  pd.to_datetime(times,unit='s')# R3 y/ w; q; B' _- r. y
    dates(ls,10) 2 {& j3 s$ U  v4 D; A8 I* X8 ^
    4 j- G6 G* [5 {" k
    DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',
    + F( P( @0 ]: Q8 _7 {0 Y               '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    % n& B; S: m- X! U; H6 }' g               '2020-02-15 00:18:33', '2020-02-11 02:18:07',
    . }2 w9 Z% R' r/ i               '2020-01-12 21:48:59', '2020-01-12 00:39:24',
    9 \' a* |  ?& _               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],4 V+ Y7 T1 E7 `  y6 N& H5 l7 C' w
                  dtype='datetime64[ns]', freq=None). Z/ O7 C  N( h+ F& k) X3 s
    1
    ) \+ N9 T; R0 e/ R9 O# H2
    " ]1 u! |& w% q; ~3
    5 m# B  K, n$ B- G8 A4
    / a, n' X/ [0 y' H' \. q" d5# g2 [# F9 O+ b; _. z0 H
    60 @# R' c, Y) Z
    7& ~; ]5 N. i0 u4 i8 F
    8
      W. @! {3 K- m; Z9% a3 J1 d) |% a0 V7 M* {
    10, b, ?, p, c! @' X6 d  F
    11. Q+ d& m/ q0 c
    124 Q) N6 i: G+ P- I6 \
    13' G0 l3 z+ e9 ^7 e, Z& j& b2 g( f$ I
    14
    ' F8 D) F3 ]. S( p2 `$ k; G$ Yasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:! R* M0 t" [, `8 q2 g; Z
    s = pd.Series(np.random.rand(5),
    : H* c( v7 \, d6 j. O5 b7 v            index=pd.to_datetime([
    . b/ ^* @0 e' b) U' O' J                '2020-1-%d'%i for i in range(1,10,2)])), x2 I, r0 q( J$ z+ i

    . T: O; x2 `( M. F" q9 S3 X0 {5 r; i- S; W) i7 {
    s.head()" q8 m* N  k- U' `
    Out[29]: 8 X7 R& b& j5 z5 d' O: }
    2020-01-01    0.836578
    ! J- z$ Q5 Y& e2020-01-03    0.678419
    % U* ~+ q* \1 I3 @7 D2020-01-05    0.711897
    4 G+ ~; G- c0 u5 K! p9 t4 n& \2020-01-07    0.487429
    ; a8 X$ b0 M+ k% \2 U2020-01-09    0.604705
    / K* R. n  O2 ^& i" p  Mdtype: float64( f( r! [' x* n
    1 z# k1 `$ ]! {
    s.asfreq('D').head()& i& N$ m6 P& f6 p7 Z; O
    Out[30]: 2 H% H% D2 M) ~7 ]" Q
    2020-01-01    0.836578" W$ b6 c2 V9 c- y( w/ f. y
    2020-01-02         NaN, ]  A1 h. U, }( ~' w3 b. R2 J2 i- d
    2020-01-03    0.678419, T* \) w; f& C0 j: M; v; C
    2020-01-04         NaN! C) o0 o4 _  R% b4 y
    2020-01-05    0.7118974 b- R2 V2 P( j& R5 a
    Freq: D, dtype: float64, J# E' R' w; l- P

    3 X1 q" L1 ^! }; Ts.asfreq('12H').head()
    ; ?. r; G' I& B7 M7 y' w4 L1 @Out[31]:
    $ e4 F! C. k8 I# S2020-01-01 00:00:00    0.836578  Q( M$ n5 X( w7 _5 V, T" P
    2020-01-01 12:00:00         NaN
    ; q/ @1 @2 D  ]4 A: @5 u2020-01-02 00:00:00         NaN5 _7 ]) P% _; `/ v8 z# y5 w, R8 |$ e
    2020-01-02 12:00:00         NaN
    ' b8 I5 s9 @: [5 J7 I2020-01-03 00:00:00    0.678419( e8 u1 G$ T* y) `9 P- B  l
    Freq: 12H, dtype: float64  f, D% {( a+ C) T

    0 w9 I1 b! H9 L5 g4 e' o3 n1! Q. L' F! Q6 {# {% d
    2! b: ]- n: E' _! M2 k
    3. _3 @" k" {6 U; F
    4+ h+ [) I7 E1 _' ?
    5
    8 Y  x& J; I4 l. e8 o6
    + u: b9 V8 }8 `6 C) _7& Z, v# ]/ U. J1 `2 h( u" ~
    8$ d9 \0 h) I, l4 s
    9
    ( |5 k- M" E( V8 ^# D; l( w10
    + `$ h$ T( I$ t5 `3 Z' l* D' G11" O, z3 \9 k+ j" r' j* _, F
    12
    7 {0 v& N% }' M1 K3 J+ D+ o6 q# j134 C0 Z+ Z  k- T. o
    142 x) d* j/ a4 [# V
    15  d# K7 G" P2 S! `; y
    16  e# }, B8 d: {8 h% i; h
    17
    2 {; d3 D! y: _+ k2 v! c- j1 W18
    ' m; x1 ]+ l9 S3 \- N19" f; B5 k" h* q- c& n" `
    207 A- k$ F/ l( W* ~& k: M" ?
    21
      W% R6 {  l" f% C, e22
    4 E' s* S" A4 a  I23) t+ e) Y" Q1 c$ l# ~  v
    24
    : p/ e  o* t' W25' B0 ~# g* v( M
    26! I# p5 b7 H9 Y
    27
    ( k, E6 \" V/ y2 ?# ^28( R5 X. |" H( T, R5 o! n
    29
    $ l6 C$ b5 K! f# _/ Q# S5 u30
    3 c# s0 y' k0 F6 O& Z, _" r3 j; }31
    : D3 c6 {5 [  x( p* |/ H: K【NOTE】datetime64[ns] 序列的极值与均值
    $ @* H; e6 s  L  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
    + R# J+ e1 e7 t+ K
    * @/ }# N, U1 Z5 H& ~# C10.2.3 dt对象
    8 G' Z7 R7 M7 S" m' R  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。5 _% {3 z6 I1 u- N8 J% M

    . K0 U+ L4 c2 x# }% w5 @# y  }第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。8 m8 C3 }- E! {6 B& F, J9 ]; d
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
    - }" s' u# W( g2 Q) y2 u5 J9 G: I
    ! U6 ^  L( A$ H0 l/ `  Vs.dt.date8 A. n" D; W! I5 d
    Out[33]:
    - l4 T. X* w. y# d& `/ M0    2020-01-01; y6 @" C) a( Z$ n, ?
    1    2020-01-02
    3 Z* p- v# O9 s2    2020-01-03. Z! y6 ~+ }4 R+ g; t1 _/ P( v
    dtype: object( K  e3 U8 h, V8 k! n1 R4 L4 n# s

    0 h. n# ]0 H9 P3 _* g8 r* ss.dt.time$ N" }* y6 O$ @" `1 M# ]& m+ j' H
    Out[34]:
    8 v  }9 k: ?$ E& I3 A5 p  t0    00:00:008 j0 O3 d7 [; @* k. w. K: N! O2 s
    1    00:00:00" b. \6 ]- w1 |7 s
    2    00:00:00
      q! K8 R1 S3 E- P1 ^! ?, J8 w7 ]( {dtype: object
    1 N. X4 Y3 \6 K% j0 I* ^) b
    # n$ w  F! I/ C1 {s.dt.day. g* e' C4 y  p" f' M
    Out[35]: # k0 g/ s5 z7 b: M6 S" Q
    0    1# j. V2 q0 m; b4 R& a
    1    29 ^, \- n- X0 s7 Q7 L3 N! v( r
    2    3
    " b0 d5 G+ E# Q9 a$ o% F: }" B5 rdtype: int64* M  z; |/ N, o4 r% \3 B1 I8 d
    % d5 e  I1 f8 G7 R* U2 H& ~& @
    s.dt.daysinmonth
    1 q; M, u9 U: J2 ]% e5 H$ g" ?2 {Out[36]: 9 V+ o# ?( a# _5 W* Z2 A; i/ v
    0    31
    ' v/ f, z7 H8 W7 M3 k1    31
    ( `* D" ?5 r5 w1 B4 o6 k3 A2    31( ?- M" Y' p! K
    dtype: int64
    0 ^/ [3 R6 s" Q/ k5 t5 t9 x/ g5 [) C5 m+ O
    1& z4 {2 c9 N* f
    26 m) b9 ~3 q) Q& x* ]6 ]' r
    3
    ( S; H6 D2 Y. r$ q; U40 N( L0 h( ]# f$ W* }- r( |
    5+ J+ z& X' L- j) x5 b4 _
    6
    / s4 J% S0 v. C# }* I7
    ! g- e- T6 h$ H6 L) e/ S+ I8
    : E! T5 j1 Y/ M- ~6 V" m9
    8 O# @- m6 o: c108 p9 h! b+ o: x7 v4 Z1 G
    11
    # f" Q" B7 o' T* D) J( ?/ S2 E12& l  ]$ H5 K5 y  j- I- K! w
    13
    . i" S. o9 i! O8 X, F, J14
    " l/ {: q4 I+ v9 e: Z& x15
    ' u5 G( H5 K* x% m* o% s16& ]. s. h$ e5 r7 j1 _
    17
      a, S3 ^3 X+ T" ]% Z- b18
    + E3 w* T" q1 m8 L19
    0 _# t7 n- H; t* `+ x. z20
    7 x' j. o; e! I% ?" c21
    % Q8 e2 q2 w# B4 w- U. G22$ t2 G$ U8 z4 l+ s7 r1 b2 B
    23# `, q& p) ]6 [* m- `# u, x* I
    24
    * i9 H& z8 e1 x  \25
    7 i2 M3 u$ H4 V( B* t3 F+ g" ]+ a26/ V! k3 |1 x2 P2 l. S" I: Y
    27% A3 l, x$ I5 l% b* x: ~& G; D
    283 o4 C2 f; j3 n, a: w: |9 h- r
    29
    - H7 Z8 g# D- j- v+ k' a+ `" ^/ h  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:
    ; L4 X0 L# o# [# G2 p( [
    3 f( s- L* q; t' L  m4 V+ ~$ _0 Qs.dt.dayofweek
    2 S! i5 u* k( G6 LOut[37]: : G2 ?; O$ Q  l. _: W, n& R3 o
    0    2
    6 u. }* h: ?" a; z# t1    38 z$ r* Y! C5 x
    2    4
    + P4 o: f8 ^* t5 d; ]dtype: int646 y( A/ D' q# ^4 m. V
    7 m- I# H( G3 V" F% S7 E7 X
    s.dt.month_name()
    7 K: Q2 ^% L; a& W) _Out[38]:   z0 g+ j4 p; w- R
    0    January4 v4 k3 @1 a; M, l/ y
    1    January
    1 p, s% R( `" ?+ f. m) q  Q) x5 i2    January4 T3 q$ }* J: n
    dtype: object9 \  m0 O' d3 y( f
    + y/ u- U% c1 [
    s.dt.day_name()
    7 d6 S, v8 ^  w& B/ t0 G* p) @* {Out[39]:
    , B* H- l( K# Q" n0    Wednesday( k* q9 O( I, P! ]8 `6 I8 u
    1     Thursday
    # ~3 M* U! @6 J* m4 {' J* t+ K! \2       Friday# t) a3 M  W5 _0 g( I- d
    dtype: object$ l8 ~* V6 A1 a" s3 x5 x2 a

    4 z6 {9 D! E' V/ B9 k15 J) W" l4 s. Q1 r/ q! ^" s
    2
    & [' n9 U7 M4 W$ m6 |6 X' W( w: G3; \. X  Z; V4 ^% V! q9 i0 X
    4
    : Y: w0 g: M0 {- w- }) }1 A0 k. X5/ J) \5 d# a  I
    6
    " \9 F% [* f2 z& f  V: R- F( ~( a7- q2 P' Q$ w2 d
    8
    " q& v& k6 d/ b' q2 N& h9
    , c9 ?3 @* t/ {' H+ o% l: T10' T0 T6 v. e$ r" l& y* u
    11
    8 A7 f: ?' }3 x12" u8 e* d% |# p  d$ y/ _: u9 \
    13
    " A# H, t. a2 f5 N+ M3 L0 L14
    ( Y+ t1 L8 ^$ |154 J  Z9 t9 l/ {: s5 i" Y
    16
    % W% }  Y. ?+ w8 ^. m- }9 I173 A- a# z; t) l/ j5 L  i8 c
    18
    8 T9 l/ h3 t6 y) R19; ~/ a& e& Z& R. Y0 |) S; A/ T
    20" I$ M/ S  c' Z% E: ~4 I, e, R7 D1 ?/ ~
    第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
    ) e# C! z5 z/ w5 u3 r7 Ts.dt.is_year_start # 还可选 is_quarter/month_start) o! k, I$ X, f9 f
    Out[40]: - W* x# v, H$ K4 t
    0     True
    / X% i' @7 U& m& p7 V/ T1    False' L( G  ?2 \; y5 Y* D- I1 V
    2    False. X; U& \( S' E1 |. ?( ?4 s
    dtype: bool5 l3 j" R' I& e3 H& i
    ' l4 Q" G* {5 A0 h6 N
    s.dt.is_year_end # 还可选 is_quarter/month_end
    : a4 n% r: B1 p! A6 x, tOut[41]: 1 }4 u# g5 X  Y  F
    0    False! r  N6 F/ D9 `0 Y0 B/ g6 p2 D# l
    1    False+ e. U3 @& [! M) F9 e
    2    False, J: V, k. N6 v$ l( N6 w
    dtype: bool
    1 A7 s% M* j+ M0 }* h1
    7 a4 }$ T) \5 t: f- F2. E; b9 G/ @% ~5 `' u
    3/ X! n: ], s" c; c  ~4 _
    46 x/ x) b' i4 ~. s
    5: \8 i4 ~9 |& X# ~: j$ u; W
    6) q6 r' `4 ~( U1 [/ G' ~8 q- C: B7 O
    7$ H0 D1 I  B% L' P2 P, d1 r' Y
    8
    : w( ^3 i: t, t- ]' z- ]2 g# `! X$ v9
    ( Z, J. b- w) f" |" z10
      R/ w5 ]0 M, P" T1 ]/ W+ F8 J11
    3 g: ?% R2 e- n. V12
    ) q9 y) N1 `" s13
    ) I& s1 \) [! p! k: a/ [第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。0 n3 e. y; |- d6 g
    s = pd.Series(pd.date_range('2020-1-1 20:35:00',& E  ]/ `8 W8 j
                                '2020-1-1 22:35:00'," v* Y. b: z( ^( s
                                freq='45min'))
    7 X3 M, _4 c7 F8 X0 t6 K8 h( j( P

    2 `2 V# p+ O5 p5 q8 h1 l" g2 Js
    ; o% E  Y; b. U3 J: fOut[43]:
    * |  O, f8 p. z0   2020-01-01 20:35:00# d  }9 U) w2 {9 A0 p/ G
    1   2020-01-01 21:20:00
    / ^2 R- i& `, d! q3 o5 C8 U; Q" t! m2   2020-01-01 22:05:00
    4 q' \0 c6 r" s0 ]2 p1 ]" a( V7 ^dtype: datetime64[ns]
    5 m0 y3 C! r+ ]1 z1 c& n3 Y5 m! E/ W! R) z* l1 {3 ?
    s.dt.round('1H')
      Z  c( e# x* K0 TOut[44]: 8 y' z5 y8 Q% O3 |  D2 ~
    0   2020-01-01 21:00:001 i4 Y$ E& n: n
    1   2020-01-01 21:00:008 ~2 c6 {( [) Q
    2   2020-01-01 22:00:004 w- u3 o9 `8 N1 U! b# K3 ]* [6 J7 u  c
    dtype: datetime64[ns]
    9 A7 y( Z, N- o
    9 `. I8 T5 N+ x' d0 |s.dt.ceil('1H')
    1 \5 _; c5 J; a- _& ^+ ZOut[45]:
    1 j7 q+ c- K7 ~: A" H* R0   2020-01-01 21:00:00
    - K3 P3 M2 o0 w& `' B1 u1   2020-01-01 22:00:004 w+ N( i" X3 l$ z& Y
    2   2020-01-01 23:00:00
      H/ g( @% N. ~. K! Vdtype: datetime64[ns]
    % ]. o' R2 ]* b0 j3 L2 [& u1 H
    $ _7 _, T0 c, ^s.dt.floor('1H')
    0 D; W0 G# X9 t4 EOut[46]: ' F6 U" Z( m! f/ t5 O9 m! \
    0   2020-01-01 20:00:00
    8 Y& O1 ^$ k% E! D+ n3 l- D1   2020-01-01 21:00:000 `! a% n  q; q
    2   2020-01-01 22:00:00
    : C. t& Y" S1 i- k4 Hdtype: datetime64[ns]+ m- V9 ~  N, s$ P

    - F8 m! I) X& f9 z. L1; m9 ?2 Y1 u$ r9 Y/ }
    2, l6 B2 Y& L+ r; B+ d, U
    3: M, ?6 i$ q# o! n5 f+ ^, q- Z* f( m
    4
    ) d8 f/ z7 {8 Q" d/ u% U5
      ^" [8 [1 i6 |6
    % y9 P8 _: w: G; W% z. H7
    & M9 f. a7 _) @: e' {' r8  p- J; J  U) ~7 M( f1 W; z
    9
    , g. s+ r2 c0 R/ e8 y8 A' U) I. K3 `10/ I6 v; C0 K) H3 a) ^6 {
    11
    6 s! {# y, q  S, g9 o12
    & I9 N0 S8 P/ P13, }9 Q4 G1 l1 E& @" {# x$ g: M
    14- ?* L! R. |0 ~! Y
    15% [1 ^: O; f1 p6 C: y' R' O* |
    16
    1 s7 X2 i* f7 ~+ Y7 N% m, `9 l* f, w* \17: k- K; p7 F; ?% m7 k
    18
    # U$ C7 P- {  W+ ^" h' U19
    & B; X' @4 s; ^8 N4 }5 A: ~% y& r) `( Q20; X1 q+ q5 c' a+ x, F0 c5 u
    210 [7 z' N; E7 k* f9 Y5 k4 J2 _7 G  F5 k
    22( C- L0 ]* }/ O8 ]
    23
    , Y5 X% R2 \2 u6 _24. s8 Q/ c& Q/ `: G# `
    25  H: h3 M5 h) g
    265 |: G+ O5 ]) d3 L8 g* d4 `/ V
    27+ D" i( s- r) E9 [4 I/ d
    28
    - o6 t' v; W# M* \4 b29
    + u$ f/ x6 x2 \5 H# V" i& w$ w30/ B1 J. Y6 i! s( F7 }% m; @, X7 {) }
    31
    - f" r7 @; `$ M4 P% S8 Y; _325 T, J) W" p4 U: v5 P  C% W
    10.2.4 时间戳的切片与索引
    6 P( d0 _1 j+ d" x  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:! f8 p  I9 b9 ]

    5 X$ E: x; E7 X利用dt对象和布尔条件联合使用
    : M/ L9 T6 I2 \, Q) Q5 r. @2 V% N: D利用切片,后者常用于连续时间戳。
    ) E# @; `# u3 T: R% es = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31')); e# u8 V6 s7 _: N+ U
    idx = pd.Series(s.index).dt
    4 Y& G0 R# W5 p  @s.head()
    0 G) b2 `6 F; B& l: {" N7 }
    , J3 m7 N) ?- ~4 |5 X- D+ _2020-01-01    0; L  r0 k0 k/ ?/ c: m: b
    2020-01-02    1
    ) v$ t% \1 \# p8 {: o0 M2020-01-03    1
    + }, W* P+ r$ f6 q* [$ B+ D8 a2020-01-04    0
    ( W3 {  o; T  b$ m+ i2 y% N; n- G2020-01-05    0
    8 J( l3 H$ z# b, @# w5 kFreq: D, dtype: int32
    9 X* X/ @/ m$ `% J" J' W( A/ o16 \+ M: {: Z' @( ^; _- w
    2
    % B9 J# w! ?, ~3. Y. L! X+ ^9 @! B: Q' J5 }
    4. ^' m$ b0 l; ]$ h
    5
    ; `; E. I2 N: e9 }3 E4 |6
    7 Z, s$ L2 D, V, }! d' w5 E7, u& X6 c# i+ S0 g9 }
    8
    0 Z3 ]& o' [  c- t, S9
    ! h/ ~' r: c  o  q) ^10
    ( r$ a3 I! V) h- WExample1:每月的第一天或者最后一天' J/ K& Z" d! c$ Y4 Y; i3 a) u

    9 p; ?; I0 K7 H; \# V* I) |: ss[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values, r" d) ]: o. a
    Out[50]: : n+ B0 k: y1 I6 a
    2020-01-01    1
    # K; ~+ w8 B7 I% j2020-01-31    00 V( o( ~+ H' `" C+ E* d
    2020-02-01    1
    ; e* m6 O+ Z/ X# E6 B( q2020-02-29    1) G0 h4 h5 }* o: S
    2020-03-01    0
    8 b6 p9 K! z" q9 Gdtype: int32! x9 l: H! F! u1 j  w
    13 l  |6 X+ [$ p, i% o" a2 V2 x
    28 D5 ?, [. J3 c  r: N$ ]
    3
    , M. z7 C, `2 D& \" B0 {4" K- J- P1 N/ Q
    5: W1 y0 f# a4 k4 G
    6
    . M9 a) v8 s9 r0 u7
    7 c6 k; q; A! W. d8
      r! C2 a# C6 Y; D9 n2 YExample2:双休日
    1 y; [+ @( L% _$ T4 S3 q' L- F0 s& X4 F( G$ S) V3 y
    s[idx.dayofweek.isin([5,6]).values].head()/ p  ]0 A7 P! [+ Z
    Out[51]:
    2 _: Z; j& A) P9 b2020-01-04    1
    0 B0 Y2 B- ~& x2 W9 b8 Y$ S2020-01-05    0  g! ^2 V7 i* J* O1 F. w
    2020-01-11    0, ]$ b' @& c* N% n- q/ [9 D
    2020-01-12    1
    " D4 M9 X8 j' @% x3 m2020-01-18    15 x8 @( i' b4 K9 H6 F( h- L
    dtype: int32
    ! o3 m1 |: S& t% K1
    " ~. v4 k; O& ~: \9 O2& J; q7 q# G- _
    3  q1 ]' z# h- v8 \8 F7 k
    4
    % B  [3 D; t  [& t  g5* ^; T8 w1 h2 ^# c3 y9 H/ Y$ A
    6
    2 P! ?# x1 c8 c/ S7* ], [2 h/ W/ p: S# ~. P  P0 I
    8
    # H" P  D$ b" K0 xExample3:取出单日值
    & [+ T* R7 I8 |) b8 W% w
    ( A4 j, F1 k* j) x# c# Os['2020-01-01']
    * p% O: A8 `. b. z" EOut[52]: 1* ^0 ]% h0 h/ O
    ! z# F) Y3 c5 |: z
    s['20200101'] # 自动转换标准格式
    3 e7 {& Y" H1 _& ROut[53]: 1$ A" f9 k. t$ g- m! X! U6 ], d! A
    14 O' R' O3 T) j& V& ^
    2
    - ^, d* p) E9 S4 T3 i3
    % v" X4 x/ x" i* Q9 G4
    + L4 k# e+ y7 F0 R! f5
      {0 H& r" ?* G8 b) bExample4:取出七月
    7 u4 Y: x. n8 a8 S  |
    " g( b" u9 z5 h$ g4 |+ Z. A$ j9 Is['2020-07'].head()% j# F( o3 X# t8 o6 n  A2 ?2 F
    Out[54]: 4 [/ B# k$ H5 e* S( H' w
    2020-07-01    0
    - K8 y0 N, \& {2020-07-02    11 h- I; K) g$ t: {
    2020-07-03    0
    , z6 W* e& D2 c2020-07-04    0
    & X* M  i+ Q; g+ F2020-07-05    0% ~) z1 f" }3 W2 r8 T9 P6 h2 L
    Freq: D, dtype: int32
    7 _" q, k* \- @  N1( G! Y7 p8 q3 W' o
    2
    & ^: L" M2 [4 m, W- \: N5 I3
    - m$ f6 X. I+ X& v1 S' W% a0 q48 y' J3 T! x( T" \6 W/ h5 }
    58 \- c7 p' ]! M7 R1 B) [/ [: n! B
    6
    % h, z( Y8 g; z4 H6 {7
    3 n# k( V8 [6 f. I8. q1 S5 |/ x0 o8 c9 r7 G; U
    Example5:取出5月初至7月15日
    ! n" I- x; O) a# M* I( D5 Q1 F$ i1 _0 m) K
    s['2020-05':'2020-7-15'].head()+ [6 W6 C! p  G2 R( R! b1 n2 v
    Out[55]:
    ! |9 h/ D  i. @3 X5 U8 j& x" D  H$ q2020-05-01    0
    3 ]! L" y$ x0 `$ [2020-05-02    1% P2 }) e: J/ n* P; I1 P
    2020-05-03    0
    ) G4 p4 J" O) W2020-05-04    1
    + N6 A( ]3 h# Z2 P) d5 i, ~* ?3 a2020-05-05    1# ]2 l/ h/ F5 y0 I  \, _% G' S  b
    Freq: D, dtype: int328 ]9 k3 B* j1 N) n+ B3 i# P

    7 e1 k8 X6 W, d5 Qs['2020-05':'2020-7-15'].tail()
    % d) Q* r$ M( x% MOut[56]:
    5 ^/ q" w: D' d, x0 P2020-07-11    02 k: z! k7 F) |
    2020-07-12    0
    ! O& O0 ]/ v1 I! ^2020-07-13    14 C. Z4 g: P! C7 J1 d) S4 F  \- S0 i/ J
    2020-07-14    0
    ! l+ J' C8 A3 M! O  x0 W$ i2020-07-15    1
    ; Y* j1 C4 ~- t' z) g  I" l/ TFreq: D, dtype: int325 P+ e- }9 C6 q) a: U: v
    * S  R3 c. U4 ?
    1/ T# l( X# t$ c7 C+ S7 Z& g; O
    20 Z* Y5 \( Y' H: V' \0 \' e
    3' E' f1 Z# n$ Y+ ?
    40 R" q8 z, e. m( S3 z/ {& G
    5# x2 W6 L: Y2 U' I& x
    6# ]: M& M1 P. e# T" i' D8 e9 f
    77 \; C) G0 j, a3 G  a
    8+ J, K4 ?/ F0 Z- E
    9
    ) c; C5 ~" N$ o3 h2 t; \5 K# s10/ o8 h5 t8 s; ~
    11
    + Z2 \& o1 |4 W12
    9 c6 K6 G3 k( a  E( j5 T) k13
    % s' k4 Q& b! w+ [5 S- N14" w( ?& l% f) u/ N) ]* f! Q
    15% j# v, q: s, z4 V
    16
    + K' i8 n# \- d. ^2 c) ]6 }17
    ! x. I! H/ a$ d+ e. f- e6 u10.3 时间差* i% D* W+ S) X: h
    10.3.1 Timedelta的生成5 I. \* l4 P3 E- }! ?) v) _4 x
    pandas.Timedelta(value=<object object>, unit=None, **kwargs)
    % z8 f( K% k- a: e% y+ ]  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
    . S# p% h! I# V& G% S  可能的值有:! G# V+ q4 z5 c& |0 o2 a$ S" ^
    ) A  ~+ \( s( f0 X  _
    ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’5 H9 m. J! ]7 r: }* e& M# w
    ‘days’ or ‘day’5 I8 t6 t! j  ^6 U, I/ b% M1 p& Q' E
    ‘hours’, ‘hour’, ‘hr’, or ‘h’: J7 g/ {* y3 a! ^
    ‘minutes’, ‘minute’, ‘min’, or ‘m’
    5 Z5 i4 ]. Y5 B% K6 B8 [9 g‘seconds’, ‘second’, or ‘sec’( C7 }4 M% g& f8 B- \6 Y0 W5 _2 t6 P& Z
    毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
    5 g( K: r& q3 ?1 h& Q$ }4 B$ S微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
    5 A& r3 m8 f% C纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.$ l6 n- O( B* t7 w$ G! C9 X
    时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:/ P4 T2 L% a* b+ F3 @  z+ g- _* ]- q
    pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')
    ! r5 j+ B$ R5 G( NOut[57]: Timedelta('1 days 00:25:00')
    ( R. v6 [9 t. u' ?% b, `; ~+ ]) ]# l/ r1 K* S
    pd.Timedelta(days=1, minutes=25) # 需要注意加s! k( ^% z. n5 o& `  A
    Out[58]: Timedelta('1 days 00:25:00')
    , J+ y' Q; y3 Z! O1 \* L- Z6 i, ?0 U: A/ O6 Z; y: t
    pd.Timedelta('1 days 25 minutes') # 字符串生成
    - b" u8 m% x; q6 \$ NOut[59]: Timedelta('1 days 00:25:00')
    6 c- X# \1 O  K; @+ Q! [" e
    - a4 J& w  j0 W4 c; M' [pd.Timedelta(1, "d")$ O3 ]% k& T5 w; B9 Y# |# ^+ u
    Out[58]: Timedelta('1 days 00:00:00')8 H0 i, m0 N. E8 B* t4 a
    1  X2 c$ J9 J( P$ q2 b
    2
    6 q2 |. q# T* ^  t4 n) J1 {8 Q3 l3
    2 P! a9 E' f% @- |( b4 ~4
    + Z9 G: L  x% U$ ^3 X# P5- v% ?4 ]  Z) x$ y: d- s
    67 p$ X7 x- ~- j2 a+ r4 Y' ]
    7$ w7 x+ O' `' t3 r1 \# x
    8  ?  l7 y* C8 }1 T" ~/ I$ ^! s
    9" x5 I, O; I4 J% {7 l$ M
    10
    ' g0 C' B3 Z9 ^  L4 a' O11
    ) n- F# m4 p  G* a  B/ `生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :2 _6 {2 F( |8 K! y7 l
    s = pd.to_timedelta(df.Time_Record)
    ( p0 M% J' O2 D5 [; E. m. H1 f- a/ `* u- x
    s.head()
    . j, s9 U9 f/ {* }4 w/ I3 JOut[61]: 3 i" D5 k% d3 M$ L# |1 v* a1 k" F) V" R
    0   0 days 00:04:343 Y' F8 A; V8 K& O/ ]) x* H
    1   0 days 00:04:20
    ' A$ Q9 X& A: ~3 i2   0 days 00:05:22! H8 }/ v  ~5 M$ T" y1 c: D9 y' h
    3   0 days 00:04:08" W, L5 Q7 x5 N8 ^$ ^- [
    4   0 days 00:05:229 `3 B4 y. G$ |/ |8 [' `0 j
    Name: Time_Record, dtype: timedelta64[ns]
      A5 Z; w$ Z8 [- I' O1
    4 m5 N8 l7 ~5 n20 |0 y6 U! I" n$ o
    3
    - }3 D5 C( f  e- P4+ _5 ^5 n* a+ c+ j2 r2 {2 n0 H
    5, D0 v  T, I. d- Z( }
    6+ e# `$ k6 O" S5 T6 j1 _
    7
    + S* U5 E. A2 |/ l9 ~- d8; U8 G; }5 C0 W& y0 I4 X
    9- J7 n8 E4 F* |' p: M* E
    10" r7 _0 f5 ^" H- K3 Y
    与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:* I" O* B8 {5 p9 s$ z
    pd.timedelta_range('0s', '1000s', freq='6min')
    7 f* n* r5 b7 [& g- X. S! q, }Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')- {9 [! Z0 e" [' s: N; O4 [: Y
    ) J& Z: U# Q+ K8 n2 p, c6 h; `4 X
    pd.timedelta_range('0s', '1000s', periods=3)' L' e% `- O" y/ S) F& b- m4 j
    Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    0 {0 v" A: W/ v6 N3 x+ @9 I( t" V( e1
    $ ?# B6 V! X* p& A29 j, b: B" v' A+ I, _
    3
    ; L% L! o/ B: n4
    ! F+ e: _  U- @( d; }# o' E& x0 X56 n$ f( e4 r3 Y4 A. f# I
    对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    / `  m! ?8 W% N+ h, i  ]' x$ fs.dt.seconds.head()0 Q8 Q' b3 m+ j- h3 q3 z2 k
    Out[64]:
    , s, E; x1 _9 F0    274: A+ a. K/ x$ Y  S6 ~# N- i  n! B4 h
    1    260
    * n& b1 L0 a$ E8 @% o1 L+ ?% N2    322# a) a2 d2 M9 j7 ]
    3    248
    ( c7 J* Q. q$ z" h4    322
    . k& j' ?; e+ v4 F- XName: Time_Record, dtype: int64; ]. `( d9 g* d/ ~( ?4 k2 G+ S6 P
    1; w4 ]( v+ k/ D0 f" L) F  Q
    2$ j6 X7 g1 e1 g; k" p: t- R# Z4 S
    3
    : q7 r+ _8 p. u- e8 j' J4 h4. d) N# n9 V, D
    5
    , H+ k# H9 F% \5 L0 N6
    : q. K6 J0 L/ _  x3 S) h: q75 A7 d6 h* r$ s( @3 u
    8# Z/ O1 U8 @0 X% b
    如果不想对天数取余而直接对应秒数,可以使用total_seconds6 M  G7 G: T6 Y8 j) o
    ! E5 M$ D8 `! x# G/ w
    s.dt.total_seconds().head()
    9 m2 w5 ]# D* p. c! b5 YOut[65]: / ~; o# Q, V$ }# E. t& ~4 o1 U* l
    0    274.0
    6 n5 S; F$ N) O$ g6 P. {1 H1    260.0* `0 O$ F3 |/ E0 y# k
    2    322.0
    6 N2 x+ b& B" j6 n0 j, A; ^3 |4 e3    248.0
    * L& G: v4 [5 r5 l+ D4    322.0
    6 u) C$ X5 O: Z- W7 lName: Time_Record, dtype: float648 t$ R0 M# k! Z, u8 W8 E* h3 l
    1: D* n  `, [) Z4 O7 d
    27 X8 p$ @3 b0 w# X
    3
    7 V' H/ Z; t8 c" m) |4 i, T4# K7 ?( L5 i  j: q
    5: l2 }- d( X# u5 r5 P8 j1 K
    6
    ' ^  L& t" o: c3 k& ?73 i) I5 l$ b4 l3 d* w
    8
    ) \7 V; O+ @8 B: _0 r8 }( Q: c% ?与时间戳序列类似,取整函数也是可以在dt对象上使用的:
    , r$ e( P) G0 D; D8 H, m+ r# T' J2 X; z
    pd.to_timedelta(df.Time_Record).dt.round('min').head()
    + C1 B' S2 M2 G- SOut[66]: ; A! i, m$ p" d( U; Q) Z9 Y
    0   0 days 00:05:00
    , R0 E. P  i8 U9 |) _* F1   0 days 00:04:007 G3 l0 r- d$ ^' n) C
    2   0 days 00:05:00
    5 `, @! a1 ^, d, n3 A+ ^% `3 y5 R" b3   0 days 00:04:00
    7 }& N3 w7 S& I" I7 A$ }# H2 }# |4   0 days 00:05:00
    ' ~2 t3 j; s8 l2 f' XName: Time_Record, dtype: timedelta64[ns]5 ?6 r, M5 c/ V
    1
    - Y. D- c, l& H4 J: h; T5 r2
      m9 W: @6 j& D+ G+ Y' k  y) y$ O* j3
      A9 s4 w4 `' W+ E+ r4! p3 \% ?2 [! K, L3 a/ Z( I
    58 P2 a4 R, F2 |9 C! U* N
    6
    5 U8 _8 T! \8 K- l; ^7+ n* o" `3 b- A+ F4 Y3 X  x. w
    8; P. g# c' i' J. r$ D
    10.2.2 Timedelta的运算
    , v% s- q& M) t% }1 K8 p% l单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:9 ~' U# o  i5 k1 J# g8 ?
    td1 = pd.Timedelta(days=1)( K7 z& j4 h0 G
    td2 = pd.Timedelta(days=3)
    . q% o0 F" ~4 c& Pts = pd.Timestamp('20200101')
    ; b" S$ v. M5 o1 q. ^4 y5 [7 d* F; A+ W# p
    td1 * 2( d$ o2 j- g5 i/ D  Z; B
    Out[70]: Timedelta('2 days 00:00:00')) `/ X+ d" s; S3 k9 k6 Z" J

    ! M& C  ?, N0 D. i4 Utd2 - td1
    7 q, O. X& n+ }) ~) A* `  TOut[71]: Timedelta('2 days 00:00:00')  r+ J) @% T. r% e9 K2 d4 l

    % R9 N- s7 O- i6 K1 q9 Bts + td19 K2 W. @+ _4 W5 {% C& M4 q2 r* E
    Out[72]: Timestamp('2020-01-02 00:00:00')
      X5 b! W* V% u9 H4 {' y: q* e  v! y! ^; W# F
    ts - td13 [* Y" Z5 }; S5 F$ F$ {8 B' o2 r
    Out[73]: Timestamp('2019-12-31 00:00:00')
    % U" ^3 ^3 u4 o; E1# X8 B7 L- T: ^$ W- a
    2
    " d5 S. ]; ]5 N+ e; Q) X( n3
    " k2 e" Z7 L' L9 n( P4
    % O8 s' u8 l7 h. O+ U5) k/ C- m& i7 L6 c  v. ]% Y$ d
    63 N' K7 i, q$ a0 D7 ]
    7! T; v, }8 ?( |; L
    8; e* e4 M$ i' a7 p- t5 n
    9
    , l2 J) X+ ]! F- [5 N108 X9 Z+ A4 f. K3 }! A4 Y
    11( V' ]: p8 t1 z! h
    12
    $ u! O* ^3 L# @3 k13
    % E& O8 e2 X+ y* f# T14
    : y2 x7 c2 Y* V  l( K3 }7 U& }15
    + r; W2 `7 R" [' G9 w& s( m时间差的序列的运算,和上面方法相同:
    * i/ j" s' `3 ^% a4 d% `2 g/ Utd1 = pd.timedelta_range(start='1 days', periods=5), Q" q* _. X3 r: Z: O4 j
    td2 = pd.timedelta_range(start='12 hours',3 _! F9 m' w+ _
                             freq='2H',  C. p, h6 F( a8 J0 `/ c
                             periods=5)9 O3 i9 f4 w- k6 B
    ts = pd.date_range('20200101', '20200105')
    $ C7 i' G3 `6 h# N4 K1 w& atd1,td2,ts: d! u: _) {! u0 Y* ]

    - V7 L1 h0 U* [1 h( I+ M" g1 p3 yTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')) q# A1 C1 N: W; ?/ z7 F! `
    TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
    5 T; N. b0 N5 G$ f; a9 a5 X+ O                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')) ]! G0 m  i/ O# q; I! D
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
    8 J# s8 z& J$ E; u6 l8 h               '2020-01-05'],
    4 L3 b9 b; L, v+ \7 u4 r" R              dtype='datetime64[ns]', freq='D')
    5 B  N# J6 D: b- y1, S3 P1 X5 v# P8 w1 q' f: [
    2
    ! B7 i) `& w% K. q; J3  K1 I9 f$ K0 ]. p" d: B
    4
    2 {3 u& j+ C: V% T4 Z; {5
    + ]5 R% d0 I3 j6
    # a. Y  Y- X% M' \' S3 p2 r0 l7
    ( V% U1 z1 U- f- v8 |; O6 v# _8
      D/ R% B7 g9 T+ @9 y1 j& v9
    * M) c$ ]2 |9 P+ x# n; R" D10
    $ @4 M! V! @$ T; t1 i' }0 W  s11: j2 d( _( o* ?# H
    12
    ! L4 C4 G* N5 b+ J1 m9 b/ [; I1 n13
    # L  k$ r5 D" U' X, j/ Std1 * 5; M) {" I# ~0 B
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D'); b7 {  s5 g' \" v, O3 V) n

    - Q, f9 d  }& gtd1 * pd.Series(list(range(5))) # 逐个相乘
    0 D# Z# V7 k; pOut[78]:
    / n1 i* Y* m( s& |0    0 days
    : t4 C0 l8 e6 U; |8 J  X1    2 days
    5 J* Y# x* p4 y3 [- |2 K( ~2    6 days& U8 V" i* z6 u) W! h% j
    3   12 days
    5 L4 t  D1 G; q, S" K3 ^3 t4   20 days
    2 U: X& t5 h) Z& j' c6 Y( ~1 j* Ldtype: timedelta64[ns]
    * t0 F, I( W: @$ i* ~( h4 F
    6 [" f! O: N5 e* Jtd1 - td2& U9 B) i$ Q9 R) S
    Out[79]:
    0 Z9 g; U6 h5 v! c7 a; ~0 }' ?- TTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
    . Z) F- n( l6 z  I/ l                '3 days 06:00:00', '4 days 04:00:00'],
    7 }; t* u1 R) F               dtype='timedelta64[ns]', freq=None)8 y9 @& L$ h& Z7 l& B& l
    8 x  Q( j( `9 T& K  ~$ H3 T
    td1 + pd.Timestamp('20200101')8 P- Y3 b8 o& O/ X' g+ `; {
    Out[80]:
    - O/ s7 q# a9 m5 t. Z7 g7 }& _2 nDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',+ w& ]+ \$ q, |7 z
                   '2020-01-06'],dtype='datetime64[ns]', freq='D')8 g& ]7 ~/ ]3 z2 m+ x' O
    & m, d  I' M$ N* X6 _. Z
    td1 + ts # 逐个相加
    ( E& z8 K$ L3 a% uOut[81]: 3 \4 x% {& g4 J, G% v
    DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
    3 {- D* m8 g6 {% F+ e, k3 Y8 S               '2020-01-10'],
    7 s) l% ~8 b- i: A5 p$ S              dtype='datetime64[ns]', freq=None)
      w* r; r( Q, ]8 [) R8 t  C8 L; \' i1 |' z" q9 t
    1* \0 a) q; Y  j% E7 j# x4 p/ I
    2/ M" D, U2 K/ b  E9 Z* h
    35 s  u6 G9 z+ i  L
    4' J0 A, i- h1 l3 X
    5% L& C8 R: k: b! _5 Y" {
    6
    1 [+ f; c+ X* q$ g79 y/ E4 g7 ?" ~" ]
    8
    & b: ~& ^4 P0 m: Z9
    1 I) S& O! k* J6 i8 ~10
    3 H+ p: ^8 f6 L8 O11
    & Y' b! w( T7 k: M+ L: j% ~12
    7 ?7 m: _  u9 k2 C9 T" @4 z( {% a13( Z4 f) @8 X/ ^8 c9 T* l3 |
    145 g) ^4 A5 b/ Z$ i
    15
    6 p) k. ^- I$ X9 p166 ]: b6 n8 [4 l- T
    17
    8 t% o; C4 @2 S# R18
    ) q: l. X" [2 N195 p9 t$ j: I7 W" I2 c3 \
    20
    & H! e0 e* b0 q5 K, W! A21
    / `! @0 q: X2 i8 ]( Z22
    ' m5 c+ [  p- ~; W0 e9 Y! M6 u23
    1 m- O5 e, w# e1 ^2 S24
    " p1 B7 x  B7 _% N1 t; y3 `9 n25- O8 [' X8 {& o
    26
    5 ^+ c0 V! U5 P. t+ [1 B9 Z; i275 j1 g* x7 x+ ]1 ^' c; H
    28* R* F& E& q0 \, j! i' I
    10.4 日期偏置
    3 {0 X* A, ]0 g- E- ~: Y10.4.1 Offset对象6 l: c" S( e1 {% [1 T4 h0 M1 r
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
    6 F' [( l8 [% O5 U* X8 X
    - ]- ~$ y( h( h$ B) WDateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:! _. {' Z3 u" u( w4 C
    + v6 Q3 t$ {: F% K( t
    s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本1 u' d% q, s% T1 q' Y' M) C
    s.kwds:{‘week’: 0, ‘weekday’: 0}+ b8 i# H: @. T; \3 o! @9 W7 u) U
    s.wek/s.weekday:顾名思义
    8 z2 ~, h- [) M有14个方法,包括:
    . F9 D, n* S# t2 p4 B7 i: x
    - M: y+ Q6 G/ |! I% M' W" M, S! ADateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    4 o' o0 g- L8 h3 wpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。
    ' }% p" q+ p" F( w& S1 P) {
    9 \4 \1 i' `4 e- y- W$ a( L: U9 _有两个参数:
    5 J4 |$ c" u- b- o* m! m0 G$ wweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。  c8 ]5 k5 M- l; @8 q/ S. M
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一); e3 v& i. e5 w' f+ d
    pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。' }2 h9 g' q& s' q: |7 y+ `

    + U) N5 _& G5 x1 rpd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)' n4 m+ }/ z; P7 h# m8 @
    Out[82]: Timestamp('2020-09-07 00:00:00')) n6 p' t( S" L: P4 y
    6 x* K. X. o# P0 h" T; [
    pd.Timestamp('20200907') + pd.offsets.BDay(30)1 Z1 e! o, y, u+ X6 O( o
    Out[83]: Timestamp('2020-10-19 00:00:00')
    9 Q" @! J) w! r0 a9 Y" h' K1% u3 f5 i2 e% _0 R
    2
    . Q' R2 U: p9 `, o: e3
    + z( m; f8 c; e: W1 D/ o4
    1 Q! y' J/ w3 i/ `- ^- B- k5
    5 |7 p, R+ \% o  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
    ! D! z: T6 Y  w3 `. I3 A+ D- ]4 [4 H. ~# ~" Y
    pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
    : \6 J9 G% R( v# WOut[84]: Timestamp('2020-08-03 00:00:00')
    4 ?0 Q0 A* A- ^) e8 x1 v" ]  ]6 o
    $ K( v1 O; j$ l5 X6 o* Qpd.Timestamp('20200907') - pd.offsets.BDay(30): T# W% b7 H  I+ Y5 a
    Out[85]: Timestamp('2020-07-27 00:00:00')
      @' q) I) N& B# Q2 H* ?  C5 ?9 d# n9 `; K7 m7 [
    pd.Timestamp('20200907') + pd.offsets.MonthEnd()
    ; q3 E$ J3 a3 A+ rOut[86]: Timestamp('2020-09-30 00:00:00')
    $ x) F& _, J  t2 R( q: j$ k2 |1; s% }4 Q; F7 r: t/ M2 f
    2
    5 Z( `, n1 w3 ^) m. ^3. i" M4 |6 V4 |# e
    4) B; W7 n! U7 t: \) ?+ k
    5
    3 [; {0 s1 W- h! t1 g: B8 @& v6
    % s: @* w+ e" N; a: v5 e7) N8 r; T. h7 _' ^) f" w
    8. a+ r; {1 ?+ Y- g! ?' s
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。7 R9 G8 v2 M; F* {- f* a
      其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:3 w2 M- q# y( K9 E

    , [9 ~4 i9 n2 Y9 S0 \7 Omy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])+ W1 S& a; U# u! M) r& n4 q+ n
    dr = pd.date_range('20200108', '20200111')
    , r) T9 M+ c; C8 l% l7 ~% W+ M
    dr.to_series().dt.dayofweek
    , U/ L1 W8 r; d4 \- `8 o: f8 IOut[89]: % O- F3 D0 P" A4 Y( d, b# ~% A
    2020-01-08    2+ Z- n4 U* j( P2 R$ J# m8 j9 c
    2020-01-09    3
    8 b$ f% A; Z! z0 E2020-01-10    4/ G/ E/ X  r. u7 O8 H
    2020-01-11    5
    0 ^# U% C% ]3 `8 r7 T8 aFreq: D, dtype: int64
    $ W& g1 X0 v1 p- V1 O0 X: U
    # X4 ], A7 o0 V  x3 m9 K[i + my_filter for i in dr]
      e# y% ~, i# e& h$ ~' TOut[90]:
    ; J/ |  E7 l) d& H% O) L6 R[Timestamp('2020-01-10 00:00:00'),9 C7 ~1 j" x, m% {, N5 Q
    Timestamp('2020-01-10 00:00:00'),1 _0 A5 f6 M+ \6 N" E4 h  S/ z8 @3 e
    Timestamp('2020-01-15 00:00:00'),
    ( N- s" A0 ?3 ^1 W4 _4 m4 ` Timestamp('2020-01-15 00:00:00')]' b& ^% V8 B" c6 t3 f
    * z" v6 `: |/ v6 \5 }) e+ |
    1
    & [# ?! m, _- s' [2
    3 M4 ]: T8 V. X# \3! H* M0 v3 L/ r! m
    4
    + ?) ~9 e2 O" n9 d* i( t3 o, Y* X5
    ( A$ u0 H1 H% j5 d% [/ L! z68 K; @1 s/ b8 _# U
    7
    6 t; J5 u) w8 F+ Z& I9 c8  D; s, N2 f7 w
    9/ R  f& B  t, }( d
    100 c; J; F# E$ v2 s
    11
    + a4 R) x3 D# [6 Q3 ~12# d7 d; K! v+ M* }0 l
    13
    ) D! x# C! v" M. P1 P14
    1 z" E5 J& x* }- p8 P15
    3 A$ E* p% |% }8 L. x5 D+ T( d16
    4 d) i! C' |3 `17
    % I5 u0 X0 B" v  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。6 D  b2 E3 }& ]# ]/ m, n& a
    3 I! X: X- c8 Q' y
    【CAUTION】不要使用部分Offset
    : h6 ?( U5 W" [0 P' [  d4 O% _  r在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。1 ]6 Y" ^& s2 J/ ]

    2 j, y5 ~! @9 R* y$ q$ |10.4.2 偏置字符串
    9 |: J" I- e, H4 \' _9 e  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。2 H7 \) F  r6 {8 Y" ^

    0 ^" C- d( g# j( N+ O/ u+ Z% V5 `  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
    3 c& ?: |- y3 z5 l& U9 ]: V; C$ W/ G' x) B" E
    pd.date_range('20200101','20200331', freq='MS') # 月初! W+ g& i' _8 _8 E* O& w+ [0 N6 v
    Out[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'). {. ^" F# Y& ^5 P- ^
    * r) Z% k( D. l2 I5 k$ ^; c
    pd.date_range('20200101','20200331', freq='M') # 月末
    - \7 s2 S2 K7 V6 P9 {Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M'), W7 M+ q  K% @8 P

    0 Z/ f. y5 `! m, Apd.date_range('20200101','20200110', freq='B') # 工作日. X8 A( \5 v2 o; I- z
    Out[93]:
    3 e# q. _9 |& lDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    3 H: U! I+ R6 n8 ^4 X" d# z               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    / q8 S% V* U0 F* C! E) O+ X2 {              dtype='datetime64[ns]', freq='B')
    ; Q5 @' \0 q; q4 e, q4 j8 L5 O5 Q( w0 \5 l! ~0 q1 s* g
    pd.date_range('20200101','20200201', freq='W-MON') # 周一( M  S: j+ B3 i# `9 a* g
    Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')3 }8 V6 N" O6 g  {1 {' ]- C5 W% k# _& K
    9 ~0 a$ s  R) R7 e$ P
    pd.date_range('20200101','20200201',+ u) [) F9 \# T) v9 j
                  freq='WOM-1MON') # 每月第一个周一* I- T0 W7 x" r1 F" t' d
    7 X$ H- N+ B/ _9 x, |
    Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'): w/ i3 l2 v4 L* B& n

    / V" v. y9 D- y9 A; U& n13 L3 a. `$ t0 f0 N7 i: ?
    2
    / E; N) J6 U% ~# r7 F; c30 [( M- T; j# B; l9 Q4 W
    43 p; D1 d  J. z4 f# a" @  D
    5: d. A) G( Y" V
    6
    : d1 f+ r$ J# B: `! s+ |, S77 T. J! P4 S0 |+ u7 }3 a
    89 T& e6 s# s% Y1 Z
    9
    9 L& U9 i2 L, v! Q! [10
    ; t9 ?4 W0 Y0 o* V; }11
    / l4 [8 n- ~9 X/ h. v- b128 a" W8 O/ Q' R$ n% S! R
    136 H8 I7 U: K  j3 l2 X
    14
    8 [- s, q+ O0 q15' W5 w  d5 O! ]8 I
    16# \$ H9 R7 t; V6 b$ {9 D4 W
    17$ t$ z) @# }9 Z, m* I$ e4 }/ S
    18
    " U' Z) m+ e  E# p% z19
    ) y- G9 f! @" P上面的这些字符串,等价于使用如下的 Offset 对象:4 G$ o4 r, z* Y+ ^" j, s& e
    + H1 p$ ^8 ?& `3 B
    pd.date_range('20200101','20200331',6 B( K( s2 L4 }. D3 X8 w
                  freq=pd.offsets.MonthBegin())
    4 P# x; E2 P8 w& o7 e
    * c4 Y* t4 f6 L& T% }3 kOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    : m  z# b3 d# h; `& f, v) r! U9 v6 N- k$ i
    pd.date_range('20200101','20200331',5 e2 I% u3 r" ]  ^2 P
                  freq=pd.offsets.MonthEnd())- g0 C* H) w0 s0 l

    ( w6 W: a+ ?! R! }Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
      }# v7 B( R, w! M) W( ]  w9 U3 C: k- o1 e2 k0 c4 f/ Y
    pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    3 y6 ^; d# P& y* D, i6 tOut[98]: " ^( Z# G. S2 Q. D0 [0 F) u
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    / D2 |, p4 `' z               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    3 V% ?# I8 s8 e5 |1 o              dtype='datetime64[ns]', freq='B')+ Y# k, q% F  z! m0 Q

    % n* p& w8 s: W4 b7 epd.date_range('20200101','20200201',
    7 n4 n9 w+ L. c; I' }  B              freq=pd.offsets.CDay(weekmask='Mon'))
    2 n: L3 F5 ?, e1 e/ K, {3 X' s- o, q; z6 h1 }* z
    Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')+ A' ?# K- J2 U" h! Q
    & m- R" I& g+ G( l. N+ j, u6 X
    pd.date_range('20200101','20200201',. O! ^& k& e# {0 x  j' V% b$ u
                  freq=pd.offsets.WeekOfMonth(week=0,weekday=0))4 m( M3 ?) {+ Y

    ! A3 t6 }0 I( j2 T: Z% I/ [2 D/ mOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    / `7 _( B0 W* q7 I( v2 n: C' s5 U: G' t  Z+ ?' Q
    12 j1 g5 b! E+ O
    2
    + n  o, |+ Q% |7 D8 f" S37 V0 \$ `* A1 K& k& z7 w: j' G
    4
    5 m, ~- Z  R+ u8 v: M( z5
    4 F6 |. z& t' m5 V8 n6# K) t0 P4 O2 X2 f6 z* K9 F
    7
    ! s9 z2 `6 T, r: D8
    $ e0 K6 E& I& X7 @4 Z! f4 Z9
    * Y' D8 u( O5 q$ z  T: e10
    8 f- f7 f2 U! B/ _11- e2 v# x1 U+ C0 N( z
    12
    6 ]7 C8 l- d# M3 M! t7 J1 e, T% y13
    $ V0 Y& q( Y( B" @/ j147 W5 |0 U4 `1 E. k
    15
    % e; a- T8 J/ E: G( J' \. I% ^16
    9 R. D3 w* G1 H( J1 O17. K3 j. U  z: y' Z/ ?
    18
      l* c! ?8 A) ^0 E% J6 W  ~6 @9 Y19# U3 z  Y2 O; f( a) Z* }3 g- ~
    20
      d4 d/ E0 c+ V) I21
    " l/ A2 Y& i" ^  }5 C22
    ; K- D& Q/ Q4 C+ H23- g" ?' P! h, ~( ~+ V# N( a
    24
    # P8 H$ _1 n! D+ _3 U; M$ f25  _- ]9 n* ~0 `! M. ]
    【CAUTION】关于时区问题的说明
    * p% p- _, V% r) [; ]  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。* t5 v& e6 A7 `

    % O( P; D* b" r! E0 j0 E. |$ W10.5、时序中的滑窗与分组! [9 A+ |  F; g
    10.5.1 滑动窗口
    / b% j) [1 j$ D; C$ F9 O  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:
    4 ^; B6 K" g* C
    0 Q2 F3 r7 d; w( wimport matplotlib.pyplot as plt
    1 j' D: T/ s% sidx = pd.date_range('20200101', '20201231', freq='B')
    # _2 S" a" ?+ f3 T0 a0 nnp.random.seed(2020)
    . ~) c1 b  E5 L3 ~4 I
    : R% v$ V  i) t2 vdata = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加7 H( B% A6 a0 k6 ]0 v5 ], j- }
    s = pd.Series(data,index=idx)1 k; N* S& |. ^  Y5 j8 K4 J
    s.head()
    7 ?2 ]: c0 J2 ?% V2 l/ SOut[106]: / M( Y% ?/ H7 n
    2020-01-01   -1# g/ |9 J! L6 V: G
    2020-01-02   -22 c* |; Y% ~! B& r( c( q. f
    2020-01-03   -1
    : s5 L+ ]+ |6 u5 @5 g/ A4 D! C2020-01-06   -1! [4 F/ y9 @, \: Q  n
    2020-01-07   -2- h9 @2 ?3 Y+ m) Z/ A0 q
    Freq: B, dtype: int32- i& s. X3 b- j! O1 U7 M" z  @( l
    r = s.rolling('30D')# rolling可以指定freq或者offset对象
    ! Y! @2 @* G$ O8 P1 l& i
    9 B! h% P9 e& a2 a8 P) N  Tplt.plot(s) # 蓝色线
    $ V( Y% ?3 }9 {# o* G8 a+ s3 qOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]% |. @- D) P* I% a7 A8 n
    plt.title('BOLL LINES')
    ' x* x! f2 d7 r) x9 o7 Q" Z2 jOut[109]: Text(0.5, 1.0, 'BOLL LINES')
    2 C  q$ N+ @; e! D, _# n1 z- f3 G' g3 x8 Q* K! U& g
    plt.plot(r.mean()) #橙色线
    , s5 q/ [, {' H1 a, {Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]! \4 o( u; F: V- u

    - A4 G( T+ a: ~& Splt.plot(r.mean()+r.std()*2) # 绿色线
    * d3 i0 h1 v4 \5 IOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    6 y6 m& U- }  m9 r) f6 J, Q3 t* J0 s2 ~" e+ J
    plt.plot(r.mean()-r.std()*2) # 红色线5 g: W4 b% J, J' m$ z* \$ x6 l3 a6 m
    Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
    6 b2 P, C' ]5 o
    - P1 P' G8 k; q. s1
    6 q; M% E0 y$ Q- P! d: w2% `+ {5 Z  M; K  l0 ?
    34 }* {- L1 S4 g! U, E$ ]* j; F
    4, P3 i( S/ I3 \5 c  v
    5) B  q& X) l  M% _) H
    6; j  x' a. G8 d$ B
    7/ s1 D" t4 H# P( t) v. d/ g
    89 R5 C$ |5 q7 P0 ?
    9
    2 e1 |; U* e3 }  h$ t10: ?& t3 V+ K! }  f! X! |
    11& J7 I/ @. k% A1 K
    127 d5 q: |5 ~- H
    13
    7 y0 N; i) ?  o# x4 ]9 e- N/ ~14, I2 u  ]" d" `& C0 l7 W5 C
    15! `0 f; T% Q0 ]% I
    160 H0 f5 R, b5 ^# B6 a
    17
      L6 n: l; N* `  U9 i+ G18
    , m& B+ b% G7 ]19
    7 N6 \# I# `3 |1 _20/ h+ _7 r6 y' v! D$ Z
    21& E0 ^- ^9 d4 n7 {6 B* O
    22
    ' ?* K5 q2 a4 p$ o- s  q; s23( ]9 r; w# U3 A7 t% t! I; g
    24
    4 E) E3 B6 G, b5 Q* }$ A6 {3 F250 K+ o" Z. I6 s7 i. K- o2 v7 C0 Y1 h
    26, X, a9 O9 t2 i! k
    27: f" n* ^+ W% w" n4 }9 x
    28' v/ |* \6 O. a$ M" U& N6 K, \- c% B
    29
    . D4 m9 J: N# [% s- l/ v0 V! g, G# g
    + m6 j% N) E# Y1 q. h8 ?  L. n% L   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。2 c! w7 m  i3 A" s$ |, n3 O
       首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
    7 \3 G- {7 `( `  Q1 P' j+ [- t3 I$ D8 v; y; D; V
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]: K/ p$ X; }, J. Q- @
    bday_sum=select_bday.rolling(7,min_periods=1).sum()
    . O7 i* A7 d1 L2 a% Hresult=bday_sum.reindex().ffill()6 f/ o( h3 K6 s0 j4 \$ P3 z) ?
    result% C* O1 g( v/ c" b7 h" s) R/ L0 k; [( @

    4 V% ^; G5 `) i3 c. ^2020-01-01     -1.08 a8 R# _1 J6 b3 x9 L
    2020-01-02     -3.0, `( {: {3 u- i  a- ~* `
    2020-01-03     -4.01 q, |) p" Q; j5 J
    2020-01-06     -5.0# s5 \. Q* g1 V; y: H
    2020-01-07     -7.05 U. X; f; Y& J: @, A
                  ...  
    2 c6 C. e0 L- R; l, E5 r4 L2020-12-25    136.0
    8 h8 y! L8 \! [/ [8 H/ s8 i2020-12-28    133.00 J( \# F: z7 V0 V
    2020-12-29    131.0
    : H  ]# r% U" n7 I) U; M* g, j. u2020-12-30    130.0
    1 O, S2 \3 Z, \/ X  j5 X$ r$ C2020-12-31    128.0
    * t% T8 t7 g. g6 D0 q* i& q5 E2 RFreq: B, Length: 262, dtype: float64
    % E. U  ?) y, U' H3 D4 }, D, Q6 C- w8 x& q
    17 S# l/ G" l! k" U" o8 F
    2
    . O5 [0 q2 T& b9 B$ h3& J: T! f, h. K* I
    4
    " K! c3 @8 {- }# @3 I5& `# l- G& C9 z4 v# g
    6  r! D* U9 a; }) i6 ^
    7
    . v- o  ~: y! L0 f8 [8, H6 N; }: }8 R- m* `7 A- s* C
    9% C/ a5 }2 z" ~) ]3 Y0 v( Y
    10
      R3 |+ i# a( N6 F  j11+ ], @$ K2 W& E+ w8 p2 b
    122 C0 ~2 l- w" _  f/ e
    135 J* \( X; A- t3 K" \, c
    14
    ; f& i) u- x; G4 U157 g  f& q4 O6 m: W1 C2 E
    16" s- A! G% p/ ~) S- z5 u4 v# J
    179 l4 g* [/ E0 X1 S: s
      shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。& V8 N$ u( g( l# ^
    7 F5 y! @1 |5 [: f8 P
      对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    - T7 _' f& `- v* T8 k/ K" H8 E/ G* m) I" m7 I. j  u
    s.shift(freq='50D').head()7 s' q: k/ T$ h8 R4 t: d
    Out[113]:
    ) u* _1 j" r0 y% S! T. T2020-02-20   -1: J& W+ T1 q0 Z. g, N, F/ _5 [; `
    2020-02-21   -2
    ) q5 r  a8 S6 s; R8 C6 f9 P9 R2020-02-22   -1. B8 D& R+ q8 T% m! w* ^. o9 ~
    2020-02-25   -1
    8 W+ e5 |9 q. ^% T( S9 ]$ f2020-02-26   -2
    6 L; O6 r! \& _6 e# vdtype: int32
    ! h, e8 q  F) x. M6 z1 N, y1
    & J7 S9 t  H: J8 }4 d* l2
    . U" s. G' T- O, ]. S% O2 H3
    : q5 u4 b4 F# s& D5 X. U8 Y) B8 x4" L' V! t. U+ _, }
    5' K! I: y* O: p! e3 y- m. K
    6
    " B' ]6 W5 x: i7/ U* Z2 B0 m% d; U& K/ ]
    84 J% L* r/ @, i9 s; H/ x# f+ b! ~" n. w
      另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
      [" d5 I! {% V$ U9 B
    ' z+ V" b% \; t9 m1 ~. @3 \) Fmy_series = pd.Series(s.index)% g0 `& R: r& F3 u! t
    my_series.head()
    : h5 ?# g: f+ B' r# [  tOut[115]: 3 M. Y! t$ K! a+ J1 u6 A
    0   2020-01-01
    3 Q( a  t6 e. K. W: e+ V' d* ^1   2020-01-02
    1 n. \. Y9 V" Z& Y  Q1 g2   2020-01-03
    / @* h. _0 \/ N5 z2 A1 P! V3   2020-01-063 L3 E& `" A: @
    4   2020-01-07
    " Z8 Z; d3 h% u8 x8 p8 \dtype: datetime64[ns]& r- m# A0 p& m8 i2 p+ ^

    3 O! g5 N4 A, N, E- qmy_series.diff(1).head()
    ; R" @9 t% T5 @7 [Out[116]: 8 ?, J  y/ p+ b: N3 o6 C: k* y! b: G9 W
    0      NaT
    . p& R: v( {4 t1   1 days7 f) u  U6 t5 p& T2 H9 q3 e
    2   1 days; T, k4 x0 ]" h: ^6 j5 X
    3   3 days+ ^, b; o2 j, a, i2 b2 c6 h
    4   1 days
    9 B* ~8 ]4 `  U0 g1 c- r( U" R0 h, ydtype: timedelta64[ns]) t3 j. q$ O. d8 o" N# j9 z
    ! P# o8 B& J9 C
    1- k9 h8 e* Z2 e4 k0 M
    2% O; U: t2 n! t2 a
    3
    # r. z* t. |% F6 F1 D" j4
    # F' a; Z* x& _, S2 f: C5! M9 L) j$ y* Z8 ^
    6$ W, b. {& u! q2 \3 k  E- {) t! H
    7
    4 L) c4 ^5 M0 {$ U8 n8! c- Y2 e, w& p( w* @9 D; `- `
    91 [# w9 R: `% P* u3 q* f+ M
    10
      d. x' L; j# j% t$ T3 b11
    ' i) D' V3 S  B7 h, X6 G' v12
    % [4 }6 g/ l4 U8 n6 h13
    9 A- Y% B& X/ m+ O$ v0 l$ g/ d2 `7 {0 K& F149 ~: Z( F) u& Z+ `$ ?* G
    151 \4 I% ^0 b4 |* h" u! q3 K$ \
    16
      S0 h' J+ {" x! x1 n  j7 N1 J17
    0 m0 \: R6 ~1 s18
    & b( k  b  U1 V  \5 ~1 c10.5.2 重采样
    , Z2 T0 s9 d% B7 r5 A  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)
      L7 K0 o* Z* c6 C) @, S9 B9 n常用参数有:
    6 p+ x) f% V1 _9 o) Y4 R" A: B6 p5 c4 y4 ]
    rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    ) @  w: E) H& A( L) Aaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
    , Q% _4 \: [8 y5 ?' l' Vclosed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    ; t6 o$ z) [; K# klabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。! ^  T% w7 E7 v1 M' g  }0 o
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。  e% s  F3 O, w7 T! \  v
    on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
    4 D8 |2 l9 H- Plevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
    ' V, H' r  I! R' M" q1 ?6 gorigin参数有5种取值:
    ( `. `- t: i( F5 e% T# w* C/ y‘epoch’:从 1970-01-01开始算起
    / E, }  A& j1 X6 S" w, H‘start’:原点是时间序列的第一个值
    6 F! A* }0 I1 M. K) Z‘start_day’:默认值,表示原点是时间序列第一天的午夜。) u2 p+ y0 c/ b) f) @
    'end':原点是时间序列的最后一个值(1.3.0版本才有), c/ ]- P' K9 @$ n7 l0 U$ ?$ P6 W
    ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
    + J6 B7 i) A5 qoffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
    % k1 S5 X6 A! Z& w( a9 g  closed和计算有关,label和显示有关,closed才有开闭。
    ) `% R& [3 p) o& ]  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
    ! ]! d+ q/ g; D% e" ~7 o8 c
    3 S( v: N& p/ I) K) [重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
    - f* d) O1 ^- G7 G, Vs.resample('10D').mean().head()
    ( y" S$ s1 y: U% n! K- B+ b6 dOut[117]: 9 b0 j( f  ^2 e% Y3 s
    2020-01-01   -2.000000, a6 j& i0 e* f" k* _# b# _
    2020-01-11   -3.166667
    - r5 Y- R: k) S2020-01-21   -3.625000
    + t1 m5 Y: m( u( D2020-01-31   -4.0000003 [$ F& @5 o( u4 d9 c
    2020-02-10   -0.375000
    / ^7 E1 ]/ l7 }* e2 `" jFreq: 10D, dtype: float64
    ( {1 C6 Z' [. u% z2 p; a! f4 n1. j5 e6 J7 B6 h4 m
    2  ^& v5 V, Z0 `
    3
    0 g" j* k+ g! k/ `4" ?+ Y! I' A2 _- f
    55 Q, Z3 b$ A% B: F) s% }
    6
    / ^9 A+ o5 w- |% a! L7
    - ^# e% I  \& g# X8 K: w87 U$ X( m3 i1 F0 I* |! i
    可以通过apply方法自定义处理函数:; z: ~3 n9 |+ w2 Q( V/ W3 F* D
    s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差
    ( ]2 D9 ~4 |$ ^- z6 _' h  N
    % y% X* [5 l+ `7 \- A% L2 M- YOut[118]:
    / ^. J% b0 ^& D: q' p$ L' R% |; v4 C2020-01-01    3  F. r- [9 n7 m4 Q% a6 Z+ C7 j) c* Q
    2020-01-11    4
      f6 |" ~4 E$ K) W$ t2020-01-21    47 z$ C' e5 ^1 N* s: [
    2020-01-31    28 ?+ }4 G' S5 z; B+ R: M
    2020-02-10    49 u5 a, s! Z' F* t
    Freq: 10D, dtype: int324 q" k, \0 N, s6 x
    1
    ) A, D/ c7 B, m4 G! j  ~4 i2
    # K0 G7 D$ g" `! S9 d3$ J7 B! }% c4 O1 B1 G" z
    4, J; r' G: d: L
    5
    0 v' d5 X! @" f6
      Y* F) W) x# s+ ^" d# s) g( O7
    7 l- T. o: f/ S; Z  N3 C# ~8
    " `( H& x% T3 s! c9
    4 p# ?' L6 A/ k  在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:7 e- ]+ l: o" K! K

    / |+ p; [9 [5 }$ zidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
    ! w- o. O; |. `' ^8 udata = np.random.randint(-1,2,len(idx)).cumsum()+ u, `  w2 T! Y2 I3 Y2 p( l
    s = pd.Series(data,index=idx)
    ) Z- n1 l7 w' B" ]- s( q/ I+ vs.head()8 T2 S' r  W# I1 t- i
    ! p% l! \3 m4 L% b
    Out[122]:
    % ?! E# J( O. T6 x, J% m" c2020-01-01 08:26:35   -1/ N# X' p/ }$ l0 z' s- ^; P. Q
    2020-01-01 08:27:52   -1: F8 p0 `3 Z" R% \1 v- n9 s
    2020-01-01 08:29:09   -24 I/ N& {( D7 @$ q
    2020-01-01 08:30:26   -3# {& v6 j% G1 s1 {, g6 f' V1 F
    2020-01-01 08:31:43   -4
    * s0 u9 O. ~* JFreq: 77S, dtype: int32$ I; `2 Q+ R; P5 c, Q
    14 B+ H* C/ @# m/ \, Z. R3 B
    2
    0 E5 d, [4 L3 v8 @( U0 D; E6 ]35 g+ l" T4 T2 o) ~# @$ ]
    4$ X% Q2 N# K7 U( \: _1 B
    5
    : t* s# e3 z" Z1 }: s6
    4 K( F& x! g5 d" b; `( f7
    , R$ }; M) P5 l- L# Q  j4 H6 g- K8% T* {1 d0 C) R9 B+ r" I6 N6 U( W
    99 D) U4 k9 O. V8 @0 W; ^2 n: d# o6 X
    10
    ! |% T+ z& ^. h! g! {5 ~11# r9 A* I3 i7 L$ l: b% a7 R# G; e
    12
    $ d2 V' {: c  s9 R0 h# X/ u  下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:  k! k/ [2 h7 W, T
    0 H7 E' |+ Y" i" b
    s.resample('7min').mean().head()7 S+ z. P4 [0 n8 E1 ?* k3 f
    Out[123]:
    ' N& F9 V+ |, j* Q( |( _1 \2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值
    : H" G2 G+ K% O2 i4 d9 ?2020-01-01 08:31:00   -2.600000
    1 G" Y; T' U) Y$ d2020-01-01 08:38:00   -2.166667
    * n: l* x2 `$ _6 F- H2020-01-01 08:45:00    0.200000
    2 X3 f/ n+ h; U* v# }' F5 Y2020-01-01 08:52:00    2.833333" O1 [6 ]4 o! G9 G0 k7 U
    Freq: 7T, dtype: float64( v; Q; R6 a9 n+ S2 |
    15 O" U, |* a7 y- J, M! z5 A
    2# _) E: Y( n( L; c3 P
    33 C( o! Y: l$ q% F
    4
    2 w7 q& u! G. I. k9 |8 W5% y7 y. V0 E4 s8 W. t' y+ b
    62 E- |! c: u5 Y3 Q9 k1 f
    7
    5 x; \# _$ f" `( Y& n8$ @% \1 e. \0 N( k" C% \
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:+ z* a1 k2 B3 f9 ]7 Y3 i

    ' h3 j5 R" v& us.resample('7min', origin='start').mean().head()
    - E7 Z7 Z" s& o: U1 Z5 MOut[124]: , U8 o# x5 L( T5 r; R/ |! x  P* g3 L
    2020-01-01 08:26:35   -2.3333331 w- a8 E" b) [( y! e; t, j
    2020-01-01 08:33:35   -2.400000" }2 v  x  y; }- ^
    2020-01-01 08:40:35   -1.333333
    ' t( i& f$ ~" c" v2020-01-01 08:47:35    1.200000
    & C5 D# S9 Y! B! o. Z2020-01-01 08:54:35    3.166667
    6 q4 Q% r: s% r5 U% c4 _Freq: 7T, dtype: float64
    1 X# g7 f- x1 i7 [0 X; L7 w4 x( H3 @2 A14 g  T" D( u7 G9 f# k% c9 i( _
    2- G4 @+ {6 A$ o* P* A
    3! f# l( d( D( M* s" `5 W6 c3 u
    43 j; x- L4 M2 W  @1 A/ F- H
    5
    / K2 I; [* y( X0 B9 \! _, J6- H  B" u' b' Z% m$ a
    7; m5 r$ T$ `% C% V" Y! l; e
    8& ?  w0 ?- P  y8 `1 _& c
      在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。/ S7 i8 S' H2 H
    - Z7 ~) a# Z* u8 C; w' `3 F
    s = pd.Series(np.random.randint(2,size=366),# O8 q# B5 o; i! X4 J4 @
                  index=pd.date_range('2020-01-01',
    , V9 O) h! A( r: M                                  '2020-12-31'))
    % R7 I1 k! ^+ ]7 i/ Q, Y, Y1 O( g8 X# |) O$ a8 Y- t

    9 ^& A, N, M( Y" w- Z! Hs.resample('M').mean().head()
    ! y2 b, H( C" _. ?/ e; G, XOut[126]:
    ! H7 S, d! a% ^/ v7 w. v2020-01-31    0.451613
    ' r& n1 ~0 G# ~) U( U  v2020-02-29    0.448276$ Q8 V5 H, L) ]6 w* I$ @' Y* Q
    2020-03-31    0.516129
      G/ v. Z$ j% \: C2020-04-30    0.5666675 _2 V6 h$ c+ q! A4 B# ]
    2020-05-31    0.451613
    2 j) D1 I: G+ g$ [% x/ ?. CFreq: M, dtype: float64
    # @9 e( k# H7 C- |: ]" D' Z3 ]7 v  f
    + C+ s& g* o1 l' Os.resample('MS').mean().head() # 结果一样,但索引是跟正常一样; m4 i& X& ]9 G( i9 ~6 @* F
    Out[127]:
    ' A0 O& H' t3 w. q2020-01-01    0.451613
    " }8 X) ^# A- G; K2020-02-01    0.448276" X$ L, D1 @. U& ]" b! K" ^8 D. {
    2020-03-01    0.516129: c. r9 g0 n/ e2 G) W7 N
    2020-04-01    0.566667- y9 f+ L5 f& U& O9 e, @/ G
    2020-05-01    0.451613
    : M+ r# g! R8 p0 k& b4 mFreq: MS, dtype: float64
    4 D( Z0 K" m/ z* O4 C- ]/ p' ?; |% j/ f' d* w
    1
    . _+ k' b' Y( c9 g& @5 @. s# P2! G# [) Q1 @9 r6 [. J: w7 D
    3
    + l9 l' ]- V' A* i% s% D% X4
      q2 H) L9 o) |5
    7 T/ T# I( s' A6 Q3 ~& n62 q7 C8 E3 X5 J5 C* p, F# `0 u
    71 A4 G. b( A) j8 ~  b. d
    8' {9 a0 K  P/ H
    9
    * Q9 I8 T- A$ d2 T0 \/ E/ T10+ M1 l3 }% d- n; s$ D" y
    11
    9 x# O% T7 x9 d/ u$ y, m: J6 O& N7 X12
    ) s' G5 I) x( E$ l7 L3 a: C137 {2 |+ M# q; }! t2 ]5 ?
    14! y4 {4 s% ]7 S  C
    15
    5 X# m% L; Q9 l. {7 F! M1 `16
    3 C. `8 k+ z& G5 _5 ~! O" Q+ i. i17$ J  i+ q; F- z& y+ q8 z# E
    18' H) _0 t! F, D" C' ]" ~
    19
    , f; l9 h2 Q9 W! w/ ~3 o  k0 C+ Y20
    # L8 R  ?) C7 |& Y217 R  n- y7 E9 U. K* ^" {
    22: \6 E" S& G3 z4 Y5 l! g
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
    * q3 L, ~5 s3 wd = {'price': [10, 11, 9, 13, 14, 18, 17, 19],: @% l9 o: F! M# H
         'volume': [50, 60, 40, 100, 50, 100, 40, 50]}. W9 F/ I, c( X+ F: k
    df = pd.DataFrame(d)
    2 l: K6 m0 {6 h' {, ~df['week_starting'] = pd.date_range('01/01/2018',
    & {! a7 d0 w3 p7 L7 E                                    periods=8,
    - c, D2 C. u$ C. C8 L: Y                                    freq='W')
    1 ?% [$ N' h& V% C/ Mdf
    / f" }4 C/ M6 w1 h, c  D   price  volume week_starting
    ; A3 |% P0 ?' ~* C3 N+ N0     10      50    2018-01-07
    9 y0 O! X& S( Q. R. j1     11      60    2018-01-14  }: g5 n% e* Q. s
    2      9      40    2018-01-21) z3 j5 q9 v0 n' l0 \1 n
    3     13     100    2018-01-28
    , @6 B# \0 b& T" a0 j5 o2 P4 C4     14      50    2018-02-04+ i0 Q' Z& g  o% R. s& j9 G
    5     18     100    2018-02-11
    + E  [3 N* N  T4 L  b6     17      40    2018-02-18
    7 |8 c$ t/ Q3 h8 J. j) z7     19      50    2018-02-255 t+ }; f' z5 M! z
    df.resample('M', on='week_starting').mean()
    ; |  @$ D3 b0 D9 Q               price  volume
    . j& z* H+ o  pweek_starting
    " N! P% x2 @  q6 z$ t( U2018-01-31     10.75    62.5
    6 \' o; |, u7 h2018-02-28     17.00    60.07 z# {8 I4 y$ F* q( I9 L8 `+ \

    9 G+ I' {7 m$ E* U8 N3 U1/ |; b) e/ q( ~, p% P
    2  ]& J( k) S3 E* L6 U+ h
    3
    3 i; l4 l$ d3 g% z2 E4
    / Q. I5 j% L; F" g) k$ O0 Z5 T5
      x/ c' p1 L8 G6$ Q6 L% r1 M- `8 Y7 m% W5 W( T) Z8 T6 h  u
    76 v) W; b$ o( r' [1 b7 f- P& F
    80 Y: Y6 M: n, ^2 ]
    9
    1 }; c( Z6 v1 j8 K* e- |: g102 O7 m6 {& g5 ?& i  }4 h
    114 C% M3 B$ ^0 N5 l( B3 U! f
    12: g6 i3 X. z1 G! A  `6 \
    13
    8 P, ]1 a) g  v% d' M5 Y7 s1 `14" U, j7 k2 E8 E
    15
    8 E" D3 d  p+ R( H& w% I16& E1 |$ o5 ]% y0 W
    17
    3 ]+ g6 i3 `5 b  E& U! l182 W0 i8 w* _0 ~0 _3 g
    192 t  J; _0 N2 s  o3 M/ \
    20! ?4 \. ?( O# O& }3 F+ P
    217 t3 {6 z& G. y: i7 I; A: e7 T, s
    对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。
    0 i1 i$ y7 Y2 fdays = pd.date_range('1/1/2000', periods=4, freq='D')' Q, o- d6 F1 g
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    % h+ ~3 @3 y- [9 K: ~3 w      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    5 N0 M3 X; }0 \; Ndf2 = pd.DataFrame(" w$ T% ^6 J. `9 S& e  q
        d2,
    7 ]2 |1 N7 m& c! [4 W9 m4 }. T    index=pd.MultiIndex.from_product(. w+ d- U3 F" u- V! u: f" v  E7 Z
            [days, ['morning', 'afternoon']]
    ; z% G0 B7 h! x* d- g% @    )  d- x1 ~7 u" o
    )$ w1 z$ c1 Y2 A, g7 O
    df2
    4 Y5 q2 `; V1 r' e) j5 c                      price  volume9 O8 W, c% G: K  S( y
    2000-01-01 morning       10      50
    % y5 ~  v) h- ^# C           afternoon     11      60  T) i8 \/ f0 p8 k( G5 D- Q
    2000-01-02 morning        9      409 u9 o! a  @/ B" y
               afternoon     13     100
    6 @7 l& f4 k0 J5 s8 P$ i1 m1 b2000-01-03 morning       14      50
    # D+ e; ?1 h1 I$ A6 `4 A, o4 Z           afternoon     18     100
    ( I( a" D& |- R' p+ D2000-01-04 morning       17      40" k) ]1 G1 Z2 |: V7 g2 m8 |
               afternoon     19      50, g6 t7 }$ K$ x$ }% A
    df2.resample('D', level=0).sum()
    * v) K8 u7 N1 `6 n  D            price  volume
    , b' q4 \+ m% r  D' f( ?; H  p2000-01-01     21     110- [% Y; v+ s8 Z" T- [
    2000-01-02     22     140
    ! ~2 ^6 a5 G% y6 h! a2000-01-03     32     150
    ' H' ^( B  b, F; {2000-01-04     36      90
    $ U% p- P/ f3 @) J+ ~+ k8 P# ^
    ' [1 X2 y3 c/ v7 k% |0 S; w1& X9 P( T; x) I/ [# ?
    2" Y) ?1 g2 D7 ^& t+ _) v( C3 o( c8 f
    3! _5 k3 F$ u" R3 b' V
    4
    0 r/ h% V/ f1 ~57 ~, y4 `* Z$ c, J7 ]3 K5 U) F
    6; I0 s+ ^# ~* ^% X8 Q$ ~, f
    7
    9 X! t  r  R0 }# b. A84 s6 h) L% r& n% r
    9/ w4 |4 v. ^1 `0 }; C+ I$ y
    10' x# S' c' k9 g, {
    11' w, n; Z3 W) P# S7 a
    123 x& R6 D( u# O) i4 ~2 ~
    137 o9 [9 H1 h8 J4 _- }
    143 c* v4 A7 U% |! u1 t& Z
    15* g! q3 T; y; [; A, N6 i9 b2 M3 U
    16
      N2 l2 _3 g/ }# Y/ M) ?17
    ; `* R4 _) }  M9 F18
    * i% `: W1 C- R! l19
    + E8 E, h8 \1 t; M20
    6 V) y/ o/ f: d( A; D0 ?3 l21
    3 T4 A/ j# a0 {( f2 I22
    ) L. J' w- A; R9 z. a7 i23" h3 X6 P1 Y( u' h4 [) ^7 z9 k0 C
    24! F2 ~* V1 J4 {" k6 j0 e2 v. J
    253 v% G* r. G5 i/ V; v2 i
    根据固定时间戳调整 bin 的开始:
    3 |0 h  R" U' F' }$ d* \7 ostart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'4 `; S5 O+ J: T- v  O
    rng = pd.date_range(start, end, freq='7min')  d  A! X) J( R" r5 v& I
    ts = pd.Series(np.arange(len(rng)) * 3, index=rng)3 g! J% S( S% n/ X
    ts# v* B1 i# d. g% G
    2000-10-01 23:30:00     01 q' p* {  ]: U7 [5 Y0 I
    2000-10-01 23:37:00     3
    $ Q5 X4 i8 P1 H2000-10-01 23:44:00     61 @' o" b3 V5 |4 c2 m1 `
    2000-10-01 23:51:00     9. \- R. t1 O; F' D) e
    2000-10-01 23:58:00    12
      a: w6 K7 C( s) I- P+ y2000-10-02 00:05:00    15! D1 Q: {: C6 _* U7 u
    2000-10-02 00:12:00    18% Q4 W7 ~4 i3 G" L6 C
    2000-10-02 00:19:00    210 A1 Q5 o( L- U  A
    2000-10-02 00:26:00    24" ~- R1 b/ E% Y& E
    Freq: 7T, dtype: int64/ t% b3 \, l6 W! L% u

    7 s, _3 t2 u2 `ts.resample('17min').sum()
    " U' y9 ]7 c# z4 O/ L( ]9 h2000-10-01 23:14:00     0
    0 Q5 Z1 ]5 |3 L, j& W+ E- J' ]2000-10-01 23:31:00     9
    7 t, y9 j% }; S( S/ G. `' p" G2000-10-01 23:48:00    21
    / j1 Y" r( W: j/ ?% ?2000-10-02 00:05:00    54$ i* ]3 V7 ?. {! P1 S2 ^
    2000-10-02 00:22:00    24
    5 K- R7 g! P" I) M1 j- {Freq: 17T, dtype: int64
    % a0 g. v9 ~, A( O: y+ D, J  g7 e" v8 o3 \- A1 G, n  s; g
    ts.resample('17min', origin='epoch').sum()8 q% P  f7 U9 c/ a7 E6 y
    2000-10-01 23:18:00     0
    9 U- M+ J; P& }2000-10-01 23:35:00    18! D& r7 N. P( G0 L
    2000-10-01 23:52:00    27
    % X: k& t: F- [1 J8 A' [# O2000-10-02 00:09:00    39% `  n& @. m( c/ x: {
    2000-10-02 00:26:00    24
    6 `$ l- D+ b% U; z/ ~+ F( FFreq: 17T, dtype: int64
    ) {! ]" l; d9 l- L/ C7 \# ^" V
    / y4 \% @4 Q5 e. g; hts.resample('17min', origin='2000-01-01').sum()
    3 C5 w; U) X: s9 P2000-10-01 23:24:00     3; E' k  Q2 ]4 b! N) o
    2000-10-01 23:41:00    154 X8 _. p! p7 z1 Y, Z% ?3 v, E
    2000-10-01 23:58:00    45
    5 W5 S4 t- f, T: m- D: n, b2000-10-02 00:15:00    45' D: K4 p7 J6 e. y/ i: ]+ a& w6 n
    Freq: 17T, dtype: int64
      q5 I6 I2 G& F- y
    " e* u4 a" X" C+ m" h4 Z. O- P2 P1
    ) ]  k4 h& f2 Y- t) G9 L' R0 s3 ^2# ?- i0 H" G; Z
    3& q1 C) q9 ^6 ?; U  [6 _
    43 a  z; y$ t$ q, x% Z0 B; L
    57 G; L, g9 b1 E+ T" ]5 E9 q) o
    6
    ! g, I5 S+ w0 Y- \7
    2 C8 N) h7 y/ A" f; O8# f% j/ P: c; O, D( s; T
    9, }! ?( N5 [; H5 B* u
    10
    * g7 A- [2 m, K& w) E3 J4 _11- \* R  n3 O5 ?6 f
    12
    6 ~2 L0 c3 Q+ p/ E+ V3 u13) y; M3 _+ e7 l8 S' u. F
    14
      H1 c- {7 c! r4 `0 ], ^15' a# l" u3 {$ Y1 `, W4 w" k
    16
    7 u7 e  Y. r% L1 j. p( V+ Z173 Q& p: L  l2 E7 `4 `4 Z
    18, m; ]; U3 v4 y2 f1 U" \7 [
    19- F6 A5 b( P2 C; E, a9 S
    20
    4 g; ]  r( i1 a3 [0 {4 r) {21# t( B6 D) B$ |6 S. \( d
    22
    ( O5 d/ f2 J% b/ A239 k' s7 D& H7 k
    24' n7 y+ L, X4 y# i) m9 R" {, s
    25* k0 o, P8 [1 T
    26
    " z  c  e/ h  C' q) h27; ^3 z* A1 W% c% H, `
    28' D3 h5 U! U* |* y
    297 w, `8 C$ O! O/ S+ }
    30
    + j5 ]/ Q! ^& s& S3 j, e* Y: K31; `% C+ u! n, r0 t
    322 {0 y( x' a( ^9 _
    33. k$ x7 m9 ?. S7 s
    34) ~2 o6 t+ s& z; R1 v3 v$ a- ~9 V3 G
    35' [4 Y3 y1 G) R- Z( `, r5 h" T. u
    36
    ) {% `2 O) q% H% h7 w8 g  H- ?2 y. K37; {4 n! f  c( [- ?$ F
    如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
    % J' _# K- v; s9 H# ets.resample('17min', origin='start').sum()0 C2 Z$ V& V6 p7 b& `, M  e
    ts.resample('17min', offset='23h30min').sum()
    + Z. l$ r2 ^  N) T% d- H9 g2000-10-01 23:30:00     9" @) v) D/ a! b7 O+ Z+ w
    2000-10-01 23:47:00    21- d, x+ k6 I8 m( }
    2000-10-02 00:04:00    54$ r# e& U( p9 a3 l6 M% A9 c% t5 ?
    2000-10-02 00:21:00    24
    5 ^% m9 S! q: }8 l, A5 A6 MFreq: 17T, dtype: int64
    6 Z" ^3 |% n/ `7 W' s1
    + w& x- b9 x% H3 \' \2
    $ k$ w- M/ B2 a6 d' G! D: D4 z$ `3& T) ]! I! |1 J  x' s0 z* J* }
    4
    " q6 t; Q" M5 }/ b: e5
      p6 z' i! w$ w4 U% d- a! O7 E6
    # n) B% b. p/ q7
    ' d3 Z  Y! K' z6 J+ Q% E2 p10.6 练习
    & L! D; D: ^+ t/ @Ex1:太阳辐射数据集
    ) W% m2 S+ Z4 R' |现有一份关于太阳辐射的数据集:. z' [& {, l/ C" ~2 T& i9 e

    * i% [& A( }) y, X$ \2 b& Y7 vdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
    2 e; K4 @) g' [0 L- sdf.head(3)* C3 S6 M7 O' j8 h3 C& x; F9 _
    4 Y/ E) M4 s+ V. a, r( `
    Out[129]: ; @! \' W: {0 {7 g2 ?7 ~/ I
                        Data      Time  Radiation  Temperature8 V' J6 s9 ~/ m5 p8 x* t' P  d3 j
    0  9/29/2016 12:00:00 AM  23:55:26       1.21           48, G& f, t8 f2 \* T. R
    1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    ! V6 C' h/ z5 X$ G$ W2  9/29/2016 12:00:00 AM  23:45:26       1.23           487 D+ q1 b& j5 N. I/ q/ o
    1
    $ r% s! t+ v$ D* X) N! ?( i21 n4 A: E$ K1 |# T8 k/ O1 u. Y
    3+ H: h  n! i, m- `" W
    4' O  L, w+ z3 N9 Q
    5: }6 I& L: v) a+ y/ j! C3 {
    67 \. v) Q: I" m' h
    79 W# q" O4 ^8 a3 i4 M. @1 A
    8' Z8 r/ _" }) M4 B0 H) s* x
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    $ r; d' K5 U% N( w1 \每条记录时间的间隔显然并不一致,请解决如下问题:" U$ i: h! x# E( y' [! H# ~6 D
    找出间隔时间的前三个最大值所对应的三组时间戳。& o- Q/ I3 _( h
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。, \# d% p+ p1 X
    求如下指标对应的Series:. m5 H7 _7 d' X* H; A- X
    温度与辐射量的6小时滑动相关系数
    . p3 J1 |2 X7 k  }9 C" U1 h以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    % f: _8 ]: |" e每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    ) j  u& o4 s' I4 simport numpy as np+ F4 p( q) k* h, e
    import pandas as pd) M  e$ Z$ y+ B! F$ P( Q
    1
    8 `$ R! i: o$ M2; i0 n9 s: Z3 M
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    0 k5 i4 m$ B& ]* ~+ m& v4 Ddata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列; i1 L* h0 `0 w# I7 q7 O1 e
    times=pd.to_timedelta(df.Time)9 F$ t3 y+ {: {' Q  a
    df.Data=data+times, S% x$ B9 d7 @, X# p
    del df['Time']: w  Q2 ]  o- u. B! T
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
    , t. Y) n9 h! G6 |8 pdf2 d4 s) h0 S' ?) }* M, f0 I* Y
                                            Radiation        Temperature9 _: O2 A( h# ~5 x* P- N
    Data               
    : D" r9 ]! Q' v, ]' Z/ }: n  V2016-09-01 00:00:08                2.58                51" D/ @5 ]- o6 ^) q
    2016-09-01 00:05:10                2.83                51
    4 s) M, e2 u) n2016-09-01 00:20:06                2.16                51
    + m& w3 ]2 M% Z* N& R+ \, @2 [- x2016-09-01 00:25:05                2.21                51# `0 @- R- c1 P5 g4 ~$ [
    2016-09-01 00:30:09                2.25                51
    6 ]+ Q9 F. A* L" [...        ...        ...  y' W: u5 R3 t/ N
    2016-12-31 23:35:02                1.22                41
    6 ?" t  F0 _8 Z  V1 ]& |6 ]2016-12-31 23:40:01                1.21                41, h8 \' E% |3 ~- X* O
    2016-12-31 23:45:04                1.21                42
    / ^8 }; `3 Q; Y4 M2016-12-31 23:50:03                1.19                41
    # O* u. V* p; E2016-12-31 23:55:01                1.21                41
    6 q: N9 q* u( m/ T, U1 k/ Y# M; q; h# ^' j( B
    1
    & Y" c# e; T" \. ?) j2+ E8 d9 D- \3 K
    3
    7 t; |0 o+ w. b, F! c( {44 u0 O1 D5 ~& X/ }( o
    5
    + V( E5 ~+ @8 z6 s0 |. }9 h6
    ' X2 h4 O5 X* Y  E# X7 V  H( \7' K  _6 }, g/ [3 X$ L0 T  m
    89 d8 p4 M. p( t
    9$ u' O; p1 f. p3 c+ K
    10
    . ^& R  o0 b8 C: k8 _11
    ; ]6 e, G' g, k  l( ?12* F9 A- T& _1 x
    13. L+ c; Q+ [1 {8 l* F
    14
    ; K! u( `) C( Y; {15
    : N/ M0 e6 X5 ]( e% S16; N, ~& w5 ^# T3 P
    171 i( h0 D4 j3 Z3 J0 X. r# U- w; X
    18/ D4 W/ b; C8 P0 e: p
    19
    2 G+ C4 N  b' L+ H/ }: [5 m: s) i7 z1 o每条记录时间的间隔显然并不一致,请解决如下问题:
    7 X, d: j2 b& d. T1 Z( x* b& `找出间隔时间的前三个最大值所对应的三组时间戳。
    . ~6 \9 l( i/ ^4 O# 第一次做错了,不是找三组时间戳! @6 M! n7 o. d8 V9 z
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]! z5 w" j# o8 m  R, s! L/ o
    df.reset_index().Data[idxmax3,idxmax3-1]
    + H) c2 c+ J  o; ~/ K# J) Y% @. Y4 Z' T$ c: @) N' T
    25923   2016-12-08 11:10:42- B8 k$ B- M! L4 s  Y7 `
    24522   2016-12-01 00:00:02
    ' z  p* U7 r7 J+ q0 U7417    2016-10-01 00:00:19  a0 A8 }. m$ r0 x0 k' i8 n
    Name: Data, dtype: datetime64[ns]
    5 o  {" C/ }3 l  `1/ o9 J& O# D* N$ l7 l3 b
    2
    ) U0 J2 `2 W8 O/ c' z" ^: ^3
    3 ?+ A+ }; Q, q' X: ]# C" e4
    5 w/ o, Q5 [+ c: [' A" _0 c5
    % C/ j" A3 O6 o/ Q/ A! G% M2 ~3 F6
    + w; \9 v1 o. M' i# b7
    4 p) L7 Z* J, `3 q! C8
    6 W/ w. M3 {; r, s# Yidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    : ~6 E% T! Y/ C2 o5 B; wlist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
    4 N4 k( s4 Z7 a6 Z! ]! ^  W$ S8 x/ s# P/ Q8 L% j1 t0 m, Q3 J) L$ [. H
    [(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),& G+ d9 g" u2 G! x( h
    (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
    ( Y( ?' E$ Q1 v! h4 o& N (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]  X- I" ^( I, ?! m' E% a
    1: i  z7 O- E$ Q7 x: ~
    2
    - C9 l' W8 b& l1 S" r3/ x- k4 }3 [+ f8 p1 J' i9 n
    4" t$ ]- z! m, T+ E, c& L
    5
    * ^+ |7 b: r' L) Y& A7 B8 A8 c61 v, D; ~- _" a, z5 P9 c6 D0 {
    参考答案:. C+ P6 v. C+ E+ _1 x4 n

    % }8 B6 X. x5 q! ^1 hs = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds(). J4 n4 j, c1 C6 p, w
    max_3 = s.nlargest(3).index4 r- G1 O  [" R7 ^  d5 i% K* r
    df.index[max_3.union(max_3-1)]( s% o. ^2 Y3 Q$ o
    % _. M7 t3 l1 }3 y/ a
    Out[215]: ) c; Q4 B$ H! C. D' a  P5 ]
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
    5 r* m6 S$ @5 k5 y& T               '2016-11-29 19:05:02', '2016-12-01 00:00:02',( x7 w2 L* s/ ]1 k* }4 e7 u
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
    , s% R* r8 f0 }! C, p              dtype='datetime64[ns]', name='Datetime', freq=None)
    ( C8 O6 z, P  o0 C' k+ [1+ e, J& Q% ~, L) D
    2' X# @. m$ Z: t9 X4 k4 ~: @
    3
      D( l: ?3 d& A) a7 E) z  X- w4+ D" J. q2 h" L# m
    5% `; c" ~  N! i" P- B8 P* W7 p
    63 ~& z4 i1 Q$ O. E# L7 `
    7
    4 m# E% P6 K+ o2 m! J% }1 J) S0 m4 |8  T) _  V- Z7 F4 [: L8 Q4 Q' }8 t
    9
    4 ~' p- ?6 Q/ y! z1 d$ z是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。: f4 t. O* p: _
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
    5 u+ @  M1 D+ S. cs=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)& t2 ^, E+ V) ~1 j  L
    s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05), V9 |5 S7 }4 Y6 [2 ]

    $ M) }# q7 p8 i+ r4 R# d9 J(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)7 \  K: \0 p$ \) ?7 @! S2 V' x7 k8 z
    1
    - o& p* e% L/ t, T  X4 U' m+ p2
    5 k0 ?' H& H+ `9 r0 ^( f: {' ?1 J7 Q3. z8 E% Q) ^4 Q# c- i- _
    4% y6 H2 G8 v& ~" @6 c
    5
    4 @- o+ w" P* B2 z' O%pylab inline/ N4 h# e4 N8 j, @  t
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    ; j5 o" M0 h* a; N" oplt.xlabel(' Timedelta')8 J. R/ V2 \1 i7 X& H8 a- |& P
    plt.title(" Timedelta of solar")
    3 L- V3 U5 `; F1 g1 g4 d16 F- H" `. }) p$ Z" ?# q+ g, ?1 y
    2. c. n4 M* N6 O2 f" u
    3) _3 W9 @* Z4 C2 Z7 W, ^
    4
    " _$ d2 C3 j9 U( {- u' F! M, Q
    : u1 c9 u4 I! ~) p- Z7 }* z0 `& q# O; e) R: K- k2 n
    求如下指标对应的Series:
    # Y6 w; \. m( |: V+ D3 v$ Q温度与辐射量的6小时滑动相关系数
    - S! d+ y" E7 C# O) [' b& V以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列, D) T8 J* g) ^$ Y
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    " [) b3 v( }1 \0 [8 f/ mdf.Radiation.rolling('6H').corr(df.Temperature).tail()
    3 A! O6 }1 F& T( ]6 X5 w. D
    . T9 t! `; ]- `# c6 [Data6 z& c" J# I4 }, c0 d
    2016-12-31 23:35:02    0.416187  L) M0 W7 r) y0 ~" R. B: k
    2016-12-31 23:40:01    0.416565
    ) D2 r6 e( ?: ~+ \2 I/ v2016-12-31 23:45:04    0.328574  i( i/ ^8 O: d. f5 F) ~* ^
    2016-12-31 23:50:03    0.261883
    % ~; [3 j3 [) B  i0 b% ]( P2016-12-31 23:55:01    0.2624061 p2 ?9 E( N$ A& N9 ^+ h! v
    dtype: float64
    , v6 y% r3 g; j3 [: U1
    ( Z4 D* m3 E. \5 m' V0 c2
    5 c. F* r+ _4 W* f' C5 y6 K3& A+ R$ g& I; w; c( j. R
    4- o7 k0 C; w3 h8 M) G( W# [6 |
    5
    7 L; O( X9 [& g+ H60 F. n  j7 d& q+ _  s
    7/ f7 C  n+ _2 z" \7 N! {
    8
    ( }5 a0 M! e9 `8 E2 i* _4 U9( k6 G! }" h# b' L
    df['Temperature'].resample('6H',offset='3H').mean().head()
    ) e1 T  s& N4 P& U- |# C8 l% Q0 a- D; r$ M: u0 j5 i
    Data5 Z  v/ p* c8 I- S) ^9 Z; o
    2016-08-31 21:00:00    51.218750
    3 H( p: i4 L, c/ U2016-09-01 03:00:00    50.033333
    ; @4 E9 t! A9 V  }- n+ e- f2 m2016-09-01 09:00:00    59.3793108 }5 ~; p, ^; ^; ]- A
    2016-09-01 15:00:00    57.984375( O' B* P; v& U: D3 @7 Z
    2016-09-01 21:00:00    51.393939& }" T$ a9 w% A
    Freq: 6H, Name: Temperature, dtype: float64
    8 C" U" z+ }( e16 Y( I  }- v" ?' g3 m8 k8 ~
    2
    3 w8 l7 M3 h3 A) @( s# D# `) l3
    5 B" o  m9 l, d46 j; e5 a2 G; p. s
    58 q, u! D  x- M4 W# Y
    6
    ) U0 }7 r  {) v5 R5 V7
    ; p" l% z6 ?& m- {0 S8
    ; w+ O6 {$ {! r9/ o" o' {* j( r6 Y8 ?
    最后一题参考答案:
    6 v' H2 R  A. l1 k+ k( u+ a# A, g0 v; t! p
    # 非常慢
    $ N  s3 c& d2 _) b) ~! O( imy_dt = df.index.shift(freq='-6H')* u' Q: `+ V# n0 S+ X5 t- @) r
    int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]% ^. ?8 m! n! H2 K8 s, [
    int_loc = np.array(int_loc).reshape(-1)
    0 V, \8 E5 [0 N8 s5 F4 tres = df.Radiation.iloc[int_loc]
    4 E; i: Q0 n/ D4 \res.index = df.index
      x; @' M* {8 e* \) }0 cres.tail(3)
    & P9 h8 u1 J) F7 Z( a3 l& ]1
    % k  n% `. @6 ], d" E3 B+ p& b2
      S" K- N! d* L$ P+ Q3
    2 \3 L* ~  ?# b/ z  g47 G& h% `5 o3 g' l
    5
    ; h9 ]0 r8 K2 i  s3 Y3 f  [% G# x5 w6
    & |' D4 X9 N2 O; F( j" m! t7" l. t! A: F3 O3 U* h, k3 x
    # 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    " S7 J+ _# V! q$ K  E# T' t$ k# itarget = pd.DataFrame(
    ( y0 c3 @4 C* j    {
    , `' P7 }$ g: S2 S/ q( ^% j/ ?3 S2 I, n        "Time": df.index.shift(freq='-6H'),- V; F# P, w2 h$ I) D
            "Datetime": df.index,
    1 E6 P4 \# w7 G. R& [    }0 Z6 D4 t/ j& ^% L
    )
    0 _- r* @1 Q' l% |' y8 t6 L# m- q! Z
    res = pd.merge_asof(& b% Z1 I: S. k8 M
        target,
    8 t" X; l2 c, w4 r! @, h- ]    df.reset_index().rename(columns={"Datetime": "Time"}),
    7 _# p0 O8 {! p' j* [: m6 v    left_on="Time",( Q' d/ f3 F. j9 k# b
        right_on="Time",
    4 K  U! m7 r, ^3 F8 R1 u4 E. Q    direction="nearest"! U; V1 {/ q% K' X. e% s
    ).set_index("Datetime").Radiation3 }! \) [8 y9 \8 Z" H( v- x

    " b# G" {1 P1 W9 r0 M) p. O. vres.tail(3)% R1 _+ w4 W) z/ A# k+ y' f. e
    Out[224]: " E% \3 w. C( b) C! }+ D
    Datetime$ `: k2 D, v8 q9 g* Y7 E" B
    2016-12-31 23:45:04    9.33  [" \" l4 x. |: M3 U* N
    2016-12-31 23:50:03    8.49
    % O, T  \5 R( h; b) ?2 Q2016-12-31 23:55:01    5.84
    ) Q0 k; g6 E6 q& U# i. P/ VName: Radiation, dtype: float64
    % U9 e/ W# Y. y9 ^) C; E! e$ F: a! j3 [
    1: s  j3 s5 G! w, I1 u7 U
    2; }1 S) Y3 i  Z% G# |
    3- h, F6 ]9 v* O4 t+ t8 W
    4  F4 u) `. H) V
    5
    / Z2 h( W: b1 \# q- e; \66 S) a: Y- J  p. F4 e2 `0 i
    7
    2 ^$ X6 i$ g( J3 b+ b8+ O. g( ]0 T$ t
    9" T; H# v5 f: t' t7 J
    10
    & G1 B  [6 w8 g. i111 P# Z% n9 k: y8 h
    12
    . Q# B  q* q# L- w& ]" ]13; C+ c9 B. l$ b% {& V
    14
    , y7 E* i- S' M  u$ z7 g155 v: j. Z$ a1 n' y
    16
    * g4 L- q4 I$ I" F' R6 z7 p17
    5 B/ `( v- a  x! B18, U2 H" ?: f8 X8 B8 S: o) k" h
    19
    ; H$ v0 J; D6 f# M8 {) H20- S7 X' K# {5 O  b7 ]
    214 t9 A; s) {- s1 @( @6 c1 u
    22. O. R5 l% O) ]5 @: \; D/ V! W0 _
    231 U( R+ c2 g6 x  k5 V+ l
    Ex2:水果销量数据集! o; ]! j* V$ b- a' A: @3 o
    现有一份2019年每日水果销量记录表:
    9 X: {/ t. G& d" q# y, D; m4 p& g, D& l
    df = pd.read_csv('../data/fruit.csv')/ H) L2 k) O7 P  Q" K3 f- O$ m
    df.head(3)
    ! N6 H- a! K* {! F9 a0 Q, J/ S" E" Z9 {4 i7 x6 i6 N; {# n" t
    Out[131]: , Q* ?: i7 _) ?% p# K1 T
             Date  Fruit  Sale
    / }* {* [" ?# w( g) t# }0  2019-04-18  Peach    15
    ) f4 D8 u+ l" z# `  M( Z) ?- B1  2019-12-29  Peach    15: E/ ], X5 U* C
    2  2019-06-05  Peach    19
    . z& `9 S* c  \& o$ x" |0 f3 Q1( Y+ m& W' m9 ], |5 u
    2
    ' z+ B# h' F: r$ K1 O. |" g; h3
    6 W( ~% i  d$ q' r8 \1 F4/ L; d5 B! l4 @  p; e3 H/ c+ w( N
    5
    6 s1 p8 i  i" i% }6
      M% Z$ S3 Y  ?% }  M- a5 {7' V, _  A- X1 C6 d! I0 g
    80 U7 e* J. ~$ o6 f+ {, A
    统计如下指标:% Z7 i% D- c# J: R& @9 B
    每月上半月(15号及之前)与下半月葡萄销量的比值/ L1 _) G2 b  q& v- h% @
    每月最后一天的生梨销量总和
    1 M% G/ a7 U. E, f每月最后一天工作日的生梨销量总和* R7 H- K& v" v* o' r
    每月最后五天的苹果销量均值+ i1 [- S# ]4 ^4 g  P
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    5 l, J% j1 D+ E) M" Y按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    1 b: b4 R/ h. @$ v. N* k: Gimport numpy as np
    . P" t' [' i2 O6 X- Nimport pandas as pd$ l6 e% `! h- @0 f. B4 Z, D: F
    1- n: j  O* x( c! U4 v/ J
    2
    $ Y  i1 j6 X. ~' r统计如下指标:
    - `  l- m' O/ `  w每月上半月(15号及之前)与下半月葡萄销量的比值
    ; z! O/ r* n+ w每月最后一天的生梨销量总和
    , @! N  B6 B' n: _" }3 l每月最后一天工作日的生梨销量总和' {- A) ]$ `1 C  f
    每月最后五天的苹果销量均值
    * f4 r  }& F5 S7 s9 s# 每月上半月(15号及之前)与下半月葡萄销量的比值
    3 d( F2 a9 l: J) z( ?' W$ s4 j! fdf.Date=pd.to_datetime(df.Date)
    8 I, B+ h2 J! ?3 z/ Ksale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    % o  E4 ~; q+ X5 Zsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    . V/ j8 a% q* i* `6 }6 T- asale=pd.DataFrame(sale)/ a5 J$ D4 N7 a0 o9 [# `; n
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},
    + x1 E5 }3 d! a3 n& N                 columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
    ; K% s$ T  [* `# j% s+ M( csale.head() # 每个月上下半月的销量
    3 j+ `8 `' i) Z, c# u$ N, e, Q( K0 T+ ^! q2 ]6 g( z
      Month        15Days        Sale
    + M" Y) N6 _' h0        1        False        10503
    & ?' `' P9 C2 d1        1        True        12341
    7 r$ g4 G* [8 a8 p% M- n6 q" c2        2        False        10001
    ) u2 J& |% p0 o% ?- g3        2        True        10106
    % b7 E- i: T( i0 e9 q. M4        3        False        12814, u/ e1 e3 v* y
    # L* F. D' ?" ?, n7 P
    # 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序: q/ l, g& @; Q3 G- E3 `1 ?
    sale.groupby(sale['Month'])['Sale'].agg(5 ]$ q; }2 G4 |  a
                    lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    * M2 ]' a9 }: W- X% r! s' x' O7 O# n& x+ e6 T* K
    Month6 q( B; T+ I+ i% h1 `6 `" E  n7 @
    1     1.174998! W3 H- b8 ~" Z, o3 h$ B/ V4 ~
    2     1.010499$ b, w9 k* M, P
    3     0.776338
    $ }* t7 Y. V$ Q7 K; k1 d- d4     1.026345$ ~9 t( c* B$ w% f) s5 }+ r4 K, T
    5     0.900534
    + y! p0 {# c  o- o6     0.9801367 b+ n) f1 V9 f, x/ e
    7     1.350960  N: Y, W8 V/ `& o  P+ Y# F2 p& ?
    8     1.0915849 E3 A& Q" \: v* t1 T
    9     1.1165083 M( N" a3 i* b" N# R4 l$ \
    10    1.020784, _" H/ \: b) W+ T
    11    1.275911% A( j6 U( B" \+ ^7 n- }
    12    0.989662
    / I. s3 E$ H1 D4 a7 C  fName: Sale, dtype: float642 P/ l) ^5 e0 E6 k

    : k" L# r0 j- b2 Y+ ]1
    ) k9 J6 f" b9 P- g8 A4 {, L2
    4 J# X/ F0 K- v  |5 m2 }3  x, q! c0 q, u5 @
    4
    6 C' ^9 r! |  U8 e5
    9 W4 X( D( q8 Z" P% W& g& y6
    ' t9 L- r5 V: K8 \! o6 ]$ w7; L* Q6 @2 b* v1 B! J% W
    8( l* a4 M# y( P. q1 s' t. m
    96 K' V# @5 F# D
    10
    $ H  f2 ~: u4 s& I1 _11% P4 T4 b# S5 W$ [
    122 J8 N+ B9 H8 W6 h
    13
    + `& M, w( |& G. U6 K6 W$ F14! v* Z+ m& f4 U1 [
    15
    ) H1 z; G& h$ k6 x; z5 Q0 E9 I16
    $ t! q3 o* |$ E  T17
    - [4 g- m, j9 X" Z18
    $ `7 h1 T: V. K6 Z  S+ ?19
    8 E# e) W3 C& D" A3 X$ p$ b! I20& @/ R, q( |5 W: x/ `! @7 R# ^
    21+ r1 i8 w2 m: j6 F) Z3 u8 H% O& u
    22+ H) k6 e, Q, d" c) L
    23
    % R* j0 c. k" f3 b- g  G24
    / U: C) v# S$ z25( h/ c* F3 P1 ?* j# D/ [/ M0 r
    26
    ) _: @7 l. Q) ]/ d8 |) i, \27  j, h. Q. Q( i; w
    28
      ~# M0 _0 V: c293 R# Y$ g4 [6 O* Q$ j3 i1 N8 [
    30
    6 L) ^+ ?' ]: ?! U& d31$ o2 Q5 e. ~( Z: }* ^) j% o) Q9 `7 A
    32
    ! i3 j' w: _8 ~6 [331 J7 l% z0 o9 T' k
    34
    . d7 N) l2 u* W# b( B6 j5 N1 s6 W# 每月最后一天的生梨销量总和. H- ^0 H4 G2 @* E
    df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    " @' D2 p% Q/ o  @4 W$ W! r% ]- S( n. H) L/ k4 z! R- m% G
    Date" O0 T2 x" J: H) u/ V
    2019-01-31    847
    ; N6 c7 U' X) v! A8 M2019-02-28    774
    + B* k3 g. Y9 O2019-03-31    761
    * d* G. L7 F8 Q5 U$ n9 E2019-04-30    648
    % W: s* {5 ~! Q0 ]: h2 _1 [% x2019-05-31    616; m% S0 ~' n( B2 A
    1
    ! d- m5 Z7 ?6 X7 h/ ?( ]0 ?2
    + j  p" ^2 E! }# S0 \( M- f3
    ( Z5 U. U$ O! X) J& R. B# b4
    0 y; y) f! f1 ]* C, q0 B5: f7 n- Z2 _) l; \, d
    6
    ! T" Z+ D# i6 Z% N, q4 A1 u3 X  J6 \7, A" ]1 o$ a' @. v7 ^
    8" \2 w! d1 C/ o6 v, @3 i- ]
    9
    $ ?1 |8 ~: D! @" k; c# 每月最后一天工作日的生梨销量总和
    * y8 U/ _+ w# @; T/ @( R! ols=df.Date+pd.offsets.BMonthEnd()0 s" V- N% Z/ E) U( G  s$ R
    my_filter=pd.to_datetime(ls.unique())* O3 B% |5 `. P/ R
    df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    / m# d) W8 B+ @7 n. N6 D
    $ K" |* Y) h8 h; z0 k8 ?Date% `/ ?; p3 X6 s4 {- w1 n5 T
    2019-01-31     847
    % P6 k, M/ m8 e% m( G2019-02-28     774: g* d" m  ~' c9 [, D# e
    2019-03-29     5109 Y* T) Q, }; M4 f. T, q! ]
    2019-04-30     648
    # v) [# ^/ {' K& h! `. n: q2019-05-31     616
    ! I# b5 h- @6 p. j$ B' F  v" W1# J& Q: Q  U) T8 c  w' S' o9 c
    2
    . Q! j/ I. f/ I9 `1 [3# N$ {  T' P& A; F
    4/ H) C1 L9 h& a7 ^8 B
    5/ ]7 a) _0 n3 l- y
    67 s2 N* M" @/ v3 i3 u6 u% v
    7
    5 d" n& n. c: x/ }8 C% y! Y86 q3 I( ^# E7 X% ?; f
    9
    4 J* J! s8 K6 {) }105 a1 d. U" `" H% Y- E8 S
    11/ b% W: q$ z2 L+ c6 _
    # 每月最后五天的苹果销量均值% r8 C- u3 c' k
    start, end = '2019-01-01', '2019-12-31'
    / S' z3 t: }5 x8 ]3 c9 eend = pd.date_range(start, end, freq='M')" V- q2 u% Q+ n- b, z3 D$ V4 o
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差# J# R( @1 ]! e% M) t+ m8 z
    % f6 g7 C5 m+ c$ a
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)+ v# Y* ]  r/ y+ S# H4 A
    td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
    ; c2 b) h  v0 [( r+ ]( g7 \& Wend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    3 b0 O4 K8 f2 m. j$ K% b6 l5 ?4 M0 D2 g  d) J6 r- Y5 Y7 h
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量; c8 a9 n" _# q: l* `" X
    apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head(): U1 b% B! Y2 I1 V' ~7 G

    4 U6 ?  @- O/ N. D) P: cDate
    . K* l- `' b1 l# H: ^% k, G5 g1     65.313725
    $ T8 C, W1 ~2 G; @1 I2     54.061538# `4 B( O6 O" G% C* p
    3     59.325581. X9 H# M5 c2 ^7 |) ?6 D% y
    4     65.795455; h) u' r& c; ]' [7 |* ?
    5     57.4651161 p+ G( p1 _/ B7 ]- S8 r1 r
    2 x  t: v' d; ]+ u+ w" A6 [7 [4 Z
    1  F% a! r( m# S/ l% w$ L% k
    2% k  Z8 L' P  ~! Q9 Y
    3
    / O* ]( E. ^8 t0 q( j4
    * z) W8 o( F3 j# g3 _$ G5 @58 I0 f! t- F; k! ?/ o! p
    63 c$ K9 y% k6 F# w) O, r% {
    7
    5 u) k4 P$ G( v, ^7 j$ B8- j' Q! D2 k# {. W2 U( Y; U
    9- ^4 |) `. z6 _+ U# f$ n7 o4 R' D
    10
    ' v  W6 o( {. b+ b, ^' b% O/ u119 k1 p+ Y% a" W
    12. B: e1 U6 ^: n. k" n9 r# m
    13
    " T9 F% x* g, U, Z14
    ( H$ [  U' p$ M% s9 _* v6 m; p15' ^2 V" E) H& p6 w% q/ v* o  j
    16
    7 w% C" V/ }' \# |- V174 Q( J- r; T2 t" w8 T; A
    18
    * h7 p( O) Z! \) q# 参考答案:% \/ s: u& o6 O) E- V
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(: c2 X9 b& b) v8 T: F5 y  u. Y4 ~
                ).dt.month)['Date'].nlargest(5).reset_index(drop=True)' L( p* D9 }$ }2 w0 F& p

    ( K: H* [3 k$ U% `5 O2 _res = df.set_index('Date').loc[target_dt].reset_index(
    7 l0 w& U3 M8 v1 N' J. M            ).query("Fruit == 'Apple'")
    - t  d1 {4 ]( q2 T7 X0 g) U4 \# V. [4 \
    res = res.groupby(res.Date.dt.month)['Sale'].mean(
    $ |- G4 a6 ?7 z8 V4 {+ L( A3 z( j            ).rename_axis('Month')/ I1 @; ]' E9 D! @
    ' Y) [# d) n% N; Y) |/ E  x

    ' [+ E9 C6 s( gres.head()
    : k1 _- l. r$ J, D5 {  N9 KOut[236]:
    * l* `' r) k, P1 B0 oMonth4 D2 X9 I) H! Q. |
    1    65.3137250 z7 i8 t- w8 ?7 l1 f3 ^
    2    54.0615387 [  |; L. K! _- z7 ]
    3    59.325581
    ; q, m& Z' B% [  R) p2 W! u4    65.795455* S0 x7 P$ X; z6 e/ _
    5    57.465116$ d, C8 W/ [* k9 a* Q1 N5 z: E5 C. i
    Name: Sale, dtype: float64
    - V3 K' g9 t  u8 X/ k; X$ F1 p3 |" l. I- r
    1; i/ {9 T, c4 L% X5 m
    20 l. b( Q$ Q3 Y1 j5 B2 \
    3  F: ^0 y# {. J  _6 k  r2 S
    4
    + H- ~8 y* q4 W* v  X5
    6 n+ ]* f; L! Z6
    / M0 x" v$ \3 d7 \7 k" ]  [7
    3 o$ W  ^) r) I8
    ( ]" h& q# ~0 D/ A4 t9, H2 g0 d& H' J. x
    10' w* B  h; Y: Y! b% {
    11
    5 X, L  A7 u5 s1 _1 r129 @$ c. N/ R7 d
    132 {4 l# D3 g+ ]+ j
    140 ^9 c* b2 l7 I$ ?/ H$ v  V( d
    152 A6 D% _( _0 g# R! j( n
    165 w4 A/ J5 G) U
    17# H2 i' G0 B0 R' O$ L
    18
    5 S; |- Y5 b& i19
    " m" d& q/ z$ [$ X% x20
    / [3 k' ^  T7 z( g+ O按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。( y) d4 F. W: B" L8 i
    result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.  s( e; j5 M3 }8 N6 o. m
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
    # c# m2 _5 G5 ?9 |$ h4 z                                       
    ; \& j  @& H4 C2 N% i% u3 M/ Zresult=result.unstack(1).rename_axis(index={'Date':'Month'},
    * h7 X- e2 c8 V' N+ c                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.5 E" K& E% R7 H% G$ X* L
    result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)# _/ F* Y# h% E$ I+ W( X( B2 c
    result.head() # 索引名有空再改吧
    8 x% v4 O1 f+ x) _
    , Y9 e' H5 k) z: w, J          Week        0        1        2        3        4        5        6
    $ S$ \4 p' e( M8 a2 n6 l- U. q/ KFruit Month                                                        % R( d0 H# a9 p6 h0 C# \
    Apple        1        46        50        50        45        32        42        23
    8 j4 L$ R! `! s2 aBanana        1        27        29        24        42        36        24        35
    8 z8 Z( x# o7 n4 s: e: `Grape        1        42        75        53        63        36        57        46# s9 X" J2 Z, k
    Peach        1        67        78        73        88        59        49        72
    $ d( O0 S! M$ T( XPear        1        39        69        51        54        48        36        406 N2 E/ F! v& [
    15 k' r$ Y, k# @$ C" a4 ]
    2
    & @. n9 a7 X% k- |% H+ k& R3: C2 ?2 \7 P: O1 i; j0 ^, ~) p2 n
    49 o1 }7 ], `& o
    5
    : e+ g( m6 m9 b3 Z1 e6
    * i( Z9 M4 d) Q7 M# f! X7
    $ l) \$ s7 E4 T( Q8
    5 J5 e  S1 P3 R. Z& E; I: d9
      H8 H* C! B' H, y1 r5 I10: M- Z: `2 I6 B/ W# d. D
    11
    $ c4 A& y! J. i$ B  x# I12
    9 ^; `9 p2 |) [; n$ n139 |" Y) Q; K$ C; W+ D# _+ v
    14
    ) W: [, D* `6 F" b15
    $ v) W; k; u5 D: ?& b1 K- i! F) Q按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    / M  E! b$ q$ |0 X# [# 工作日苹果销量按日期排序
    ; J4 h/ e8 F' O; A  s5 L( U  lselect_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
    ) k( H( p9 |$ [  e% jselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总. [) n6 m8 t$ v! U; B
    select_bday.head()
    0 G9 Z* J8 f- f# y+ [
    : O- m; a3 _$ {Date$ J7 F" r" @' [# B
    2019-01-01    189
    * i# E& Y+ ?  w5 f; _( _6 G2019-01-02    482
    / L1 `+ X) ~1 D- c2019-01-03    8903 I7 ^) K3 L' Q
    2019-01-04    550- _; W& ^: f7 |+ V
    2019-01-07    494
    / X! x. f# j# ~. U6 j6 |; M' F
    , i. c5 i& f: B& {# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。0 j2 Z2 Z3 Z% I1 k7 P
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
    . h# I) k- Q5 O9 {) P- j- H/ B, x: L8 s
    Date
    % f5 b5 n+ W8 k0 F5 t2019-01-01    189.000000& W: o0 s! F9 W
    2019-01-02    335.500000/ g( V' t- R1 H. z
    2019-01-03    520.333333( M6 k7 K0 z  C9 c
    2019-01-04    527.750000
    7 p3 B4 @' Z" [2019-01-05    527.750000
    2 k/ b% S5 C  v4 _2 [& s( n) B9 a
    ) O" T# X* p7 T  G+ q; q! G————————————————
    , y) [' [9 y& @/ z版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    2 ?& g  @* y9 h# a原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    * E. K* K$ ^1 L4 J8 X; d8 `  |% A/ ^' h( i, D) R% h+ t( ]4 l* w( J5 Y8 D
    6 ?: U2 v7 Q3 l# 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-8-24 04:59 , Processed in 0.713931 second(s), 51 queries .

    回顶部