QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2892|回复: 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
    8 v% a6 C8 q, {7 a. {

    # \$ A% I2 e$ y5 X( e: ]: f0 L: E% y+ G7 w
    文章目录% v. ^$ L- d4 ?" y+ A
    第八章 文本数据( `* s7 f+ F' c- M' R
    8.1 str对象
    . w, }0 O- o; C  k8 J" X8.1.1 str对象的设计意图; c! N: I- U1 s  r' ]
    8.1.3 string类型2 ^! A1 R* R9 X! ~& p. U6 d3 M
    8.2 正则表达式基础
    / E5 v6 b! a4 F$ @, P1 U: V8.2.1 . 一般字符的匹配
    0 T  R, z' E# j4 `9 i$ \8 @8.2.2 元字符基础7 M: j# Q9 R5 [5 l5 E
    8.2.3 简写字符集7 p" e/ ]' [. Y) j- m) }
    8.3 文本处理的五类操作0 f% i: Z" R" R9 H/ |# I" B
    8.3.1 `str.split `拆分
    : i0 I* D. M! W3 v8.3.2 `str.join` 或 `str.cat `合并5 N# J4 x9 Q% K% f& [
    8.3.3 匹配- u- I) l- {( N$ U9 r
    8.3.5 提取3 K  I- d- F. H) w; [1 X
    8.4、常用字符串函数
    . l+ b5 ^0 L7 P) C5 w8.4.1 字母型函数
    , k6 ]6 S& A) s' ~+ p, g8.4.2 数值型函数# N6 U6 [+ c8 t) g9 m
    8.4.3 统计型函数# Z. S+ g1 S2 @- h
    8.4.4 格式型函数& [4 I3 x. L; j, v! C  j  D
    8.5 练习
    6 ~& y# q* N8 V4 W4 LEx1:房屋信息数据集
    0 @7 V' j. y5 C  C% c7 UEx2:《权力的游戏》剧本数据集: V6 J0 a3 H0 _( s3 T8 N8 H
    第九章 分类数据- W3 k. s" `2 p- U2 d, j& \) _5 f
    9.1 cat对象
    / E: y4 c# m7 G2 [' K' U9.1.1 cat对象的属性
    1 Q4 f- n7 p! H" L9.1.2 类别的增加、删除和修改
    ( M6 Q8 S( {5 K9.2 有序分类
    - p" V: |+ m0 i& U7 u' a9.2.1 序的建立
    9 {; e/ Y2 H2 A- }; X9.2.2 排序和比较! C2 z! W/ G, t
    9.3 区间类别8 ]7 g# s, I) c3 |
    9.3.1 利用cut和qcut进行区间构造0 R. r) \0 V- Z) X* x$ M7 N
    9.3.2 一般区间的构造
    ) N3 i) Z2 I7 V5 v+ }9.3.3 区间的属性与方法& z5 y( v% N* c$ a% {, b
    9.4 练习3 \1 I9 _3 _, L5 c0 a
    Ex1: 统计未出现的类别
    5 _2 T0 Y& |) b/ v9 AEx2: 钻石数据集7 b# B9 ~4 R% T2 A% c2 R1 f+ [% _  x7 U" X) U
    第十章 时序数据! f2 y1 m, \1 d, _4 Q7 e
    10.1 时序中的基本对象: t6 e# k! E2 K% W
    10.2 时间戳
    $ L* u+ ?/ d2 O4 o4 a4 g5 ~10.2.1 Timestamp的构造与属性* z# X+ F& J# w# X) l
    10.2.2 Datetime序列的生成
      L# C, i6 ]& b) F& m% R9 q10.2.3 dt对象$ m% M& u& n9 ?( x: _
    10.2.4 时间戳的切片与索引
    $ x  g" m3 ]0 r' P/ }10.3 时间差# w& T; K! Z( J& b$ X! c5 I
    10.3.1 Timedelta的生成/ N$ x) z+ D& h4 E9 o
    10.2.2 Timedelta的运算' ^5 J4 S- [' _
    10.4 日期偏置6 i( I* A" a3 M$ j  d; i4 G2 C: k
    10.4.1 Offset对象* ~1 j! P/ O7 L; t" W. P
    10.4.2 偏置字符串' ?$ l5 J* o: p) i- @
    10.5、时序中的滑窗与分组- x; S7 Z8 Z) j8 z) u
    10.5.1 滑动窗口7 e0 X( b; p; D: Q
    10.5.2 重采样, t3 z2 A! t8 s+ [
    10.6 练习
    + B# z% I# J: HEx1:太阳辐射数据集
    0 N2 f& O5 m# Q9 u( [/ kEx2:水果销量数据集
    8 \$ z9 Y1 A" [3 A0 Q/ X: J  课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
    " J- _+ z$ z- `1 h0 l传送门:+ c# |$ a3 e0 Z

    5 ]4 \7 P2 k3 s/ `! H( Rdatawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)! w" Z$ Z% k- `
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
    : t; G2 N# G- S  R' r! J, m第八章 文本数据# G: I7 k, w) t& O* G4 m: {% H
    8.1 str对象5 n3 W, F; E8 p5 V6 y  ~
    8.1.1 str对象的设计意图
    + S: E7 V+ P" [. u# b8 H  str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    7 S& `3 ~4 H' N+ {; V2 B1 x
    5 A* e) G  [2 ?3 X4 @% o4 `2 p2 lvar = 'abcd'3 ?4 Q: \. i# R( M
    str.upper(var) # Python内置str模块& ^" y3 {1 X: v' B  {) q1 z
    Out[4]: 'ABCD'! l7 \7 q$ |3 O6 g' H0 J3 F; O
    + L3 y; v! N# W+ c6 r; @% U: o
    s = pd.Series(['abcd', 'efg', 'hi'])
    % X& {- ^6 R0 T
    / Y; n- G8 ^9 I% w+ A5 ?( s9 ss.str! {9 h- M4 [' i* B, T+ q0 v
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
      P  N* a% }4 ?0 c; M& r- F; a' N6 {2 l6 O( l/ Y
    s.str.upper() # pandas中str对象上的upper方法+ [6 z; i; y2 q* a* @
    Out[7]:
    : X4 V3 N. b* D  O0 ?! H& a0    ABCD6 K2 O  ?/ L3 W$ \5 V
    1     EFG
    2 J7 D8 C1 O, s1 J; s2      HI6 u% N0 `! |: ]
    dtype: object# f7 g: I" {" \. o
    1) U- V5 D0 \& E5 Z
    2! X) P  [$ r' u6 E8 h
    3
    4 T9 {! n+ z" L' D5 x# i- v4
    + h+ m) O# E0 y; z; v5: U0 w" e, l+ ?
    69 k, Y3 C0 u. _* p" h$ d1 q
    7
    9 B2 B$ w: h- q* t; h0 {: l" b8
    $ [$ m9 ~8 j- G6 f/ B" G0 o9
    . p3 W9 R5 |8 y, }# ~10
    / _$ n1 ]" O9 R- e) A11' x/ S% {! V% ?8 U9 j& A
    12
    $ w8 h; I( J: C/ T1 U139 V. N. V. K) K4 P3 h1 K
    14
    ' w  r! f4 W/ S- s152 p+ U6 p) o- `: S( M5 b* a# ?
    8.1.2 []索引器
    5 y; [- v" W3 b( K) @- C( ]" m* [  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。) A2 n4 X; t/ U3 g
      pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    6 f0 J- y1 O, Y* n& G
    / N* a( c9 m+ [. a. Ns.str[0]
    : v; P8 x9 \" ^3 Y4 o" L* t; YOut[10]:
    * I7 K( j( X# L2 }# Q0    a
    ' z3 ?1 ?' n- s1    e/ v  g) W( x! V
    2    h
    & A( z( J4 E3 `$ S# t) Y5 i: Mdtype: object
    - y9 v0 F- K1 J0 i1 n
    - Z& J$ l1 h1 ^, Q! K2 os.str[-1: 0: -2]
    8 \- Y" n+ J" X0 r* Y8 ?+ @, D4 A# DOut[11]:
    8 }' z5 |: F9 ?& S0    db
    # I6 F8 b  @+ l9 h( j( v$ H' C0 i1     g
    * d# l! S  v6 o6 F! J7 _' L2     i5 q5 V: {+ V3 V* |0 k1 B2 k/ m
    dtype: object# D# F' p, p8 q% n! E3 o
    - d4 @( k3 o, M/ v  j
    s.str[2]3 ?% t: r2 s) u0 y- a6 j& `
    Out[12]:
    ' B( l% }; M+ g! n- c6 I& D0      c7 a2 e! \* l) h5 l# L" {1 c$ i
    1      g
    # U6 v& I) w( O2 a: b8 [( S2    NaN
    ! r8 ]/ D/ J" s2 S, [5 Xdtype: object# e+ F2 x% g- t: f2 U

    ' T: f; y! Q; C6 [1
    ' g3 r3 |- h  n) L5 i1 U1 r( b0 t2
    9 Y. C# r, T2 x" n7 _3( J4 |$ ~1 W1 l) A
    42 I' G, b. v# q: x$ k
    51 q* C2 ?& s: g+ N% I( I3 U" a2 T
    6
    3 W- ]  N+ D5 G3 ]4 c7
    + x& \$ J! x* W. q8
    " c' I# o6 k" B4 k. W" ^9
    ) s9 ^( t" ?# F3 B10
    3 E8 r: O. k- j+ d- `9 x11" u) L- h* E- v8 K
    129 K/ z0 _3 d9 e5 u5 l
    13
    0 `$ O# N5 G  O' s0 h' h145 U% S6 L$ f( I' ~5 C
    15
    ; H% u$ p: ^7 {# [16
    0 a6 Z% W; q1 [3 L& f17
    ' k0 h6 d0 s2 x182 Z; m5 N5 b+ {& r8 q/ s" b, h
    19
    2 }% u: |% S' e20
    + h7 A& V* B" o8 J! U/ wimport numpy as np% z* J7 D+ X. U: I3 q) C% B: U
    import pandas as pd3 x# G9 _7 V- Y7 X* T& n% f" u$ O! z/ Q- i

    % \6 b- M. M7 z' X' D" N7 {s = pd.Series(['abcd', 'efg', 'hi'])
    2 ^0 f! e: F  M! g' ss.str[0]! i9 J+ B( w7 c2 Y2 c
    1
    . ~1 B* M2 l" _5 o! i2
      Y( a" g5 q$ U35 t' p7 h- c! `, L1 U3 W7 v
    4: F- g' M  p9 g7 o" e; {1 c
    5) Z) R* N: }9 ^/ ?
    0    a. d3 f# W' K% n+ k# m
    1    e
    ( d3 k' o; X4 U2    h
    - l1 E& y. \" d' _dtype: object
    0 F# E- w6 e  ~. K( S1
    8 E( p! w* p: J% ^; _- ]2  _' o) x3 E0 a' C% j5 B
    3
    ! C% ]8 p8 q* Z7 g: _  U2 y( c4
    8 u% q7 U7 m! J  U8.1.3 string类型6 ?8 |- y, `5 a. r+ M, n( o/ Z+ k
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。3 `) x1 X3 Y, P: Q. h! F7 f/ U
      总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:6 E$ c4 w2 T4 R, Q4 S( F8 E

    ; |- i' Z) U9 e8 a) c: m/ `二者对于某些对象的 str 序列化方法不同。) W. F, p" A/ q3 d+ O5 B3 A& M
    可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:' O7 b' I2 t- [& o
    s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    2 V$ \" ?( O7 H2 `0 is
    0 A/ x: x8 I4 P* I9 G" O* s1
    & r; e5 Y+ X1 Z# G! B3 @5 t5 f- j+ _2
    - q9 z, G: S- U+ @; q0    {1: 'temp_1', 2: 'temp_2'}1 w0 q7 l  N: g* V  u: U' ]& K7 o
    1                        [a, b]4 t6 ^4 V& v  G8 N$ X* ^- F
    2                           0.5" ?$ m: h2 z2 H
    3                     my_string6 q' I+ ]/ h" L- b
    dtype: object" r+ s3 F" h, v2 B
    1
    2 ?  C" o/ `( ~7 p" U+ y: \2
    + c1 k. ~2 L. {  m% z/ q% G- m2 m" c3! u) c. l' i. I1 b/ [
    4
    ( a4 I) G& T7 Y' t5( n* ^2 Q, g; C, X# O2 \0 A
    s.str[1] # 对每个元素取[1]的操作6 ~, O, U- R  g; c  \6 H
    13 H3 j6 m8 A7 d$ N1 \$ w( e, z
    0    temp_1
    4 v0 m0 ]. @6 u) ^4 O1         b
    # N9 n- e0 E/ s/ D2 G9 v% J, H; N2       NaN& _/ d2 a2 G7 I
    3         y( D( [  ^6 v; R" j6 N
    dtype: object1 P9 N* ~% d) _0 m8 o
    1. k- x  n" E+ w4 r0 ^3 P8 l5 D
    2" @  A6 f6 T7 I% {2 R9 H9 f
    3, G7 n9 @1 O  J+ x9 R/ J- w
    4
    & A7 C! K  p3 z/ S$ T" g5" Y) r& Y$ J) v# `  u3 Z
    s.astype('string').str[1]
    3 X9 K5 j  j# |* Z& n. L1' E! y# i; J: m# O' m1 _
    0    1
    $ g+ T1 G9 _, K# ?* h* _1    '
    8 G$ u8 f8 m. b& h  n8 d/ c2    .* S6 a5 o! E  b) G9 H
    3    y) c, ~  f' w$ v$ b* W% B  ?5 u
    dtype: string
    7 X1 s1 M9 U7 C! w7 E  d0 |18 c+ T9 l) J, }+ D. J
    2
    ( Q2 D* e5 o- \" W% A3
    / v. Y5 O. x. U) g9 @% {$ m) Z% X4
    1 ?3 @5 o, ^, o1 M( H% ^* q' r: q5
    % y  S4 q8 {( J- N1 N/ k除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    , ?, X- N5 a1 u; }' e% g# _8 ]* e" a2 d4 [+ e3 K  X
    当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    # N; Q' d! d  d- _string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。6 D( J- M8 u% X/ u  ?  T' Z
    string 类型是 Nullable 类型,但 object 不是6 T. f' ^4 j$ A+ D1 F- x5 l/ K  B
      这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
    9 K$ Z. @% Y1 r- a1 r  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。8 L& P0 G- ?7 }$ e7 x& Y
    s = pd.Series(['a'])3 i4 q, s  D0 S9 F1 L
    8 p( M: {' j  B5 S7 {
    s.str.len()/ \2 l, Y+ ~# V) I
    Out[17]: - y5 E8 k" }7 M/ L% E
    0    1
      H- u2 U# A- I& Sdtype: int64
    6 @) m# R% }' G+ S& H8 X, W& T' R( w
    s.astype('string').str.len()
    * k& c/ d' a; G8 h" N5 n( TOut[18]:
    ; h. T+ r7 L+ Q# }2 F9 A- o0    1+ @* E- p4 F! a% g  d$ ?
    dtype: Int64, ^* I( R) N) y7 u
    0 j  G; `" _4 J, ?: W2 e
    s == 'a'. v( O! o8 L5 P4 I; j. G
    Out[19]:
    ' E" B; l4 t$ C$ Z4 A0    True
    : x9 i! F- H8 x3 |  O+ sdtype: bool* O) z& x2 H' I, ]  o: g
    5 ^% w5 ?3 b. Q, j8 S" B
    s.astype('string') == 'a'
    / p  q2 T  c% k8 y% N8 r8 LOut[20]: # a' j' u4 u' F! z/ K% J1 k
    0    True7 D+ Y& @$ c; V* k$ H5 F
    dtype: boolean3 _1 P. e( `3 B+ `0 Y

    : N) B# u' X  \% Y& m* s$ z9 ks = pd.Series(['a', np.nan]) # 带有缺失值
    6 o1 F# b' d5 }* Q- \) c; ~4 S* B. A/ q3 S% O
    s.str.len()0 D' ]6 w' \) t8 M% ]  m+ ]) |& E, p
    Out[22]: + I" E9 F, d+ B' D
    0    1.0- U/ Q; R, Q1 Q8 _6 ]
    1    NaN
    ) g% z& n2 A5 c5 E) D* i0 `dtype: float64  j0 C8 T; ]: B' q6 D: `! v0 Q

    # u/ R& b6 I$ B" |+ a3 M2 u2 T5 t. C0 Us.astype('string').str.len()
      S" j& i; m3 w4 ]+ P; v& _Out[23]: ) n6 U! o# J: z0 q
    0       1- g0 Y2 j% G4 l. c' k& `
    1    <NA>" E5 m* U( N, j
    dtype: Int644 \" q, K( R5 i  _3 p$ W0 ~+ N

    ) C; V6 y/ N+ R4 l* Hs == 'a'
    ! _# @+ @" _8 E* r/ q. ~Out[24]:
    1 c( X1 A! i) }" }4 S/ j0     True: j  [! z& p/ J7 K
    1    False) n. n2 d" R8 ^& Q! ]
    dtype: bool
    7 x" `9 v  z3 p/ i; ]3 d$ A5 {3 l- r! E5 M3 F! j
    s.astype('string') == 'a'
    4 d3 L/ {% k) SOut[25]:
    % t8 M; k. g+ m0    True
    3 `" n3 o) d2 ?' e1    <NA>: z6 w4 g8 e5 M1 n: u" z. Z
    dtype: boolean
    ; F* A( s- K0 }) {8 Z
    ! E/ n! k! G! h. ~15 M  E) |. p, c* n
    2
    ' Z6 m( j0 b3 c3 [; E3
    : b( G7 X+ s" F2 c7 b6 N) q7 k4
    * S% M1 @$ N# ~3 b57 s( E. ]& i- g" T* I; d
    6
    ' z; H; K2 D- b( p3 z1 k7
    $ ^9 a1 V" ]! J/ f  @8" v6 i# N0 c$ S7 @4 j
    9& l& V0 \% |; v7 E0 u
    100 M$ D* ?5 o3 G9 P+ U! ~
    11
    4 R( u8 q. o# y$ \3 ]. P2 c12( C8 P5 _, {) b& l2 Y. E  a
    13
    8 ^  ~/ R, ^3 }14
    9 F3 @+ k" F5 v15
    ) |8 O! ?. K4 Y/ x2 n( y16- w* T  w" Y& t/ E6 l( ?" L+ K
    17
    ' k  O. k: }; `$ w) G18  K2 W; l2 K, [/ o1 q, g+ [0 J
    19
    3 Y1 }+ ?: R: |2 A: W% c( ^* N# O  ]+ @20
    8 F/ _" A9 r; \- e* }& J21
    + ]6 P3 y5 S# `- O$ p$ Z22
    ' f' t' @) A/ V5 q" ~) n& A. d. h234 r* t% n& q" U" R7 N6 d# S" O# d
    24* \; u0 q5 h, s' i6 b
    25
    - ?- R8 f; c! V8 z0 u- g. e" Q& O% B269 D9 b7 u: b" n  A3 B) M' N
    27
    , \7 g, b+ ?* T28$ V. L% V# x, k0 c1 g$ ]6 ?! \
    29
    ; Z8 c" Z3 U- V, Y4 h# y# g' a30
    8 y; ~$ {% X( I/ e7 q- E2 d: c315 j7 e: u# M% j9 i+ S5 ~) v
    32
    0 }; D4 }0 Z; O0 a! ]" `9 x. q8 e8 l33
      e) j0 C" i8 f5 }8 |3 ?9 W34+ E# [6 G  o' d, s
    35
    , \4 t9 q( L% K/ ?8 y36
    " K5 Q! u+ b" m* x% p( T37
    ) S1 F8 F/ N/ A3 g  }38
    ) t* J+ q! S2 l  D39
    # q- X: t  f0 T- Z) F; E  `40
    * p. C. R6 X& {7 C( ~41
    0 z0 |0 p  C0 P1 h& }' P2 ?42$ v  c4 ?2 l9 g6 l, J1 c
    43* p7 y' g  B( x3 n1 Y& ?4 N7 e5 N
    44/ k4 x1 U; m0 l* T
    45
    . h/ D9 ~' I' G! c" ?* F46
    2 \6 l9 b. |3 _1 a' G47# L, ~8 V! P' Y/ I  A
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
    ) Y! _. x8 [8 N" L* @
    , ]* z+ m  B/ Y2 ?1 Vs = pd.Series([12, 345, 6789])7 E. a! {! ~0 u$ u9 u6 R
    0 H) L# b- I5 O, N5 u: ?* C
    s.astype('string').str[1]% h7 a" M: U+ F0 G' I" a8 K: `6 C
    Out[27]: , Z1 w* W% ?4 A! X( o) c) H2 n' c
    0    2
    " z3 W: K. E7 p+ m2 I& K. Z2 L1 m1    4
    $ A& v! A& m/ l  U( V- O/ E2    7
    + v- n- s. X3 Q6 @; s+ Odtype: string8 f- p' S6 H0 }4 c& K: `1 z
    12 i  |! v) H, [( H) x
    2
    ' U$ k9 b& p* S" ]8 u* x3( H# Q, g2 h/ T0 a" f
    4
    * D7 p( _* n. i! Z% N; o% w55 S/ n" Q0 n; r, F0 L: L. c9 p! @
    6
      {2 ?: d/ r. S( Y8 t. R8 U! {! h3 _2 U79 @" y/ r* T! Q8 @
    86 j: W& T/ B2 |3 p
    8.2 正则表达式基础
    1 X  J  t7 m0 D7 S+ [  u8 l这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书# a' p' h" E% ]1 E, B& b% T! n  ?

    1 O1 V' W' q0 y. G# L& r. d6 ~/ X9 p* s' G8.2.1 . 一般字符的匹配! d. _- N9 g3 N
    正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    : I2 o) ]/ |+ l0 F3 v6 X5 U# v5 V0 S/ E! v6 M( Z( x7 I" y2 H
    import re# Q" w1 b$ ^/ }

    / ]; M" w- w# p- ?re.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配
    & s* d4 p( f/ _4 IOut[29]: ['Apple', 'Apple']8 ^2 M& o) D8 M' r# ^
    1: b# D0 @( J! {* z3 V( H
    2, E$ ^; O/ ~2 }7 D2 o
    33 t' I/ \& v4 I
    4$ T8 O& P) L' y4 _
    8.2.2 元字符基础
    " W2 q1 I- E. a# }元字符        描述
      K3 s: {2 h+ s  A* O" X/ K.        匹配除换行符以外的任意字符
    , R$ H# U3 N, {2 Y[ ]        字符类,匹配方括号中包含的任意字符0 ~- }" _* I$ z3 b# ?% ^; M3 J
    [^ ]        否定字符类,匹配方括号中不包含的任意字符
    4 \  u- A: _2 O, b*        匹配前面的子表达式零次或多次
    : o& b5 l# N* r# B  r3 @+        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字
      S& r" Y; z/ w5 c?        匹配前面的子表达式零次或一次,非贪婪方式9 K3 ?- p- c- g+ l" f
    {n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式: }! g# a2 `# P; m/ b
    (xyz)        字符组,按照确切的顺序匹配字符xyz
    8 @) j( p9 G0 v) X|        分支结构,匹配符号之前的字符或后面的字符
    & f: v9 k0 C/ J3 a( d7 w7 G4 i\        转义符,它可以还原元字符原来的含义) r, j0 Z! a6 [+ o
    ^        匹配行的开始
    7 e/ Y/ C6 E7 S! R+ y2 e$        匹配行的结束$ S3 o; k; e$ ~
    import re3 {  }: S' R9 T6 I9 W
    re.findall(r'.', 'abc')
    2 {; _1 m& @; N/ z/ HOut[30]: ['a', 'b', 'c']
    ! A( c, N3 N: L8 g
    : d" `$ f7 V- m% `1 y. s6 @re.findall(r'[ac]', 'abc') # []中有的子串都匹配
    # k% n$ o% ^6 B. qOut[31]: ['a', 'c']
    3 L& s  F8 w; G- u/ A4 @) Q- f- u2 @4 q' G  J9 Z. x9 Z
    re.findall(r'[^ac]', 'abc') 0 O, T- k( N6 x
    Out[32]: ['b']; V- E  N" z% p

    " D: P8 e# Y: C( s, n9 ire.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次+ z0 ~0 s2 l! ~
    Out[33]: ['aa', 'aa', 'bb', 'bb']# ~0 ~5 Y+ X8 s1 P4 e- L% U7 M

    * t* H9 R) `3 K4 c9 t2 H5 vre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
    % u& f* g- r/ o# sOut[34]: ['ca', 'bbc', 'bbc']3 ~# q" {( Z' `- w' @6 ^
    8 P& Z0 J. Y/ x* Z2 S7 ]# @6 z. U
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。+ S, _' c0 S" h! Y4 C0 {  @# ~
    """: E3 Y' v. C5 ^( k) m: C
    1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。
    $ j; `+ [8 n; _3 {2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边- j/ V, U: |5 ^2 ^& ~; D
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,% U/ z# g9 S! G' x0 _  Y( m) X# h
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    , H) S/ D; c$ k1 r% M0 O; E  ~  T"""9 V) B- s* v7 u# w. S7 w9 {

    0 z0 m7 u9 z* K  l3 k0 N8 xre.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。1 [( _: p, o7 t3 x% f8 X
    Out[35]: ['a', 'a', 'a', 'a']
    2 J7 y4 j7 k' i! ^: W# S- ^5 b0 D- b6 E6 R4 y
    # 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
    : Q( `7 D1 \( f: V. x" ]; Y1 \: y# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。- t, \0 \# L1 p% o2 F1 q( [
    re.findall(r'\\', 'aa\a*a') 4 b7 l7 ?, l$ a" g% b( @
    []
    ) h4 |: R9 p' |6 o+ Y1 s8 f4 r. u3 H# L
    re.findall(r'a?.', 'abaacadaae')
    ) U9 y7 F/ W" w0 r  @9 Q, K) wOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']' J" {0 B: K+ ~0 Q: m4 l
    # B7 ^+ G, v! t8 b5 u. I+ u2 G
    re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
    2 G& z) i; O8 {) S! l8 D; B[('width', '20'), ('height', '10')]
    5 \$ ]1 J+ S" r$ Z6 k) j( r  e% U: T% O
    19 c, b% R1 D8 U) K+ n  U; x
    2
    8 I5 R, d0 \- e; k$ w3 O6 g4 G1 O31 V; b  g6 w/ o' ^
    4$ V4 m! U4 Z, ?! m
    5# Q/ a% M) V) G' U% q4 a; ~8 Y# f
    6
    ; Y- M. p/ g! _1 d* ^7) P$ h& Z, c  C* B( d7 n
    87 h9 n. F* R' D; q: k7 k' ?" F
    95 o: a+ G+ P6 S. a- ~1 i% ^& p
    10
      L2 S! r- ~* C* i2 X6 T, l/ H7 p110 R  \% \/ b7 u- f. |
    12: h2 r) p) b1 p' n: i+ m
    13& j+ y1 a4 a& w- Q4 _9 O! _
    14( j. D5 |, ~3 J' H% [, t$ @' f
    159 S0 p$ o8 V, v5 I' D) {: f+ `
    16
    & t. n$ x  `5 Q17
    9 t0 Y1 z1 W$ w1 Q* X* Y4 Q- ?188 ?/ z) j8 D0 u; l- O' Z* V7 H; P
    198 q/ `! v. Y: w0 E, I. ?% y) b( \
    205 _. e1 p+ s7 N% ^( j- b1 @" U
    21
    * u( V' Y" i5 c3 `22
    7 A, s. \9 B: b+ x* B2 |23
    ! J. O3 M+ S2 g: |* a0 w! g2 e  R24
    $ Q9 Z. Q) y- w& v, F25. c: y* d( U, i( g+ b' x
    26
    . }- b2 r" v) I6 O  c: Q, x. M27& I3 p0 M! `2 L; \* V4 @
    28
    : ]* C9 T7 `9 z29
    & T8 {8 \) u& T) H* e30
      J7 y) H6 E' F! ?) _. v  _315 {9 O7 ?# T% y  t4 O0 ]: u3 |
    328 F5 s. t# c/ K0 M
    336 E' B" L  M4 ~2 \! Y
    34
    * }, b) y0 h& l* X35
    ( }0 U# Q1 D; h6 }36- `; y* G& ]( Z
    37
    $ e+ l( X- p7 v1 y' r  j8.2.3 简写字符集1 q9 Y2 S+ H' S1 X; L
    则表达式中还有一类简写字符集,其等价于一组字符的集合:
    8 f: G: ~; M/ p& I3 D+ o1 ^# v" t$ k1 r; A/ }  _
    简写        描述
    ) x* ]" z+ ~3 A1 A7 u' R& o\w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]* @% l& e. g) ~+ U# g6 N# j/ }
    \W        匹配非字母和数字的字符: [^\w]
    * x, Y( ~$ k6 |, o0 Y\d        匹配数字: [0-9]
    + P& T7 d1 u& S. Z. h% G\D        匹配非数字: [^\d]" Y. K* [2 W5 R4 B7 ?4 U: r
    \s        匹配空格符: [\t\n\f\r\p{Z}]( m% `5 t% F4 X0 e6 o& o
    \S        匹配非空格符: [^\s]6 e5 y: A8 b/ m9 N  i( D1 S, O0 m
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    ( ]) N  K4 u: o( u; r6 G  Lre.findall(r'.s', 'Apple! This Is an Apple!')
    6 X5 L* R. `2 O( }0 V4 m/ iOut[37]: ['is', 'Is']
    0 S0 E) a& Y8 |" p  i" C" ^
    4 ?) I+ P& @  w. a3 [  Y7 ]re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次& p& w: K- ^5 K9 D+ E8 n7 Y7 z" j
    Out[38]: ['09', '7w', 'c_', '9q']
    . \% }4 P. _% g; v6 s
    - A/ N9 E! v7 H4 N/ b/ Tre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)
    9 c; `# ~5 \- M9 u" U* A& POut[39]: ['8?', 'p@']- Y6 A- O8 a0 L' Y

    1 q/ A+ J. ^+ ]( T! r% l- Fre.findall(r'.\s.', 'Constant dropping wears the stone.')3 x/ M( ?3 ~$ I# B
    Out[40]: ['t d', 'g w', 's t', 'e s']
    * t; k6 F8 H$ a! o( B7 ]- @# z% {( K) `+ r
    re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',4 H' D  }3 Z. }" ]% x5 X, N
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')* l8 R2 u- U# R  d% j! v

    $ t( T# O* {4 x' j9 {; G* XOut[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
    : i: c  `9 l0 i& g# d
    8 [; [2 e3 w6 G( N2 C18 o3 X  y$ e1 Y& \
    2
    + x9 A: Q0 `6 `6 Z39 z+ W$ j4 s! N9 ~  a2 F- N! {
    4
    . J  I# S6 Y$ b1 e; p5
    / r, D; `. h, p) g6( M+ s/ |6 V9 Q- k& L& j' S0 J
    78 t  G! W! S0 }3 Z
    8* r& Z; ~, M7 i2 B  W
    9
    0 t$ P7 J* {, t' O+ k9 v0 V10: Q3 N% `& O$ ^3 O5 z0 n+ C& B
    11
    6 i: T. c* J) D( b: o6 ~12! H* E( ^( A! N
    13
    " N' R$ z2 W. c8 n  h$ D8 ^! {# j147 l  q. l2 \; s7 ^' c1 e2 r
    152 n% K) j: |/ U1 e+ Y
    16
    & \6 l2 c* c! `$ [8.3 文本处理的五类操作. O7 T7 l. O: G! |# @6 g) D
    8.3.1 str.split 拆分1 Q0 \9 \9 k% @2 n" m& }7 n5 F9 a
      str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
    : ^" T. u: n6 ^9 B' b( M$ p
    ) c! @) e. j. W/ N$ d+ ns = pd.Series(['上海市黄浦区方浜中路249号',- s% I# ^0 q8 |! J+ P
                '上海市宝山区密山路5号'])8 A. `3 |! u# l) j
    ! J6 \" ]* S$ d- o) Z( ^( ^

    4 ~$ {% S4 H8 s- |s.str.split('[市区路]') # 每条结果为一行,相当于Series+ `' m) B& S& A
    Out[43]: 6 ]' _) J' d& ^" t
    0    [上海, 黄浦, 方浜中, 249号]5 j. |/ O9 ]$ B4 y, r0 v
    1       [上海, 宝山, 密山, 5号]
    ! w; u, W0 f/ |* O3 [# k6 Ddtype: object7 r& |& o3 H, p* q- u  p
    " `3 p0 U+ R, |! }' V4 |2 D: S/ q
    s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame: M' |+ f/ }8 B5 e" O' V* ^+ ]7 x
    Out[44]:
    ! H2 u7 y! [. _    0   1         2
    1 S" }, z2 F- R, g& J0  上海  黄浦  方浜中路249号! F# v7 T2 u9 ^, J$ o: w
    1  上海  宝山     密山路5号$ f2 A3 U6 ]+ v  z
    1
    # e7 i9 u' y* V  W' g6 c2
    3 b+ Z& w5 H! O' A  s3
    * c) ~5 [; m) h3 q( Q4
    * W% ?* U$ p& t, x5
    ; `$ _. [, c/ [$ Z- ^6
    4 F5 m$ T# w& C# W$ T5 [$ [79 B/ f" d: A% G) @% l
    8
    ; Q) O6 h! L" Y- e( O- I7 j9% S0 l, m( v) q; l, t
    10* s4 s& x; K* y2 z
    11
    0 E  i$ q, v+ _3 }8 E12
    2 q' h1 Y4 W, L9 I/ Y6 ?13% J6 s0 u2 t) a- t2 u4 y# _4 t4 f
    149 ?4 u8 D8 A0 `# R" G
    156 p4 r7 k0 B. Z" y
      类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
    ) M  b  p: S. S" L
    " w5 S. s5 Q5 A8 @: D8 E, ls.str.rsplit('[市区路]', n=2, expand=True)$ i$ H" G9 s" ~6 |' ?# u, |! S
    Out[45]:
    9 u2 e8 P% K8 n) c                0; `6 `& s/ R+ m  y3 K' w4 k
    0  上海市黄浦区方浜中路249号0 p3 z1 N; V! s1 e
    1     上海市宝山区密山路5号# Y6 ]/ K3 H" }3 ]3 E; N8 d7 F
    1+ u2 J+ S/ y  h% t( `5 _
    2  b& d4 @7 S8 b  h+ ?
    3) @3 \- X2 J4 ]# X1 F
    4* \9 F6 Q0 W( \/ O5 V0 q: ~$ Y1 U6 I
    5
    ; y" z& I( T0 t! l! R8 ?+ q# q/ d8.3.2 str.join 或 str.cat 合并
    - Y1 X9 ]0 y2 D0 Kstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。. e: @% v8 y) a: N* ?+ {& w
    str.cat 用于合并两个序列,主要参数为:& \+ s$ \4 n# T: |6 T6 H. d( M- ?
    sep:连接符、9 `$ ~5 a! f1 P1 g
    join:连接形式默认为以索引为键的左连接
    6 z+ T+ U2 `+ F1 j, bna_rep:缺失值替代符号8 V) q; m8 {+ ~# g
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    ' @9 W& h' j+ \& }3 P' I& C" `; cs.str.join('-'); X+ T: b- R- D1 l4 x
    Out[47]: " l- T% e' w6 j2 h  T, H" _1 U
    0    a-b
    % c" Q! ~# A& p7 D; c+ x. ^1    NaN! K: ~  B0 d, [
    2    NaN
    8 ~4 f& N) n# U4 idtype: object
    1 N7 I5 Q8 N- w1
    1 F! P. c( j  w: d, B( v2 x2
    9 {! @6 s5 f; F( B6 c; P6 G3
    ( e: y# ~- `1 D8 D+ V8 m. f4
    ! t8 D+ q( F! I8 X7 n5
    ) ~0 A) I$ O; L4 g8 F6% r, ~& t, o: J% Z9 A" ^
    7
    , @% J) O- `: E2 P' js1 = pd.Series(['a','b'])1 b2 [! N! n; e" Y
    s2 = pd.Series(['cat','dog'])2 w6 M: S0 `1 p
    s1.str.cat(s2,sep='-')
    / L5 V8 Y8 c3 o3 }. I# R" cOut[50]: 4 N2 m- d% l8 k# n! N* I
    0    a-cat
    3 B! w5 I4 a' ~7 b1    b-dog% f8 ^7 i) D: L
    dtype: object7 o, W% m" k0 `9 F* J

    / G1 V1 C/ |: is2.index = [1, 2]
    9 i# b1 o: [. zs1.str.cat(s2, sep='-', na_rep='?', join='outer')5 q+ E1 S  u' x3 N2 I
    Out[52]: 1 D" y" y5 d2 d) _( P
    0      a-?
    & T# W3 d8 Y$ I) [1    b-cat2 I# [  `3 V$ _8 b& }
    2    ?-dog
    2 Z) |4 L2 p8 V/ s' `% x) \dtype: object
    2 B5 ?$ u: S4 H, |3 B% F1
    . r3 Y) W0 F& g& I, B9 K0 T2
    ' K0 z5 ~; z* U* P6 M3
    : y8 Q' J/ L3 |7 B/ @4
    5 Q% P1 k4 d1 F$ T' R* {. z5
    0 n: \. |* v; O6 R7 q) B6
    $ g7 t3 T: ]# r& D: x7
    " f6 ?% R- v& v  J; K8 C8
    1 ]' m: L8 S0 N+ }" T% v8 h9
    ( t. f$ ]# C7 a  Y4 @10
    " H5 d' O" [; k  T11& e2 s9 @$ n; E7 p5 ?
    12
    & H/ y4 i, S# E- c# s13
    , y9 j7 g6 F) s9 B14
    0 \, I2 u7 p9 x& Q  s* n4 \1 V15
    4 H: c- b2 `9 o9 U" |8.3.3 匹配  i% g( {+ p3 @# s8 M0 n
    str.contains返回了每个字符串是否包含正则模式的布尔序列:1 m8 L5 B7 T4 ~! ^3 }
    s = pd.Series(['my cat', 'he is fat', 'railway station'])
    - G5 D  B; ^1 _: S0 {s.str.contains('\s\wat')  I2 W: b$ M* Q( _+ @0 j; e
    6 p! m% e- O, ~
    0     True2 D6 `1 X4 p& H: D5 B  K/ U4 t
    1     True8 _1 A; m) |' S
    2    False9 Q) j! `% ]8 c+ L, `: l* O: Y
    dtype: bool; K8 n- n2 M: c: u: \; M
    1
    ; n: _* Q. P4 M7 l5 m2
    ' l3 l' _* Q: Z: c3
    & C/ K6 {! ?1 C5 M0 O* r, u41 [6 _) |, {1 V' g3 K4 T, K/ s
    5
    + ~' J0 M7 Q  q$ n67 `* G; t$ U, Y2 ?
    7
    ) h+ {" Z9 S- Y* A; G3 G4 e1 zstr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:/ Z) F& z4 Y8 ^" z( v/ B4 S
    s.str.startswith('my')9 n; S( n$ n1 z6 O( |: f

    $ p' w$ n" D9 p1 h7 f. Y8 @0     True
    # h: q8 W4 |, T5 b  t, Y3 R1    False
    7 x$ {$ p! I, [: D& g7 V$ }$ m2    False6 M$ x6 B3 K" |3 w- [% h
    dtype: bool
    9 V7 ]3 G$ d' `: z5 h1* E3 h1 ^* ?0 L7 h
    2. @) l7 g  m7 a, x# S6 y  c
    3
    2 }& U- n2 a3 x7 y6 h  w4
    7 @8 C" p" m9 m1 o( s; C5
    % M; w/ k: ~5 b1 @+ z( E6) M' g  S- U% e; G, ~
    s.str.endswith('t')# a9 i! h# C, Y  C$ T' {

    . @' c! a/ ?! K7 G* O; w0     True
    ! Q: X+ A2 J) |1     True/ e. {) V8 ?  s: Q
    2    False
    ( ^0 Y; I, |5 u. ?dtype: bool( \* O$ n3 H* C% ^3 r3 ~. ^$ C
    1
    9 E8 [9 Q9 O% ~) J# M4 c0 T2
    7 e  _% l9 D4 m4 I3
    & M- d& p% ^; f. M' \! @" ]* d- r4
    : q; z" g" Z7 F* l, y5
    ' t% {/ F( ^4 x) Q. m6 x6( n  Q4 V8 d! A$ _- R' J" S
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    : B) h0 Y8 ]; X7 D2 l* h( Vs.str.match('m|h')
    : m9 ~0 S9 P$ F- h* {5 S$ Bs.str.contains('^[m|h]') # 二者等价0 x) F: ]2 R9 }1 n. O5 ?. U+ C! [
    . n* G! O$ p4 k, u9 J
    0     True2 \  C0 i6 O( @: [
    1     True' Y" d1 E0 d: ~. E- R2 e/ C
    2    False) m+ V) ^4 m6 f9 p7 O
    dtype: bool4 a4 U  m& t2 {  q& G1 Q0 {# K
    1
    * d7 I6 i: o  R+ C2
    ; Q6 _8 z  I. Z. L) I3
    ! I/ e3 Z# r" M5 b4
    9 l5 {; q" i5 m, S" M5 j5
      J0 S7 F6 U! x, d+ }69 I3 m; p& A3 p2 n1 s
    7
    - C- V* g& F, c$ ~+ is.str[::-1].str.match('ta[f|g]|n') # 反转后匹配7 n( i9 f  G3 d0 G
    s.str.contains('[f|g]at|n$')       # 二者等价- d( p: i- t4 e; K: _

    7 s/ W. H1 A" L0    False
    . T8 T* i! f; u4 ~' ]! K1     True
    2 h: |$ r( y6 h6 r( ]2     True
    - L  ~0 Z& c" Mdtype: bool4 m# O( \/ m% w+ |- @5 I4 T
    17 M: T( ]7 {1 ]
    21 e( s7 P7 y' c9 ~' y, l% G
    3: I5 D6 n4 Q1 v$ A
    4, u" d; d8 J1 s" y5 ]7 T1 g6 S! C
    5( T# v1 e5 k* A( C% L% v
    6/ [$ ?( B5 L$ s6 a1 T
    7: S+ x. k9 e: }7 i7 g/ h! c
    str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
    ( x4 b% g4 \" ms = pd.Series(['This is an apple. That is not an apple.'])+ S/ n( R$ A3 k, `
    6 m  F! ~- h- N6 P5 F: Y0 F/ ?
    s.str.find('apple')
    , t/ V; }( ?0 ?Out[62]:
    $ @/ t$ ~6 A2 S4 \7 j0    113 B, `% i# S& {, Q- g
    dtype: int64
    * k  L* Z* t2 J( q
    * s& A( N, y7 m* es.str.rfind('apple')
    1 v6 n& y! D6 K3 Q; eOut[63]:
    9 }( p! ?& f0 Y0    33
    ! ^0 |! r) `3 r; n; wdtype: int64
    9 m, p! ^: Z: b* M" y1
    " Y7 y  @6 O/ o. J: P- E2
    7 S# _8 y' o, b1 w+ x9 x/ A) v3 \3
    ) I1 s1 {  a  E7 F- C" n" H% [49 f1 k& c, E, @" N
    51 i( E1 F9 `$ y3 a2 M. l
    6$ u/ a$ z# \, ?
    7
    6 V4 d+ b- Z" n* b: B, A8 m+ s8
    0 W3 l3 l4 w! z7 {92 b, o( P2 H7 H+ E
    10
    ( l, d1 n/ Z5 a6 p1 g11. I; W  t& [9 q) F9 e; s% h1 b
    替换
    " ?: i0 j1 J! H' X' K7 q$ Xstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    5 R$ z) d8 b% [7 Gs = pd.Series(['a_1_b','c_?'])0 d7 O1 p  s; c2 [6 B/ V8 v
    # regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?! m( ^1 y. H7 |  N) O2 a8 \0 ], v( v
    s.str.replace('\d|\?', 'new', regex=True) 0 U- L. m  S+ T6 z7 C3 M" j
    ( n( B2 _7 q' B' R9 U3 F9 a
    0    a_new_b
    % T/ e) J7 g& b2 r1      c_new
    . _' L- W8 \4 mdtype: object
    % O! F3 F& ]( `# W$ P10 L' W5 P- K- X9 Y. k. n5 P
    2% ?1 U: t6 n1 |* c' i# B1 v* |4 ]
    3
    * K, h; R" w. G$ E) ?0 x, \) }( @4
    5 `9 y$ m' Q( k3 e  ?0 r57 o5 P9 W7 I1 c7 e  \+ N
    6
    9 u) J4 b* ?/ P% M; v7
    ( Q( ^3 ?8 G9 Q) F/ L  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    , h. Z; B/ k8 h5 `# l- t" M( P# |; e  T
    s = pd.Series(['上海市黄浦区方浜中路249号',
    : f" U$ t4 S4 d! f2 B  u5 j                '上海市宝山区密山路5号',
    / I, N7 T' G# Z& A. O                '北京市昌平区北农路2号'])
    * t$ n: r6 F' h3 S/ t( Kpat = '(\w+市)(\w+区)(\w+路)(\d+号)'1 ^  f# N5 |$ a( e8 Z0 ~, x
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
      V: b4 i- j  zdistrict = {'昌平区': 'CP District',1 ^  J  ~* C1 ~7 k1 W& }! \' ]
                '黄浦区': 'HP District',3 ]1 K0 U! N* h# f4 X
                '宝山区': 'BS District'}7 a0 ^6 B5 e$ q
    road = {'方浜中路': 'Mid Fangbin Road',& c0 A. h+ @3 y; s6 g) m
            '密山路': 'Mishan Road',7 ~1 V8 {! S6 b7 \% D( E7 X
            '北农路': 'Beinong Road'}- l; `) R5 N) p% \8 F9 f
    def my_func(m):6 J0 D8 K6 E6 Y1 J; I! y1 I
        str_city = city[m.group(1)]( ~1 z5 q8 d% n& V
        str_district = district[m.group(2)]
    " A$ l+ N2 E. f; J    str_road = road[m.group(3)]3 d* c. Q' H/ F( B- T- ]$ H
        str_no = 'No. ' + m.group(4)[:-1]5 m0 ]) t- J" ]+ ~! n% O+ e2 a
        return ' '.join([str_city,* K% I" P/ ?' J4 b3 d
                         str_district,
    6 l+ }8 ]& x4 [1 G                     str_road,
    2 G( {6 @9 J) ~: m$ Q3 ^                     str_no])
    $ l# W: r( P( |0 t* }s.str.replace(pat, my_func, regex=True)
    # w# L8 |3 c$ }- _# A; A! O; E% w9 O. o& U. D& k: a- N
    1
    4 i" [2 A' n& l# H& s+ J2" e& |- ~, J7 x! ~
    3( Y- u/ L' o0 J+ p
    4: b$ J& t& I9 T  g; U7 G/ D7 y
    57 S- s* A9 P& w5 B: H* a
    6
    ; }- D  D3 [5 b6 E+ \: m6 b" V7
    + T/ d" k  H4 k9 u' I, ?8
    % C- K0 l7 R) F9 N! _& o9; {  C8 S" V! p2 p! G$ Q
    108 e+ |4 X! H* A1 v; F2 o
    11, H1 V% a- [8 Q  B1 q0 h
    12
    1 i! x, X1 l+ b# g; ?% Y13
    - ~& u* f9 p$ D& a# k6 b14, Z# B; i9 U9 I; V2 u8 f% w
    151 b, e5 K: n- u- o3 a
    16
    4 X& W8 K2 v+ Q: b17
    , J4 X2 l% F8 r% r  w* D18$ y/ M* ?& J- c, k6 D
    19
    " X" i' v/ U7 T7 p7 A. l20
      P2 l* n/ i& }( H0 y: H21+ f6 A0 B4 y0 X' R# m! _' X
    0    Shanghai HP District Mid Fangbin Road No. 249
    " a0 ^& }* [, X1 W4 N1           Shanghai BS District Mishan Road No. 5
    1 W) ^" f; L3 V. H2           Beijing CP District Beinong Road No. 2* S+ Z4 S0 D/ |1 h$ G; \
    dtype: object$ o) d" O1 g/ ]; W1 _
    1
    8 a: r- `$ Z& M  n, J- K2
    $ ]3 v" f$ X: D2 {3
    ) N' Y6 Q+ F8 `1 E" Q7 i, Z9 O/ D45 x+ r  P  H9 L+ |; c. H+ X
    这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:  h/ P3 {+ D( X+ P

    ; m5 c5 u5 T# u0 h# 将各个子组进行命名
    : l& e8 D: r7 R; ]4 O  }pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'3 W! z- j! ^, h! W1 d8 O
    def my_func(m):
    ( a* C( W% _7 n: F, |: O& D. ?4 m    str_city = city[m.group('市名')]# W+ r# a4 z8 a0 y9 c3 [# `+ \
        str_district = district[m.group('区名')]
    # N# q7 {% C2 h# N% z    str_road = road[m.group('路名')]/ U. I* i  @0 I( w
        str_no = 'No. ' + m.group('编号')[:-1]
    + e$ Q6 a& F& t: {% _    return ' '.join([str_city,
    ! h6 _. W$ z( ?% j: \* x  D                     str_district,( ^7 c: ~: r/ H& m! }( |
                         str_road,' E0 ]! }' |1 n) b6 I, W$ b
                         str_no])
    & ]7 m# ~& i5 n+ h, is.str.replace(pat, my_func, regex=True)0 s6 q0 t# J: w+ o- f
    14 W( T  r) V# U* W3 C' O. L, F
    2
    $ X7 _, o4 S& V39 ]7 M- Q0 `7 h+ Q
    4; O- J7 v+ k. N
    5# w! M* ^: S& o. W/ \# q
    6
    % t" x' P7 d. m" N7* b/ I: c9 w8 s, ]. x) Y+ ?
    86 C$ b$ s1 l' o: g; R5 O7 ~
    91 }$ K1 e7 h3 s' _
    10& o. G& R, h2 M$ s& I  ]4 w2 f' }
    11( }$ G& P9 |: D6 `3 k  K4 @
    12
    4 V- W8 i: O( O+ v/ Y  |. Q( ]0    Shanghai HP District Mid Fangbin Road No. 249
    " j6 X6 f1 ^" j6 o5 @% ?1           Shanghai BS District Mishan Road No. 50 {, s. r# i, V! A, r* a7 P
    2           Beijing CP District Beinong Road No. 24 |- _1 u1 P& ]( ~; o) T6 s
    dtype: object+ n) g) P6 u- M' J* s4 {
    1
    7 W. d; T/ I4 \& o2 I9 v, u4 }8 x2
    ) k4 ?4 {0 o9 G, H) f& }- }3% Q- q1 `- e9 n
    44 d5 [2 |  t+ U
      这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
    0 O' h& c, d1 U& z( N( u* A- o" ]: ]. V
    8.3.5 提取
    6 t% U3 }. y( ~4 astr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
    ! ]- d' t/ y0 o, ?5 y0 s$ ?s.str.split('[市区路]')
    8 F9 L6 Y: p  O! _/ `, |7 I" H6 qOut[43]:
    . d5 i( M9 p& _( D( W4 C0    [上海, 黄浦, 方浜中, 249号]  z6 k' Z# p. g
    1       [上海, 宝山, 密山, 5号]
    / G! p. {2 N8 R6 t, ]: Ndtype: object
    4 y1 J4 K+ E7 P6 E7 d
    6 d- C- c% E7 l( ?& l) f. J+ C! `pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    3 n3 P+ S% N* R# ~  Ws.str.extract(pat)7 p3 N' c( q  j; O: ~/ i
    Out[78]:
    + Y$ o# s7 e7 S$ N' b    0    1     2     33 t! ^' u  C$ E0 c; o
    0  上海市  黄浦区  方浜中路  249号/ O$ Z$ ?: W* S2 n+ Q# _
    1  上海市  宝山区   密山路    5号4 |; N3 T+ C7 L: _) X1 V
    2  北京市  昌平区   北农路    2号' c0 A$ T$ j3 U1 `0 K5 c
    11 S$ j! t; I( ?
    2
    2 L% d9 I" U/ y9 I* X8 h  ]3, m9 A" h7 D% A' y4 O; Y
    4
    + a0 }( R# Y- ?+ m8 i( _5/ z2 A9 Z, Q2 E# ]+ n" M; R
    6
    , G2 P$ S7 K: k7 @7
    , ?0 }8 K7 ]3 U( h. |8
    ' {1 o& n7 `9 l* }" @" c, ]9& O0 A: F1 y2 G" C; X
    10
    3 t* C( U$ `* ^3 `% S" X11
    4 R3 g$ H5 k/ A& `4 C12- O, O  Y7 E- j- [
    13
    9 h1 ?* m) [0 M通过子组的命名,可以直接对新生成DataFrame的列命名:$ N% @, O$ N9 H! a( y5 D
    % A: S6 s7 E$ [2 U- |
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'* F8 C2 h1 y" x1 G) m" e
    s.str.extract(pat)
    % L! f  ]$ [# M/ i/ Z& COut[79]:
    ' I3 M% J7 m$ i( I% e" D7 P+ D% @    市名   区名    路名    编号1 D9 A. L, W7 _! M5 z3 V
    0  上海市  黄浦区  方浜中路  249号7 R- v6 G3 R0 M" P1 v2 R( a
    1  上海市  宝山区   密山路    5号
    9 Q" ~% k7 B! l2  北京市  昌平区   北农路    2号7 @. d( n8 B( n/ W4 u* C
    1
    & b) F+ k. L3 v/ k4 ?9 Z1 _$ p+ Q2
    , k9 M  _3 [, d$ S3
    * f' {$ P0 N5 ^" ~7 n4 G4
    1 t# R4 L: E% A% e2 L$ [5
    " t  t! l) P6 K' j6! i" @. M" E, O2 b3 v# C4 e) ?
    7
    & }/ z  {' r* Bstr.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:
    9 ?2 ]" h5 Y5 o$ q+ Fs = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
    5 {1 F: t0 J; ?! X5 D% Fpat = '[A|B](\d+)[T|S](\d+)'
    . L) E- s1 _; K& i) hs.str.extractall(pat)9 M1 b) l7 @  A1 k# z
    Out[83]:
    . j3 {' ]6 c$ ~       0   17 ^! ~- ]; i+ D* }
         match         + _8 H) u  C/ |$ D
    my_A 0      135  15
    5 Y7 h# z; I0 d/ p4 Q+ a     1       26   5% H+ H$ I+ U4 h
    my_B 0      674   20 q; H8 V: K" e. H5 F$ a( _! `6 X& e
         1       25   6
    : l) d9 A+ O3 m3 K. P4 M1. O5 w: U8 e1 r0 O! I
    2
    ; Z7 B8 }5 [3 `3 L! a6 I1 ?6 U6 b34 C# d* D2 M8 |+ \( \
    4; R5 x; Y0 h. ]; e; ~
    5
      k3 G# p) u4 l63 e/ V7 K. }- L, b( F6 s7 b
    7( x- N, ^, J  B& d; {; b
    8) F  d+ r% f3 f' t% R2 j+ u( t
    9
    2 X2 g3 s8 D5 k; R2 @4 x  m10
    ( U& n# _1 T) {+ R: ?! Hpat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    . B0 D- ]; J: V) u! y1 r) Ts.str.extractall(pat_with_name): W3 ]& `1 D3 z/ _. S, s- e, b9 c
    Out[84]: 7 N% v% {2 o, F( X4 j! Q
               name1 name2
    ' C4 m# T% s( k& {; o4 S     match            
    ! _$ r7 {3 n$ ]! N& vmy_A 0       135    15$ _, _+ S9 h7 I2 r! j4 }- @
         1        26     5! l" Y8 a5 X" Y: o( v4 u  Z
    my_B 0       674     2
    + _( ~* v- ^* q  X9 c7 z     1        25     6: i/ ?. ~. v; v1 B/ x5 z4 t
    19 p4 ?8 T$ C/ L! O- R4 S7 X" L* q
    2/ B3 P: w3 f* c4 V) P: q
    3* }0 S* H% T9 j( G0 C* I
    4+ R8 {) K5 p( z" a9 [- E$ [
    53 }* z% L* W, I, ~1 R7 d9 O
    6) }6 c! Y: V) S; [) m& u4 a( l
    7. ]' @% G( k( q+ z2 f
    8# Y" Q6 W$ y* X4 s. ]' O
    94 d2 P( c* f9 ^! G+ {* ?- E
    str.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
    - b% ]3 A( d! T/ E+ y  ]s.str.findall(pat)( ~6 ?& d" K; m2 L4 ?7 Y
    1
    $ H: o7 {# _+ E: tmy_A    [(135, 15), (26, 5)]
    ! c3 l! L# q  wmy_B     [(674, 2), (25, 6)]
    % }4 [2 Z3 A' j4 L5 vdtype: object% D/ {4 u7 i% N7 w
    17 T/ U( ?" `! {7 O
    2
    5 d" c6 x" n( G' ]6 \' u( T3/ m0 a9 a) w! ~  L3 g5 q2 p& X' {2 o" s
    8.4、常用字符串函数
    . m; i/ A' A7 _: w: n+ s/ i  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。4 W5 n6 }. F. K9 R. v  n, r' O/ m
    + o+ Q& E8 g( z
    8.4.1 字母型函数
    - B, L- v8 X5 c+ J* t/ Z- \' `  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:" e8 O" v/ }* {
    ( X3 v. P. k1 ^( W0 @
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])7 s5 \  s8 g5 Y4 r2 H2 ]  V

      ~- t0 j8 _% E8 B. F' Ys.str.upper()
    , C' K" j6 H6 N3 B* b9 AOut[87]:
    ) }3 H# F/ A( j3 O0                 LOWER
    & N" L& w  m; U2 C  Y1              CAPITALS
    $ t% E9 l! U; Y1 l2    THIS IS A SENTENCE6 d8 R% Q' l+ r+ g3 h: R  Y
    3              SWAPCASE+ Z% |6 _( Q1 q# d4 o+ q0 X! c
    dtype: object1 {: p0 ?# P% a( f; _. [
    * e' B  D4 c9 Q' h
    s.str.lower()( ]7 d' n7 ?6 |6 I' j5 W
    Out[88]: 7 T" l. T  }9 {
    0                 lower
    4 S; n( j* G: U9 D1              capitals
    . I1 C# _% R8 F- `2    this is a sentence+ M2 l/ Q# p- k& `7 `
    3              swapcase
    2 d4 F! _' L8 [* g, T. u$ zdtype: object# E$ G+ [3 c* c* n5 S/ U3 `

    6 ^0 d4 D6 ^/ V% [* n# us.str.title()  # 首字母大写
    ) ^: s: U- q$ l$ jOut[89]:
    % t6 F$ [; p* M/ H" ?0                 Lower$ w8 B5 b: O& L4 ?
    1              Capitals
    1 ?! C$ s$ B. _; D4 f* p2    This Is A Sentence
    2 p8 @) g* z. p+ A8 Q! p3              Swapcase# K+ s6 s& i5 z( V
    dtype: object) x# f$ ~; {  d1 c2 H- o: E
    5 B. t+ z3 O8 n5 r  Z9 w0 s0 ?3 c
    s.str.capitalize()  # 句首大写
    + S$ ], s; E( V6 D$ Z5 r& \2 D# IOut[90]:
    - ~5 P0 ^+ j/ d0                 Lower2 \. d* o9 x* \$ J
    1              Capitals! \9 @( p2 E# A/ D, ^3 z# i1 F; b
    2    This is a sentence
    9 u5 x7 }% B! A5 N. u3              Swapcase1 a/ ^: d/ A4 {  P
    dtype: object; t0 s% B7 q7 H* W

    % {3 |7 z8 C; D: \s.str.swapcase() # 将大写转换为小写,将小写转换为大写。! W% f0 Z" l" L6 O
    Out[91]:
    " f$ ?( A7 j6 `  b+ j( n0                 LOWER
    0 v9 m7 e; ?8 q2 D! [& `9 L1              capitals
    . [$ `5 G: X; I& c2    THIS IS A SENTENCE  [/ l  t1 o0 k5 Q
    3              sWaPcAsE
    8 i7 A9 ?/ J& \2 U; adtype: object
    0 Y$ P/ e" b6 C( I. \4 b  d
    : v7 S$ g- o. Y6 f0 d$ ^. O2 w0 ps.str.casefold()  # 去除字符串中所有大小写区别
    , q: y8 A6 M# h# p& I
    + l1 m, Z  E  p1 @0                 lower
    6 c( P, Y3 x% b# a$ M7 ~) `" V1              capitals
    2 b1 n# J. ~) |3 M3 O2    this is a sentence, ^8 ~" Q/ B) e0 z- U. m" S  \, Y
    3              swapcase6 y7 m6 c% l; z

    9 r1 P% M, O# D2 J, n1
    ; s+ F$ f; g$ V7 e% `) s26 z- D/ B3 H3 p" u5 A) Q
    3
    8 r" I4 P% l5 f- t7 O* _4
    6 r: x* M0 @; |' i$ Z7 M5
    ' `% c2 Y  J5 f' F5 V* H6: H1 H( ^2 |0 F0 Q" v# _* }: g
    7
    ! j6 m  s3 c0 \) F8
    , ?+ `; q' k' G; k3 G9
    4 {; s( G9 M3 c2 R4 u( d( G: c4 q10$ r  g# e- v7 o$ X: J5 C
    11
    ) X( V/ K2 j/ A" a8 r' ]12
    1 [2 ^/ g4 k( ^( L0 e3 V13
    ' N# [9 B; X& E% s14
    / V; G5 b# q8 s+ e6 t9 B) w5 \: Z( a. S15+ [& m; Z) W; |3 g
    16
    / o, J0 F# Z4 N: {0 c0 O" d: e178 I5 e  _7 W4 ?. V+ S0 l
    18# q1 [0 q* j* J! \: _6 \
    198 w1 L; }# l! \# x6 j, ?
    20
    1 Y2 D9 T2 o% |0 U217 x$ _: i6 A( ^
    225 n! X! ?. y8 B5 S# J) B; D
    23
    ) t' A  F& L7 y; H249 D% _" D! z+ k6 X' y% e
    25; r6 D2 F7 q8 v& r/ y
    26
    $ \* M0 _" k/ ~27
    * t& _  `# B/ i7 z' r28! Z4 @/ r( S% T& W
    29
    0 {0 d2 ?4 S+ A* X  L, O- Q30$ C: C  u+ V8 l1 C8 O) P
    31) g+ _2 S+ ]+ l: n+ i
    32  I! _4 i) C/ P+ u  _
    33% d* y- O5 Z! m( {, \5 t/ ~4 i) k
    34/ [  y* n3 {$ E. ^( J2 V
    354 a# J6 x9 v) g) {; b+ I+ c9 l- W3 F
    36
    ) h# e/ ~& J1 U; C7 E. k37( K4 V/ l% G1 y8 t8 T# \0 O
    38" ~, X* [& G; J
    392 L8 N! J/ ?9 W+ x
    40
    1 S, X" {, d. p) n41
    2 c: ?* X) A- W8 Z7 T3 l7 B3 d42& Z* b$ G  a& ^( n& }
    43
    6 W  ~: n* t9 s  }" ^1 I440 y1 f. [2 n$ `- p' e( w) ^+ J3 z
    45
    * L/ ]7 J$ Z4 F1 x46
    0 z) E1 `$ x6 g$ c0 a47
    / a' _) {( m! n. i8 w( |48! U; P9 }$ u0 t1 X
    8.4.2 数值型函数# R" }  J) E9 V, @: {1 F. [* {+ M
      这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
    1 O2 r/ k3 w" `% y( @
    / `3 J* H' u7 D3 O5 rerrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:& g1 J$ A. I+ k! P* P" f; U1 ?
    raise:直接报错,默认选项! D' Q' I9 v* W# g
    coerce:设为缺失值
    4 C" X/ |/ C% e( c7 a' [ignore:保持原来的字符串。
    * G0 E; P' o4 q! Y4 |6 _& udowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
    ( |! G2 s: Y7 q! ]s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    / F- C5 A( }- D, n: G
    ; Y6 R( j, t0 kpd.to_numeric(s, errors='ignore')
    / n  Y0 N. B" s; BOut[93]:
    ) p- r1 S5 P  B' b. J/ u! S: ^5 Y0       1, `3 s$ P# O6 G2 I6 m
    1     2.2! E* L2 Z- O2 p# W
    2      2e
    ( e9 H7 R5 k& ?8 Y# W' N# E4 E3 Q% c3      ??9 A- J8 D. R6 n( O9 T5 Y
    4    -2.1
    - m  o4 b  r  }2 n6 ]  o5       0/ G1 `, l- g7 J  x9 Z
    dtype: object
    / u1 q% `8 e  K8 C7 s# `7 h5 `1 Q4 x
    pd.to_numeric(s, errors='coerce')
    2 Z- \& y( }7 {  _0 M/ zOut[94]: ' n6 v# o  t& o9 A( Z. X
    0    1.0
    # b# R8 G' E" K  W* q. X) B! Q1    2.2
    , J2 f8 e; c% h: \2    NaN! r! ?+ E. D9 H5 N
    3    NaN4 p  S& [& @! o" Z
    4   -2.17 c; A" i: E0 I$ j, {
    5    0.0) @" v4 m6 }" h( p
    dtype: float64* m& V) f. I( }, J- O1 ~0 `. w9 r

    ) s( n5 q" d  A7 E! b( I1
    ; M0 P4 s. o$ i+ h& U26 R! w+ f" v' m" O' [# j6 B
    3& U) K1 k& f, [  @
    4) v$ h8 c4 ], `; b
    5+ A3 l1 ?2 ?, Q: ]0 }3 J7 C6 H7 l
    6/ ?; O: a+ X/ U+ v8 j* s) R! Y& G/ S
    7
    4 a6 h7 o' y. t2 _) E( x7 P3 o8
    8 `9 ?3 ?/ \. u1 v' e95 g! Q5 D% ~( ]& u) B6 C; h
    10
    & X: U* K0 Q, t9 T' ^7 H0 f11& S: K: f1 A5 Y/ I6 s
    12
    7 v& e' j2 f) V3 A! Q13
    1 y2 F$ H' r  g9 ]- Y. o14
    : X5 d1 w9 p+ u: u; `" {. f1 ]; b15
    # |8 A* i/ v6 I( K16
    + _6 A2 {' M( E" w17& `! ]8 A5 W  n: ?5 z( e5 J
    18
    5 t% D% I* d( P190 G) s/ G/ y5 J- \( v
    20
    # G, ^- d. E. |  W21/ r/ w6 C9 X2 O  |
      在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:+ X! N! ^% q$ f+ |  I

    7 x+ F: L  {4 {* W$ Is[pd.to_numeric(s, errors='coerce').isna()]6 [: M. n( C! @7 m& D- b/ J4 O
    Out[95]: 8 R  g$ t! Q" e: ?0 v
    2    2e' K1 s9 b/ S" @$ Y% \
    3    ??( j4 v# k* y+ Z5 m5 |1 R- X" v
    dtype: object) M0 K9 b& N2 K$ q! h
    17 k1 l# A. N2 a* _
    2
    6 C$ o' Q& l$ X# @( C3
    , _- M" a% R6 a* L4
    ( K  V4 Y6 `; T( L, X5- l' C; U3 T6 A
    8.4.3 统计型函数$ Q; V* z* H% M# Z" i; E4 y
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:
    $ I' q0 h' a" d4 m6 ]
    1 ~' O; c6 r( F4 es = pd.Series(['cat rat fat at', 'get feed sheet heat'])% z# x  e) a( X

    3 i" G2 g! h% O. Gs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次  L% q! q* }% P- L5 `: a) e
    Out[97]: + k- c7 p* B# z8 v& P: w
    0    2
    6 w; e! [' j; x1    2
    . o; ^! D5 _# }& Y0 x# v; Tdtype: int64" \' p9 p- |; q7 z  {
    * f$ ?( V2 ?9 P
    s.str.len()! h& E0 K' N" ^4 G2 P
    Out[98]: 0 @6 P* \. ?; r( {9 Q
    0    14
    5 @0 s1 u- l! s1    19) p% V& o% _+ X6 |7 J' a: \9 D4 q
    dtype: int64
    9 U$ \6 @( m- L' J  Y2 K# P+ n1
    $ U7 I0 J. @' R5 ^3 s2
    7 z+ F5 U- b* t6 o7 g32 {+ @# k( l- L& W2 L- n( b
    4
    * f' Q1 y2 r% _+ ]! w* ~5
    + X7 V# K5 u) p  S; n0 i4 a) N# k6
    * K4 w9 s) V% ^0 c/ `7
    $ D1 R/ S9 e5 \80 [, v+ \/ n- A8 n' G2 ~+ j; @
    9
    * z  W" D1 P5 g, b  u/ j10& {7 c7 U6 S: P1 {" K% z1 A
    11# X: z. h3 u& F" O: [8 F* g# M
    12
    * F" \# n; f8 h  E" N13
    ( d. Q- Y: i* @2 V8 `, Y+ J' Y' `9 O8.4.4 格式型函数$ q+ T& x5 F; D
      格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
    , L" K" e- @1 E, i
    $ q% ?" ?: V  j$ [1 o' H7 q/ nmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
    - |; h4 c5 r) o$ M& T' V  c6 _( l7 F# K
    my_index.str.strip().str.len()/ v: e& E& W* K! x: V
    Out[100]: Int64Index([4, 4, 4], dtype='int64')
    3 d' w. q: x# P8 U0 l7 B6 d9 P$ @+ i) d) r- f9 ]
    my_index.str.rstrip().str.len()/ q0 r$ n3 H3 T" o0 _" F" V7 Q% O
    Out[101]: Int64Index([5, 4, 5], dtype='int64')& B: n0 f5 G* b& p

    / S- [, k8 \+ X; f% h8 N6 }4 Rmy_index.str.lstrip().str.len()
    6 u# F0 c7 ~0 f) @3 jOut[102]: Int64Index([4, 5, 5], dtype='int64')) ]6 l- i  @9 v8 n( L6 m# g' h
    1
    ! x& o2 C2 n8 j& c0 R2
    4 H1 H; C7 O: c! R2 ~3
    7 m/ d6 c0 z1 ?  J; b4
    4 |  F2 m9 Z+ F4 b4 s% a52 H% P! O6 e: V6 ], s7 c/ t6 K8 ~
    6
    ; ^! g7 s7 t# e6 s7$ j: C2 r: h- g
    8
    $ n# S. H4 b0 v7 }, o) k9
    $ U% t* `( z. X. `3 Z+ \103 ~% `  j/ O4 E0 y/ z" C; g% _
      对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
    ' `1 i* C( Z. ^  _- X6 E) x" g2 f" \; R% n4 t
    s = pd.Series(['a','b','c']); d+ T# {" k( G) y
    3 I! \' h$ O9 U  v7 C+ ^# S
    s.str.pad(5,'left','*')2 ?& [8 C8 r* [7 `( z, R8 S/ p6 M8 C1 }
    Out[104]: / V: ?; j- Z; |+ w+ R
    0    ****a3 \/ E/ {9 ^# G5 j) u$ v9 V
    1    ****b8 H% w0 t& E, U3 ]0 s; {/ a5 a0 z
    2    ****c
    9 P& m: Y! D% k) tdtype: object
    , T, y% c, C2 A4 O* M! O/ G% Z; b! M4 k7 w
    s.str.pad(5,'right','*')* Y! P* k7 F+ f  a6 O6 m
    Out[105]: " s- S8 }1 P, u, J( z* T- j
    0    a****  F' q7 }* R6 Q( @
    1    b****1 A! a, o  ^# q; q# ~# E) ^
    2    c****2 t8 Q' c/ L! U- y9 E! h" T$ t' Y' _. ?
    dtype: object
    " j2 z7 ~: ?$ ^6 |: p% I/ ~% R: g! x5 N& B- A7 v. z% j1 Q; H
    s.str.pad(5,'both','*')9 U* Z: d/ ?1 u
    Out[106]: % d: C2 P/ {# I) A* g( l! s
    0    **a**
    & p* M1 I$ f- g  D) U1    **b**8 _6 M* |) Y7 _+ u5 [. H
    2    **c**! F0 U# a! O$ {% P) u$ a
    dtype: object
    + A( G: Q% }; k) |  t& ?6 V( [' G9 N9 z; p% J" _
    1; t& F$ i/ Q- P3 @; @
    2$ R+ @7 f# X# V9 G
    3# D. i8 V5 D' G& a
    4
    & I9 o4 I  Z) B) a5% h5 |4 [/ |( o# J- Q, b& D( Q
    6
    5 K( Z% [8 \, m0 y4 v) b+ c7
    9 _! k' b6 X2 q% r! B8
    $ g4 {; r' u; b* W8 i+ Z9
    9 _6 P  j+ ?! w- }$ g; E10! e  v+ I: b  }1 A
    11
    3 O3 }4 P& N& k2 p) ]' R. X/ U12
    & B3 o, w4 Y9 J: ^: K# M/ ]13
    & B8 b8 x4 ^6 T14+ J# l6 h% Q" {
    15
    8 q0 G. m% l) w- N' j- w16
    # N$ _& n% J7 p( p" l17& v! |6 |; {" u+ s: G
    18# N& h8 J& V( @, ^7 b
    197 m9 @5 F9 b0 A" s9 \9 A% H
    20) V/ @/ {3 Z. B/ j* a3 z
    21+ v9 y" W" ^: c* @; W! S* y
    22
    ; Q1 k' q. G6 Z' x  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
    ( j  D5 V+ Z. g; Q" b) L8 @/ I( O, V2 y! `! e/ L% x- [
    s.str.rjust(5, '*')
    9 E7 u6 c" ~' d0 R& M5 k9 NOut[107]: / m( {& w) K7 e% v
    0    ****a, @# S& c* A7 W  M% |5 l, U
    1    ****b+ k8 I6 m- H7 A
    2    ****c% _$ [! j& {- ?, v+ U
    dtype: object0 M0 R5 w1 K& n2 _7 E" b
    9 g% X9 y% z% V4 _
    s.str.ljust(5, '*')
    5 Z, H0 }  d2 D! T' H/ uOut[108]:
    7 {, s3 J* R. t9 n0    a****( o5 v& D9 X; l4 [% e/ p! w" T
    1    b****( D4 f1 |- o: m% ?6 r, `9 J
    2    c****
    % c* R- |; T) M; T9 U4 Z# Ldtype: object; n" H8 ^" r, q3 D

    ! u9 q3 D, K8 {& Ps.str.center(5, '*')
    , K2 R$ e* Y8 F( |1 D9 O( COut[109]: , n# A) f' ~5 y$ ]) Q3 b7 z
    0    **a**( P% |6 g/ P4 l/ J; W6 K0 [
    1    **b**) ^( z1 c% s* E# q
    2    **c**1 U% q- \/ [0 Y& E8 P
    dtype: object
    + y( G  F6 L3 Q, Q' u
    ! o) u& C  ]: m5 q+ W1
    # I6 \* E0 j1 t- X6 O2: x3 c* ], Q8 ^1 D
    3
    ! @  N. g- g' k8 o5 r2 w! n4
    / G# p  L- q( l" G5
    7 w6 l& F- }' N" P/ h( N/ }8 R6
    " _1 [% O8 ]3 X& ~7. i& l) U4 f6 k( H4 R5 t9 a
    8& A2 \. w, l9 g5 ?5 z" z
    9
    / ?4 Y; m$ G' ^% i/ m/ h108 Q# j( _, I4 W: p& q8 V7 t
    11' e, p! k5 {  ?$ c$ y4 J: P: ~
    12% _/ U3 l0 Y. Y0 q# `5 C& H
    13
    / V# A+ [5 ]7 k14* K5 p8 C% Y2 Q1 A( w2 Z! d8 ^
    151 W. x0 Z" i0 T! W* K' k
    16
    % L0 D' P' B; K% z& c$ p170 n% H- g: p$ |  P. A
    18
    ( P* Z( @! K% O  g# r- X' x192 J) M! q$ H, I! W; s
    204 |, Q+ A' G& k
      在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
    * O8 Z$ r8 F% q3 D" A0 q8 o, r: Y0 }
    ! f) t2 ]. r# l2 [; }" R% n' gs = pd.Series([7, 155, 303000]).astype('string')
    1 s+ y& f3 [7 @* [8 X7 G9 P; A  i% X. \# {; e& y5 t
    s.str.pad(6,'left','0'): T' ~- n  h9 }+ S' B4 C4 `! P
    Out[111]: % D& r) n0 b. @* X
    0    000007
    $ S: A2 p. M4 ~  O" L1    000155/ I& H5 {! A1 w% u$ y7 c
    2    303000+ c- N9 V& R9 z  a( A% N
    dtype: string3 }$ S# r7 F( c9 E: g  g
    % F( E$ F. ~1 I: e# d* e) C
    s.str.rjust(6,'0')
    7 E/ D* Y. @/ X, V; j1 k6 `7 WOut[112]: 3 S7 n8 Q# \4 F# V" E3 \; G; F
    0    000007! o; ]7 k+ s: g0 s2 @! n% O
    1    000155
    & b4 I) D: G5 {3 x! N2    3030000 t) Y  S, `; K- o* o1 f5 U. y
    dtype: string
    & a- `4 i+ i0 b; y3 m$ ~. N; H8 ~3 ?
    5 O8 y: O& w8 M( L' G) ps.str.zfill(6)$ k8 |4 l& E/ m. M. M0 }
    Out[113]:
    , U; e4 D! i+ F0    000007
    & M5 q+ y# f' w0 ]8 t9 P1    000155* H; ~; O  |' K  R* |+ [. g9 K& M9 R
    2    303000
    0 g" a/ b/ T# c# B) Q/ I  D% L, Ydtype: string- M' S6 I% d3 k/ [
    0 h8 E' E) t3 Q7 X, z
    1
    8 Z! Y# p( e: g8 d6 N% n2
    $ Y, {% v9 S4 h* R9 C$ E/ m34 D" I: \+ x. V1 ]# C. V. d
    4( G0 }: q, ?% b
    5: d9 g6 T. v6 `7 c& I4 t6 A+ x; O
    69 {$ V3 t, g1 u+ ~& u4 y) Q( Y( Z
    7) j7 y+ T0 M7 v* L( w) Z
    8* F2 ~/ @& S* d2 @' ?
    9) e3 F! e9 d/ W
    10
    " N, _. D8 p4 C11
    4 p2 ^3 w6 S2 O* i4 b12
    0 M! ~: W" R6 @134 ~. f" K- ]8 H+ N4 K
    14
    ! Z& [7 a5 q3 }( V9 ?) y/ }: t$ ~157 V0 r  E) B: [7 {' f2 j/ J) }
    16; t9 L' {6 b2 @1 q+ K6 t
    17% R" ]3 t9 W- T5 E  G2 O) D
    18
    5 l( e3 r9 G; i19* l: w# Z  f7 W% u% i
    20
    / {& `' D4 M1 a* Z$ ]21* x; X! S  l; ^% s: H- y0 N
    224 q7 Y) C) B, ^+ ]
    8.5 练习9 b# t: n" l1 m; R
    Ex1:房屋信息数据集# y! r5 C% b2 z: A4 C1 W- f  ^
    现有一份房屋信息数据集如下:+ c8 E. D& @5 Q2 {# c/ b
    + Z# ]0 d/ `/ x% M7 d* X
    df = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])4 G2 {: q7 u; ?( L
    df.head(3)2 P9 ^( o( ], Y5 D/ E3 i
    Out[115]: ; I5 a/ v3 A# I7 a- j9 d9 [
          floor    year    area price
    2 _& _: e& d5 O; }9 s7 K: ]0   高层(共6层)  1986年建  58.23㎡  155万
    # r6 Y7 O8 T9 `  k6 g& n2 j8 x1  中层(共20层)  2020年建     88㎡  155万- z+ J" ?7 z2 ^# e" K
    2  低层(共28层)  2010年建  89.33㎡  365万* W1 G4 m' v; H0 `' I1 a
    1
    4 A/ i, a1 {# c- W1 C2
      Q5 Q# i* }, h% C& w3* @0 G. k; s* g4 }6 s
    4
    $ h' M: T8 q! R  K$ M' v( E8 X! l5  S* C% F# `/ m9 m
    6% A6 b( k) g, \, }, w
    7
      v! Y8 r5 O) x; b5 R$ i. P将year列改为整数年份存储。
    8 B- R* t0 L( o# ?7 u. {将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。: C8 p! t: R  D' B4 U: b
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
    7 k1 Q: n# p# s将year列改为整数年份存储。
    5 z- {/ x; N( o"""& [4 v% d5 h5 O3 E& y
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
    * L' v* k: T2 Y0 t( ]" ]% g注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,( f; m+ S! ]8 H& D( E. J
    转成int后,序列还有缺失值所以,还是变成了object。! b6 J. i% T7 n( K7 k9 u
    而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。# w: Q' l! p* [( F$ K4 O! S9 q
    """8 Y( ?- L# S+ d$ O# @; c# v& K9 b* @
    df = df.convert_dtypes()
    5 G3 s( Y& `/ E6 O( T% i1 N) Y3 Tdf['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
    $ A/ {1 e, l" \df.loc[df.year.notna()]['year'].head(), }( H6 C5 s2 S3 R6 B/ }3 R3 y5 v

    5 _9 H* I6 z% z- b4 z7 q+ X6 ^0 b: K0        1986
    : i( h* l4 z- ~7 `4 t1        2020
      n( O/ Z$ x4 C! c6 _2 F* _/ `: {2        2010
    ) p4 d: e) }4 W( W" V7 A9 R3        2014
    ) s- |% @# K1 q6 R: L4        2015) R0 X" Q1 A# B) ~$ u
    Name: year, Length: 12850, dtype: Int64) {+ Q5 L4 p+ U4 T7 H
    # P2 n4 T  W0 G6 l- Q% c; b
    1
    7 ~. \/ d" J$ Y, P2 f& c- E( H2
    5 x2 |# o+ c5 [. G! F! U, S( }# f3 }) S3
    # I/ E$ k9 Z  m$ _+ h0 v' h4) L5 R( d* P3 u9 Y  n% J
    5
    4 Z0 H/ N7 ^3 m- j4 r0 |6- P! S5 [+ E, q- }4 k% t& t4 W0 L
    7
    2 _4 A; k  ?1 I" Y+ q$ p. F8
    " o, B! n" C1 O4 Y* M/ R9
    $ A/ |* n- b& `; b& U4 D, o; Y10+ d/ s) ~9 D) B- u- L9 a
    11& u5 `$ @2 V/ @) o
    12- w( F# M+ L1 v# T
    13* J+ ?. I$ ^  B" u" M1 k
    14
    & P( U8 C7 B+ n8 u' Z15' H0 j3 m7 p3 r. x: o7 a) y+ K
    16) A4 q+ R" K* d- N/ K
    参考答案:
    2 X2 [( p( U8 D/ {, |1 R4 H. W6 }1 C; k* E* X5 g. k$ \: x
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么
    : i/ Q& r/ @+ k+ D
    3 c; d" l, {( h# C/ kdf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    ! }/ X$ t. z) L4 e% N7 I6 ddf.loc[df.year.notna()]['year']
    ' n# M" Q/ J/ d! m# a4 y- y$ N' S1
    3 K" @( K+ K3 `2
    , Q  P2 a$ Z, G- G# k' \5 J将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    + B( M4 B* r: E) e" jpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'
    9 g) |9 c4 H0 _5 Mdf2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
    7 |2 n8 Y" r4 r( _. I* q( Cdf=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型# P( |4 y# R9 @4 T" n
    df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
    % }" t) j2 K% N* e6 bdf=df[['Level','Highest','year','area','price']]" W5 \2 c2 A" N7 L* a6 }
    df.head()' I- V* _( V; ^. i0 G& y

    4 x4 n6 O. ?" m) C. u2 f, D$ }   Level  Highest        year        area        price
    3 `4 e/ r/ h* _/ i  ]0        高层                6                1986        58.23㎡        155万8 A2 @) {' K! B& F$ q4 _
    1        中层                20                2020        88㎡        155万+ U0 [  I2 ^+ _5 o* }* D
    2        低层                28                2010        89.33㎡        365万
    - W* a+ s) k8 C/ K2 M4 N3        低层                20                2014        82㎡        308万
    : W  X2 [: N+ F9 h2 O( H4        高层                1                2015        98㎡        117万# J  Q! m/ I+ w1 R% H2 `
    1
    ' D) G3 |) m0 l$ k$ K( d" i6 I2# J- E5 N* s" D* `( n; ?% s
    3
    : i$ v* R3 q& |4, H, z3 d$ i: ~+ f% `
    5
    ) _; v' o6 k/ m6
    : b: W5 x7 W% \% ]3 Z: i! |7' K) ]1 X! s1 w; Y+ ], f
    8
    " }* H% ^' O% g& T% T. l4 E95 Q5 R4 d# `4 q/ Z
    100 E3 I) q% U* `' ]: R
    11
      n- q8 m8 p, z' s12
    7 e9 Q" w' a+ l) i# N; t133 }/ d% g; G+ `5 b# Q0 g4 A
    # 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了8 x' N- m$ z7 I  _; b) b1 N/ r* m
    pat = '(\w层)(共(\d+)层)', d; e. `- {$ n, ?; X, x0 h
    new_cols = df.floor.str.extract(pat).rename(# Q6 B, c, d  D8 \8 N, t9 G: y
                        columns={0:'Level', 1:'Highest'})7 W2 {" F$ @; s+ H7 i
    ! C) x2 j+ P6 \8 y
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)! ]1 S' B6 K1 d; Q+ ], v
    df.head(3)* `. |6 d( n( I4 p0 T, b
    ; U3 F0 k5 \/ Z0 Q7 y5 i
    Out[163]:
    3 w, e+ P9 O6 S4 N4 z/ e   year    area price    Level Highest
    1 \7 z6 R8 p0 o; |( @  D6 N0  1986  58.23㎡  155万    高层       62 Q$ J% D6 O7 w0 t4 X. B! T1 r
    1  2020     88㎡  155万    中层      200 e1 }, o+ J" \: M$ r- z
    2  2010  89.33㎡  365万    低层      28
      E* l" g8 X2 o3 v$ A! H1
    ) I: \# n; t! n' |2 b21 `: m) z$ H1 L* g
    3
    ! e* ]1 w8 g9 C" E4
    3 T: Y! N- z- H: e. ?5" f, o9 T3 V- I0 I* A+ o) c
    6. t* `# ~' U; B* g8 R
    7
    ; f- x/ D1 J- s  j+ N8
    . s2 |7 N6 \" G% c& D+ M& t/ ?  o, R  b92 l- T2 B/ ^0 \" y, G; l: S
    10
    5 r2 f5 I" g& ?11
    ' u. H! G0 `" G8 Q4 o. }12! ?4 v  ~5 a( [: Z3 {
    136 l5 H4 B$ `$ {1 x; ]
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。" @9 A$ z. b0 f" l0 ?" t& t1 p
    """$ {5 \8 d, y2 ?+ y7 h8 f
    str.findall返回的结果都是列表,只能用apply取值去掉列表形式! z0 \4 Q, ?2 y0 i" e. U
    参考答案用pd.to_numeric(df.area.str[:-1])更简洁
    9 h- I/ }0 H  H& N3 Z由于area和price都没有缺失值,所以可以直接转类型
    5 H& w8 Y5 H2 }) W$ M/ C( s& B""") I; d  c+ B2 L
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
      R# k  I9 f* \df['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')& B$ K) n7 R9 J# [. E. I0 p
    df.eval('avg_price=10000*new_price/new_area',inplace=True)% p; R9 U) _7 d9 }' x( P. }
    # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
    + d+ {8 b3 q9 k0 a( Y* u# 最后数字+元/平米写法更简单1 X0 c" M* v' x8 r/ F# {( N, K3 y/ D
    df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'* K' `$ }  T& E3 f7 [; }
    del df['new_area'],df['new_price']
    ! Y0 g$ @2 ], K2 H6 w$ rdf.head()
    " o. G; S2 e8 J8 B; L- K# @
    + f5 U6 M1 r: _- [7 S3 x   Level        Highest        year        area        price        avg_price
    . P' v; d6 w9 V8 ~$ b& R0        高层                        6        1986        58.23㎡        155万        26618元/平米% P' @3 j: B$ I0 b2 \! h1 f3 l0 J1 z
    1        中层                        20        2020        88㎡        155万        17613元/平米( \' c- n2 w7 H/ D/ O
    2        低层                        28        2010        89.33㎡        365万        40859元/平米
    + l1 s# m. u# x3 ]  G$ S" E/ J% @3        低层                        20        2014        82㎡        308万        37560元/平米
    $ s  K  C+ W" Y' }2 j' z4        高层                        1        2015        98㎡        117万        11938元/平米
    2 v) x0 D5 J/ m9 h! g! G: L. a, V
    . M6 V6 S- L" v) k8 i& z8 w# v1
    ( K4 O0 M5 D' d25 o0 b6 ]$ \; C1 d& t
    3$ l7 Y# J" \. k( B
    4
    9 \' g9 S1 x! W* j! x5
    6 k0 ?/ `) M' [4 z6
    # G- a/ x7 J& o$ D, Q7
    & s+ ~2 f- g# H' M; d! v) J83 I# f& T0 j3 A$ ]
    9" a# _5 a( F% L" e( `8 t. Y9 M
    10
    , b& Z3 V+ w  s4 E+ f& N% e- @4 v11
    + h8 j8 E; }$ I2 \# a: b1 s12
      w# O1 e; d0 e$ j13
    4 ?" p0 @; g0 ]9 |142 Z  m0 S! a! E: D
    15
    3 ]* j! [5 ^$ X( H$ J16
    + c: }: f0 B2 |) l- q, E8 ^176 L; V/ s! b( g- m
    18
    ! l# n1 O" L$ m) Q8 X* o190 [: Z; Q2 B$ |1 D" u
    20
      V1 J; Q5 E! X7 {1 }& M# 参考答案+ B; {3 }( n: R; ], Z; e& T! v
    s_area = pd.to_numeric(df.area.str[:-1])
    % [) R/ ^& B% j: Vs_price = pd.to_numeric(df.price.str[:-1])4 r; S5 @/ U) K3 p' j
    df['avg_price'] = ((s_price/s_area)*10000).astype(" V; P  \; z$ V; Q! L; H
                        'int').astype('string') + '元/平米'
    5 Z7 b. k7 w2 M4 V% K- o  ^
    : J' M4 S# b# P7 Odf.head(3)' ~4 ^& ~% z7 K
    Out[167]:
    6 E9 X1 ^+ c9 _* g: r   year    area   price   Level Highest  avg_price
    4 ^' f: \: H8 ^* F% D5 B0  1986  58.23㎡  155万    高层     6          26618元/平米# |# g" }4 z+ q
    1  2020     88㎡  155万    中层     20          17613元/平米! p0 K  U% Q# k0 @1 e
    2  2010  89.33㎡  365万    低层     28          40859元/平米
    6 U) N# |7 z  s! c3 C; G  R17 r' [9 u' i. f
    2
    8 r9 F7 J4 y4 b3
    / ?, J; N/ k( ^4 D42 x% ~& Y/ W$ o" F$ @. _& m5 G
    5
    1 b" Z. B+ g4 O% R. c6
    + e! G7 N5 H- R! Z- Y8 S' Z  B3 g7
    4 A! P3 e2 y4 l) E; Z8
    " y2 G* D- n9 H8 f- i9
    - }# [3 ?: F5 Y/ n* ]105 }, h$ M- `/ u6 ^
    11+ M5 L$ ^. s$ @8 ^; r; r. T( c4 K/ O
    12
    3 A, R( O, ~* J' \  L5 m# ?Ex2:《权力的游戏》剧本数据集
    7 G  S" r- e' g/ H& V1 w: T现有一份权力的游戏剧本数据集如下:7 n9 l! M7 J% y' ]# O9 U

    1 V' O2 }# q# o3 u8 y1 |df = pd.read_csv('../data/script.csv')8 A8 i7 k3 \+ P2 {1 u; D
    df.head(3)3 t3 R$ ?1 L* \! M: i& O& d

    ) S, ]2 v- v( d8 C5 \( G0 q+ w5 IOut[115]: 3 ?3 N7 J: z4 U" e
    Out[117]: - w/ r- O, d3 C; E
      Release Date    Season   Episode      Episode Title          Name                                           Sentence- t8 O" C, [9 T6 ?, \9 i% w
    0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    * h% r1 ~+ q+ E( Q# `. J1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...
    , Y8 C$ Y  s7 H3 b( o2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce 5 K- B9 e) h  @* G/ e
    1
    ( ]- R( k6 y+ R* V  `8 D2
    6 x8 o+ V% x5 E2 q3) Q6 O3 m! L/ o) }. _- {
    47 _! e; W/ r  n8 y$ n
    5
    ( b- m% n& B* p8 D6 J" L6 R9 s6
    ! a' p- k& @* D  i# j2 I! f7
    - |$ v5 c+ F7 @& u1 P0 Q/ Y3 J8
    " L. n. n0 J; U9
    * q9 m* D7 A/ j* g) o3 F! w- q; b计算每一个Episode的台词条数。, S2 E& @7 {3 {& l, J
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    . B& b6 @) t, _* [! a若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    $ J1 X% {1 B# G5 Y: \$ h1 F计算每一个Episode的台词条数。  L' z) }/ q2 G8 @3 ]
    df.columns =df.columns.str.strip() #  列名中有空格
    % w4 A' ?/ J) ]df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()  e3 v- L! y, K
    2 B8 ~/ S9 Y7 |. |: Z4 I- A$ Y
    season    Episode  ( i2 ~% w' g2 B
    Season 7  Episode 5    505
    ' \/ W0 K! Y" }& W$ n/ B. PSeason 3  Episode 2    480
    . j- [( o/ f1 ?- ^* `; Z9 dSeason 4  Episode 1    475
    2 ^1 z. V2 w" }( [1 d  jSeason 3  Episode 5    440
    ( D% p9 @) `& f1 O: [% n, GSeason 2  Episode 2    432
    $ g1 R" W/ M& m* m. r' H  [# u1
    , I6 V) y, V+ k+ N3 F4 h2
    ; F7 J5 y" G7 j! g6 F3( _0 q5 l1 l5 t; [" T
    40 I2 u/ N! [/ M% W  o
    5
    ! H7 B7 j- y2 Y) t% x5 d7 h- v6
    & i* O- [9 \4 a7" t7 E4 N6 W; n4 F
    8, a3 U, y2 b5 X0 c
    9
    6 k) p/ T7 u% G9 Q6 T以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。* i6 e2 n" M0 l7 p, a
    # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数  ~+ Q# v( x% J. Q# p4 x: A7 x
    df['len_words']=df['Sentence'].str.count(r' ')+1- e5 {. k+ I- B5 s
    df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()
    : }7 V* Y  ?3 M) s: @
    # ~8 M# ?' e/ }; m9 l8 mName
    ) v  N' Y4 L) B) N4 n0 _3 k6 P& Gmale singer          109.000000$ K3 P( v4 ^$ r( V1 ]/ `
    slave owner           77.000000
    ) U  o4 I' e8 C* Rmanderly              62.000000+ p8 v# v5 E! ^; o; [
    lollys stokeworth     62.000000
    # y+ f8 ~2 _4 q2 [8 g# }4 z, bdothraki matron       56.666667
    & C+ E9 m8 D8 W2 y- jName: len_words, dtype: float64- X' D, @: L) T/ e- n
    1
    2 T0 i6 q& y0 g+ k. L2
    % e6 m- R, L9 Z& Y' ]3/ O% O$ d3 a9 z: \2 R: P5 @0 z$ E
    4
    $ Z  Z- t' O2 f( U/ I5! I. ^; w3 q' ^' D
    6% @8 |& q) X& p" ~1 y0 k
    7
    + b& B6 H: d2 q8/ {- H  o; P6 a* c
    9* `  X: H% O! N: V3 I' f6 Z  z; {
    104 F: C. A" Z1 s
    11! A. g% V8 u2 J' C  ?
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
    & E6 k% [. Q. @7 q: c; bdf['Sentence'].str.count(r'\?') #  计算每人提问数/ F, c" v/ C' ], U1 M; W
    ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
    2 ^' ?2 z& p* A5 r* a$ T: Jdel ls[23911] # 末行删去( r. r8 C: l9 [2 X; D0 ~
    df['len_questions']=ls& \' |! H+ k; n2 L. t7 y* n
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head(); M/ q) v8 o$ y0 O5 O

    * Y8 U- i4 b1 y. v. n: p* H( \Name) |  q, y! ?5 C; D( @" h% E
    tyrion lannister    527
    3 S% M4 X0 J" l/ ?0 s5 Ujon snow            374
    6 b  ^7 V+ }- t4 tjaime lannister     2830 L/ Z8 [2 z0 b6 K0 z
    arya stark          265) n' D- n3 @; `' ^7 r7 a) j* W
    cersei lannister    246
    ; k& o' q( p7 h' |' M! zName: len_questions, dtype: int64& @9 Q9 R: Y2 `: o
    1 ?# k( y- q( P$ v' g3 V
    # 参考答案
    : ?( y3 T9 V- M* l) ?5 ns = pd.Series(df.Sentence.values, index=df.Name.shift(-1))
    8 r* v, L/ l' [% X( t0 cs.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
    & O7 L4 q% {) Z, j" X
      }5 Z, R7 d& }6 g1
    0 ^6 C8 |) ]$ z$ E2
    2 N& G7 m( R2 A, \* k& W3
    : P; r5 e- j$ x" i4
    & J* r( s; |4 }  U" P54 h- _1 q  L1 a" o; e# H9 Q
    6
    * M3 W4 v  }+ T7
    2 A* R2 r+ a- _+ w7 ]7 d+ `6 x" L8
    9 E5 @  g1 y1 A' l# ]9! L8 W5 p# r) \2 I& }6 M1 u
    10
    8 K7 W* f& _, V! v: W, g11
    3 t1 U% w* k2 C! o& v6 T0 p12
    . E" V+ r) d' O! l13
    . V) ]! Q( Z8 v. N0 {14
    # T: r$ I8 U: T0 e- |15
    7 `# }/ ?# l; m* m# J5 u2 [5 X9 a16
    $ l5 B, H. O3 z4 o: m8 }0 J17
    0 R+ P- u9 E1 o; D4 U; b第九章 分类数据
    & o& d  a) A1 F% z4 vimport numpy as np
    % o+ ?& Y8 K  w9 T( Y6 kimport pandas as pd
    0 Z8 f0 q: w# V10 J2 ?; R% E- d, Y- D* H- e
    23 u9 _2 ?7 M' I) n0 o/ {
    9.1 cat对象
    ' a& i3 u/ x) X( `* X0 d; s( f# S9.1.1 cat对象的属性% U* s- N7 Y% r2 l
      在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。5 x$ @7 B  z, }, \8 M0 G# [
      l/ O! Y5 i& ~- q
    df = pd.read_csv('data/learn_pandas.csv',
    & c+ t1 x- x/ X+ z     usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight']). @* o8 N' A, ?( E
    s = df.Grade.astype('category')8 e0 w& H0 x6 r7 ^! A
    $ n3 a1 U  R0 T
    s.head()
    . Z3 t  C, u- M5 g3 oOut[5]:
    6 H! Q2 A& _6 D( F' k2 C0     Freshman" ]9 A" D# @" Y$ D) l, e0 G
    1     Freshman, g$ |- ^+ F- V: d, I. Y7 @5 K
    2       Senior2 U# u4 c4 K/ a" @- D% Z
    3    Sophomore
    * h( X" f1 t3 F) ^5 T4    Sophomore
    ( P  o* I' K& s" X+ }) ^. H3 RName: Grade, dtype: category; y6 k6 S8 J8 K+ d4 O; U
    Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
    7 a$ y  J3 B8 O  x) R# p  ?8 z# v# j. `1
    & N0 T: O6 F0 L0 o% x7 c: M2- `  I& j4 |8 Q1 b, V0 x, c
    3' s& a. W+ l5 m0 n( h! x
    4( G6 ?0 ~' m% W0 F6 y- _
    5
      {% a5 ~7 O# o( O. m6
    ( v; k& t* l: ]3 J7
    7 k1 N/ a# n* z) T2 Q8 u83 y- d2 [! A# @% ?: M9 ?5 h
    9; A& s* m' B3 y& {! K0 Y
    10; L' E$ H; u* v& o
    11. P- {3 l$ Q  w1 x
    12& k- n9 D, _4 [- H1 K( t
    133 R. B2 X8 k" s% `
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。
    2 I# Y1 X# ~0 f) j) d$ ~
    * ?5 [4 n+ k( c- N- P! E! \s.cat
    4 a# K7 }2 T+ i. ^' [  ?Out[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
    : D5 ^1 ]: j8 K; e( j- g1) I' q4 W5 p, b$ Q. J! ~
    2# y" {% N" p/ D" X
    cat的属性:& V( B# y0 H9 Z3 q9 L7 Y% B
    9 L9 [$ A2 V1 U8 `, V
    cat.categories:查看类别的本身,它以Index类型存储1 R7 [+ k, T" X& z
    cat.ordered:类别是否有序! L, z/ L. Y7 m- ^* `/ @9 E2 H6 T
    cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
    9 F& a) Q+ c4 ms.cat.categories8 r! }8 M7 @$ P4 N) n, e) i( s( ^* V
    Out[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')* `8 b  s( {# ?# t2 C) _# `' G( W- t

    * J, O, }2 `; `8 Ds.cat.ordered
    # P9 f8 l! v: p6 `4 Q8 w. x9 lOut[8]: False- [5 U! A* ~  @9 @- s

    : f, S8 ^& a2 c, R- k' m* q9 w! rs.cat.codes.head()
    : Y+ N. W/ V5 i5 X! POut[9]: - `2 ~2 h- M0 l- R: V
    0    0
    / k5 M1 ^4 x) y5 Z) A1    07 [4 q3 \6 p! U# v3 K6 w1 M9 A( d; e
    2    2
    2 u5 i& R' z0 s7 Q3    34 P$ I3 G3 v% G
    4    3
    ; i6 ]( @/ |; w, ^( i" E* K- H0 m4 ?dtype: int86 P! D* M3 D2 s7 m- v/ f, v
    1
    6 V. m1 Y+ H/ G24 Q/ }/ c5 m5 X; O# c& D
    31 }' I; G/ q2 C% j& h, j+ X+ X6 m
    4( N& S  @9 {; r1 b8 [
    5
    & y: h# R) Y. {: y6
    0 i( P# I- Z" m2 O2 Y+ x: w$ a( ^0 n% {7
    " P; i' p5 R; e! `# L1 u83 F& m! a1 s. r7 |& v/ K# ~6 l
    9- ?$ |2 d/ \/ A; F) }# X0 r: |
    10
      a9 b3 V, p, j! {7 ]11/ ~& j2 @- l) E. r
    12
    0 F/ M1 m7 ~6 `130 T- ?( l; V! T& v6 \. R
    14/ A  `; ]; m3 @+ x6 f, N$ }
    9.1.2 类别的增加、删除和修改2 u, Q$ g4 l( H! D' D
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?
    6 q( k- N$ o. U7 D* o+ M6 h! d. N3 s, |7 ^1 T+ `  }" B* B
    【NOTE】类别不得直接修改
    ; h0 p/ L. ], x& b" U9 g; C7 x在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。( h4 B5 {) T/ l6 |1 C
    ' F# f& V4 f; g+ h, {3 ]
    add_categories:增加类别# P+ _. Y( L  D( h
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别
    3 r; ]8 }" K0 `, a! x& zs.cat.categories* G" E( S7 e. d0 v) Z$ f2 U% a# _
    . ^" K: X) m6 d' i; R( m
    Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    . J/ S' ?2 |0 F# \1+ n" t, u. J0 E9 N8 M( d6 e7 G0 E
    2' H% ^/ R4 c, c' `
    3
    % a7 i# Z3 Q" n3 ^3 V4 x) m  d" @4! |8 w' F7 Q9 j" P. L9 s4 D
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
    + E+ d+ S) l; x" r. y' K& ts = s.cat.remove_categories('Freshman')4 g+ t* H) c, j7 K
    4 n3 h! e. K5 w' [# I- P
    s.cat.categories8 e, ^. N  a# E" {6 O% V
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    7 _! _* K8 B% D
    % Z/ V" f, k2 |" w6 J# Bs.head()
    + o; g1 j$ o5 I/ `: `) aOut[14]:
    # D: r( t4 n$ U# E1 @0 z0          NaN+ P' s0 T% N2 S. y2 L
    1          NaN( \! J  i' a: Z2 @: m* j' x
    2       Senior
    ) T1 I( W5 {' Y: U. A, r+ Y- d3    Sophomore, ~0 {" B1 E" ]9 V2 D1 A; [* M
    4    Sophomore
    5 @; E$ a- S) x4 A- L/ aName: Grade, dtype: category
    3 g  Y  h* j4 j: w, VCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    7 i% F7 a" m2 ~: J1( ^+ P" `+ U" `( e+ k6 K+ m/ i+ z
    2/ B7 R" V0 f3 Z" O+ |8 H+ p
    3. |, S1 l3 j+ D0 n
    4
      D$ s/ S* X' X+ |$ X' I5
    4 h6 \8 f' g& ~4 D* Y" j  d; p6
    7 I- _9 M- K6 \( n9 {. N7
    ! U& Q: I0 W! [( e' q2 Z+ z9 V5 G83 E" a8 G# @" {) p; n
    9
    4 x( m) b3 e/ A1 q+ ~10
    3 s+ Z9 V7 K; |9 o: v2 {8 \11
    $ l" D; N& \' O( P: \12
    9 G( r% _$ k' J/ @1 K5 v4 d) B, r137 G( \- h' k/ C/ y! t4 t  C
    14# w1 l& ~6 B0 J) q* G0 }
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
    / G$ W1 e7 T: N) Ps = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士
    * A3 Z5 D, B, J8 P* b0 qs.cat.categories7 l! O1 z: \" t+ |8 T
    Out[16]: Index(['Sophomore', 'PhD'], dtype='object')
    , W* Y0 n$ w5 e3 }8 @5 @2 d6 G( w
    ) j' q8 Z& W9 x0 K$ ^3 @3 os.head()
    1 ]4 l- q  l* |Out[17]: 7 Q' Y  u7 ?1 S- h& B: j7 _
    0          NaN
    $ U. W7 _  v( C9 O% W6 _8 b, W1          NaN
    # v* W7 `5 U2 O# _+ w$ C  R' {2          NaN
    & W8 X7 M9 A; O% F! p) |  C3    Sophomore6 m# M2 l& E3 h' n5 K* e1 P8 y' z
    4    Sophomore2 ?0 O( I* Y& m% T
    Name: Grade, dtype: category9 g# }$ ~/ H8 ?0 Y1 b2 \
    Categories (2, object): ['Sophomore', 'PhD']
    ' {+ n0 H4 G( N/ r$ Z, {2 t1
    + O" k4 |$ L2 [8 ~! g1 Q2 Y2
    % R7 D! U( \" R8 X0 _( u3
    6 @3 v& ?& c2 G3 e4
    ! Q! k  ?5 D' q5  d* r, L7 b, n/ r$ \  T  c
    6+ K6 o' s. e1 f, l6 `1 n
    7& a2 \6 ~7 V% s4 q" k7 W% I
    8
    # v  @1 K- R& k0 `, ~9
    + p0 [  }3 `6 i' B  N9 B2 w10. q# B- ~& t6 ~4 S1 C# u
    11
    2 \8 H1 ^; \( O12& P" x9 b9 Y' ^  q2 {" F! ?6 p8 e
    13
    : ^$ l9 F' ~3 r  w) {1 Jremove_unused_categories:删除未出现在序列中的类别2 I! F) r7 K1 r
    s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    : D6 L8 {" G+ @/ H+ n& w4 Y- N) V) is.cat.categories
    / s8 |. Z# ~% b
    5 G# V+ B# k' M9 ]& t9 k% ]' O; JIndex(['Sophomore'], dtype='object')5 k/ @" P" L2 p7 J8 _% {
    1
    + ?* K2 L, t2 q! X$ x5 `/ v2
    * H. s( N" ~/ u3 t3 y: N0 y* H$ A0 L3
    $ \, [/ _: H0 P% |8 E; C: G1 X4, I* a3 R$ D( d2 s$ h" u% s
    rename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
    8 F0 c5 s: @" j$ W% ds = s.cat.rename_categories({'Sophomore':'本科二年级学生'})4 r" Q3 P1 H. s9 J3 h9 K; F; K
    s.head()
    ( a0 a9 `: l2 m+ {* n. g  b7 Q- f: C' @% c5 f2 }" v9 F- l- l7 @
    0        NaN
    + W. V% L8 I4 y; e2 D7 K1        NaN' R  b; f% W  K
    2        NaN
    4 J( v& v3 a3 p! q7 A+ \6 Y9 I5 W3    本科二年级学生! g: {$ w: d+ ]6 J, w4 k, E' ~
    4    本科二年级学生
    1 G9 K3 j+ l# S  s% [4 |' L: T9 ^% FName: Grade, dtype: category
    7 a& m& h/ Z1 E- C. m8 KCategories (1, object): ['本科二年级学生']8 n4 N: }) K  S  A+ }6 D
    1; O9 `) N+ ?7 \' C6 K$ I9 D
    2
    ! b" s* ~: Y0 R( R4 w4 J3
    ' h3 n" C: H4 P" t4, r" B3 O% z1 S2 T- F- ]
    51 z$ j, Z$ k; K2 [" Z- L6 W* N
    6
    ) w. I$ g/ d7 r2 s; s! B/ Q2 d  j$ |7
    8 E& ]) d( Y, \, o8. [" _: B3 V+ B1 ^# C6 ]- H/ V. c
    9. S: ?# n, j% B3 x
    10- ~, e! l, |% v, N, Q) D4 b
    9.2 有序分类
    " M/ \: V) r% \9.2.1 序的建立, M4 a+ m8 L7 W$ g: I1 w
      有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:
    5 u- A3 X. Y9 X7 ^' ~' B- [
    $ B; d! ^2 A* `, o/ C: fs = df.Grade.astype('category')2 c" e  \! r8 b7 h) o  W: D" A3 m
    s = s.cat.reorder_categories(['Freshman', 'Sophomore',
    % j+ `8 ?& p! t# P$ y                              'Junior', 'Senior'],ordered=True), i5 Z$ Q# h. c5 Q) c1 T7 l
    s.head()
    0 m* f5 \! O5 [8 S' `8 j; H  aOut[24]:
    & y  t  |: {  e0     Freshman6 y  k3 I0 @+ O- Z( _; X
    1     Freshman
    ( w# l# K7 T4 }! ~& M$ I2       Senior
    % T+ b1 }( J* V) [3    Sophomore
    0 O# e8 h1 E# l6 R$ M1 ^4    Sophomore
    , i5 W2 f$ X1 d$ \% DName: Grade, dtype: category/ H+ C! x0 N. r- g" }5 k
    Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']- |4 M6 O+ m" m2 T3 s1 z, `

    4 Q4 |7 `0 q+ [/ R0 ]. Z4 v0 Ts.cat.as_unordered().head()
    ( l" q' S+ V3 X6 K5 v. gOut[25]:
    1 _6 j- V  o' V- u" u0     Freshman
      ]% R$ D7 |2 Y' }6 A; v$ j1     Freshman
    2 O! q9 n* b  ]2       Senior  U' J, c! b% a3 M, \$ D
    3    Sophomore
    & |1 Z0 o7 m9 I  l% y; A4    Sophomore9 t4 D% j7 G# k" l0 c
    Name: Grade, dtype: category
    - K) [; k% A  WCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']0 j1 v+ D1 p2 Z2 N/ b5 {

    + c2 `  V1 ~- s8 p% x; g5 G/ c1
    7 p: F) a9 ]$ ~+ g2
    0 ?& r( c0 G' \3 D: r3  v# w; |8 s: b( ~6 Z1 z- f% d
    4# i  I" O# Q% _' R; z2 [: g) ]
    5
    7 _5 s5 ?; D! o68 n  f0 e7 M6 v' ~! z) l
    75 `  N' P; E. f4 Q( T+ |
    8
    & A( |! ^$ {, e: v9
    . G9 Q* g1 p0 D4 f! |$ @10
    $ t) b4 _1 ~. ]0 |11
    7 H4 e$ C, ?# \12* A+ [, s9 K0 P: J8 T, R7 i7 Y8 G8 j! ?
    138 N" c9 g9 B4 Y4 A& A% x; f
    14
    . @4 ]5 m" p/ s3 ]15
    ! u. A# j  Q8 p( C+ R8 c, ^16
    / ^( I1 Q2 [4 O0 h6 L17; X: {/ p, A1 q8 A1 z% C
    18
    4 b& k7 @$ Q3 e2 {8 p! x3 r19
    7 `# i7 \1 }5 n' W209 Z! F# W9 e& [! L
    210 W  g  W4 ]: q" M$ e* m
    226 D( R$ u: t. ?. r# ]" J* S
      如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。3 u& D6 ?3 H  O) I( d

    ! L8 x0 i. W1 m9.2.2 排序和比较$ K& k9 T3 L8 p. i9 F1 U- j
    在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。
    ) B6 w, f' q, R9 C: y6 n' W' M0 ?  h) i4 p" ]
      分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:2 e5 q5 H- [; f; |0 J0 ?

    ( m2 {# ^& N) J! T4 u" E9 i& v5 m& ?* \df.Grade = df.Grade.astype('category')/ Q  d' v* y8 @* S% @
    df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)4 z' ]; a+ d$ j5 {7 n0 t* Q6 }
    df.sort_values('Grade').head() # 值排序
    0 a9 ?1 o% ^8 H$ |' L& gOut[28]: & Y/ m* j$ s+ q) f% V+ F/ u7 J
            Grade           Name  Gender  Height  Weight: G' |9 L( n) q
    0    Freshman   Gaopeng Yang  Female   158.9    46.0( a( y9 Y) r  e0 `  n2 l+ Y/ V: ^
    105  Freshman      Qiang Shi  Female   164.5    52.0
    * t/ \3 ~) @" Y4 w; O96   Freshman  Changmei Feng  Female   163.8    56.0, L4 n2 n4 K1 N5 U( K4 G0 E
    88   Freshman   Xiaopeng Han  Female   164.1    53.0
    - q3 K1 |5 G* J81   Freshman    Yanli Zhang  Female   165.1    52.0
    # D8 g$ [9 I8 T. C; Z+ O* W
    : K1 t1 M/ G6 @6 O% {3 tdf.set_index('Grade').sort_index().head() # 索引排序
    % s& ]; t! i8 v5 m) E# Q; dOut[29]: 9 r8 o% c: H" G3 L( c
                       Name  Gender  Height  Weight
    9 s' ?5 j2 ^5 F' N! zGrade                                          
    & _; u7 O# E3 k" B# L5 N  X  AFreshman   Gaopeng Yang  Female   158.9    46.0
    * ~2 C+ F3 W: g, Q0 bFreshman      Qiang Shi  Female   164.5    52.0$ N! W0 [* `; D0 n
    Freshman  Changmei Feng  Female   163.8    56.01 g' F, D6 K$ h! y3 c* T" `
    Freshman   Xiaopeng Han  Female   164.1    53.0
    8 F! ?8 C, }8 V5 UFreshman    Yanli Zhang  Female   165.1    52.0! _5 n) s1 [: ?. K# O$ A

    $ U' K( t6 s; H" y1 x' x6 t# j18 w8 T* y% |, k; h
    2
    ( V8 g$ N9 G/ h3
    4 `1 {9 q2 A- H$ Y# g6 Y5 P4
    & j0 v6 M/ G& j% q53 B# \6 L+ \; X
    6
    : E8 j/ }/ E4 l( I7+ x$ ?) f) j- i) [5 B/ S: x7 T' B# L
    8
    8 }" [7 n$ F4 ~& ]$ B9, O5 ^% v1 L# s
    106 A8 H: W6 f+ J4 K" s
    11
    2 L/ v- A  d! z9 z12
      M+ O4 _/ \% @& l/ o$ d, n$ j133 m& P! f  {9 z+ X9 E/ D  D- j. [3 j
    147 M4 E0 Z6 M1 ^% X( k
    15
    * @. ?' e8 u/ P$ G16
    # P& D0 p- u' F( E& ^17
    3 ]0 c) `0 O! j+ w" q6 K184 U. A. j7 H( y" K9 \% P
    19+ W; {7 t- e# k$ s
    20+ Q! L* D9 c$ }9 V
      由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:6 n# W; `, H6 S! \

    ' K; Z/ x8 k: i6 V5 k==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
    * ~. G: d3 W0 Z7 m$ L>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    ! ?4 F+ j' x" p. w6 L7 ]5 U, Ares1 = df.Grade == 'Sophomore'! i7 m1 Y& u! z9 X; U
    ) O, x# d5 z1 Y- }' i* Y% o
    res1.head()2 t( ?* T! \$ y' g7 c+ l
    Out[31]:
    ( F0 A8 {$ z  ^. @0    False
    2 W. B1 n+ U* x1 c1    False
    & r: @# t$ I! t# v4 Y2    False
    / o& A1 S$ P1 {' ]) q! ~; h. Q; e3     True& {4 H% h/ R5 L7 X4 K3 C; P
    4     True
    ( v* }7 ~# m. C/ G( P/ ?/ uName: Grade, dtype: bool2 P  S2 R5 y: V8 U) U$ c  S' h& G

    8 W) X2 c/ a; z) O+ Q3 l, tres2 = df.Grade == ['PhD']*df.shape[0]8 ?: Q! i. r# b. s' g
    ( A  q7 Y' h5 n$ A  a; J7 H) B
    res2.head()
    0 H/ e& l( W: C- e/ wOut[33]:
    7 }" M# n4 C* T# P9 X! e) v) t0    False
    : @* A# m4 ]5 I9 w) K1    False  n( Y3 F8 h+ ]! o# ?6 Q
    2    False
    + T( [5 N5 M( e. Z3    False( A) j' B' R) J. s
    4    False
    5 s" c  D( L9 r& ?, h7 jName: Grade, dtype: bool
    # N- a! c) ]4 H9 W" ^+ o! a
    ' r, S' @- O6 J9 P6 K  A$ X, M, Zres3 = df.Grade <= 'Sophomore'6 g5 o  n/ l' q6 c# J. \/ N

    , c; l" o* m( I4 N6 I8 n) `* rres3.head()9 j' n) S* q3 b$ M% ^
    Out[35]:
    0 x! h0 ^1 c0 W6 \- h' \0     True
    + o, e8 H7 {; _0 j1     True4 Q' d+ N$ g6 l" s; R4 e
    2    False" n+ w# H; e- i( ~+ [+ k8 e' m
    3     True
    # D/ |, H' Y( v) G0 u; _4     True
    7 F' D+ G  q, I! |% TName: Grade, dtype: bool2 V( I2 e" d9 q
    - p  G9 N2 R5 ]( c, U+ i7 X
    # sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
    ' h. x9 r. B% G, P: dres4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) ' D5 ]0 n1 T3 Q' |! ~7 |+ j, r
    - v/ a) p& X8 J) ]/ Z- `
    res4.head()7 w, ~! L: n* u$ \  P
    Out[37]:
    8 s8 a% t  z+ ^0 r  @0     True0 C6 G# T) W6 U0 c& d" W
    1     True# R* C3 P( v! p
    2    False, g+ }, j# k% A$ W
    3     True
    4 ?* s7 P  ]1 F' c4     True
    8 r% w( V" M  J3 C; [Name: Grade, dtype: bool
    , |, o6 u) h$ L6 t% ^- F8 r" j+ K2 u$ s) n
    1
      Q2 ]. n' P2 W+ f# o0 j7 f9 ^2
      [. \" w( v0 q  V/ E4 \& n3 g; O31 k, b9 w" y  j( G+ N
    4
    . n' I/ P* o& h8 L3 b5- O- R" i% R+ N8 _& M
    6
    , O0 K! u* y1 l7 [/ x7
    3 n. Z+ I" D/ p# P, \6 \1 V84 v" D5 ~* n) S4 |
    9
    & X, v; l' i" W10
    ) i) ~+ X# a2 l, s' H1 {11
    ! i2 ]5 L+ e. e12
    * g! a4 K" k& l6 c131 y. i6 F* z9 w9 t; q% W
    14& r/ f1 q3 }& U( o8 f# o
    15
    ( k4 I$ S, V7 Q1 x16, g- @8 N  l9 C9 K* W' [, C
    17) B. |8 n  J! m+ q, q5 k" m* A, _& v
    18
    ' {6 F) n  @3 C19
    5 U6 N5 Y& H* z: j; Z& U$ M20
    , ^, }$ D- m4 p* f213 Q0 `2 {& Q" y6 K
    22* q0 B+ S1 J& A0 t
    23; C; M  t9 a4 B" i; N
    24
    ; z4 r7 u( R1 }" Y* }" l0 r6 p* G251 l5 M& V3 W, I. F9 C+ `
    26  m) N# U9 Z/ y+ q' D
    273 C) ?* w6 t& z4 z0 s; X
    28
    0 Q  {! O* H1 l- [) z29  ~" G' G6 v$ ^
    30( r6 E+ x6 d* \" _6 `/ y
    31
    6 R  H/ v; }+ a4 i32, r) P8 V% ^6 ]* h( v6 S& l
    33. i! |! J- e) B8 E
    34
    ; ]0 `2 Z* y9 ?; y; b35* _, O, B* I% a; J* @  p3 G
    36, b  X" i; c/ F3 X
    37# y' z- G1 y, j" @* Z0 O- @
    38+ P! P' F1 S  |5 u
    39$ Y, I3 ?! p  b% E: z# n* m& o* |# ]
    40
    ; R; q; b" a& D0 [- N' h" A  t: v41
    ; R/ N! s2 Z8 u. B0 c5 o/ @1 y, Y( A! C42) F0 q  j# w6 |% S/ X2 A/ e) U8 t8 ~
    43
    , g9 G" i% l; P  m7 R0 N. q' \44
    / i. A  Z. V! f1 v* g9.3 区间类别
    ! U3 o' e; d% K8 c" z! H9.3.1 利用cut和qcut进行区间构造
    + F' Y, F' K9 d0 @7 t$ q0 u  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。* R( A) n* n( K9 `1 ^6 h

    . t; B) P) D4 Z& v9 `( Q) ?' xcut函数常用参数有:
    ) |9 ?  {& `. B3 Q% }bins:最重要的参数。; N! w+ \! D7 D/ V0 H4 `  E
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    % n  Q0 X/ m" }  ~; ~也可以传入列表,表示按指定区间分割点分割。
    ' p) ?8 S) M; O; a6 F. C3 R4 [  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。1 x" T1 L9 `" Q4 x) Q
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。( q* `' O5 l7 E2 X; j

    5 Y0 E$ B& r. t7 [  ys = pd.Series([1,2])
    7 @  T/ _6 w- K0 Z( z# bin传入整数5 l6 [( b. s( Q0 H/ O
    ) w  W2 }& ^  S
    pd.cut(s, bins=2)
    & @; n  s0 a) g0 S$ t8 V5 dOut[39]:
    * H/ k! E* @7 T$ T- T0    (0.999, 1.5]- E" X& L: J7 Y0 w
    1      (1.5, 2.0]7 p( n9 E, ~, R' R* g; w
    dtype: category
    ' a( L) T0 N% Z4 K6 GCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    & n  k9 u% i/ l! W
    , n3 \# c) s3 s/ @pd.cut(s, bins=2, right=False)
    # P: }3 j) N9 b% x2 JOut[40]:
    : w: S# y1 C0 u! E+ f/ P7 N0      [1.0, 1.5)
    , w/ c. h  D8 e$ y( I" t2 B1    [1.5, 2.001), I- {4 D, o0 q( ?% v/ R" B
    dtype: category
    9 |# d: Z7 d  O4 v$ JCategories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]4 ]  G0 e6 H2 P
    3 v( D1 g, Q5 m( `; \$ W5 O" w! }
    ' i0 ^# W6 B8 |) t2 |! ]& r
    # bin传入分割点列表(使用`np.infty`可以表示无穷大):
    ( ^+ P6 ]# `: i* `pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])
    0 w7 b4 I! X# |: [" q) l6 p% G/ YOut[41]: ! ~1 O" K/ o0 I- w1 a! i3 Y6 X. C
    0    (-inf, 1.2]; e+ K9 F8 h" r: S: q- G
    1     (1.8, 2.2]4 l" f/ n) f5 t( r5 {$ g8 j
    dtype: category
    , O9 P9 \6 F& d, G  OCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]7 W5 F! m) u) B  w- k
      E- C/ X7 c- y+ H3 \# t: G3 G
    1& q) u( X5 S3 \
    2, C( f: a$ G( s/ v
    3* k* b) q  a: g  l  l$ m& |* e& g
    4( k, |+ f( W4 n$ I+ Y/ T4 }
    5* ~, z* @* E3 W' S
    67 z5 N0 o' |& B& g$ z3 e/ }& h
    7
    . S( w1 `( d4 F: |* Z) J8
    * Z, C' ^7 f0 X5 J1 A9 Q9
    7 x9 V, T$ \: m$ I$ y2 r10
    $ J" ~! p' S, \0 O5 V11
    $ E6 I) u' R  ~0 s12
    - E* B+ d: r9 F! k6 K% J# D& R135 B: Z4 Y# ^7 o" n" b( A
    14% L/ r( d' }3 ~0 V1 u3 \
    150 E' c8 K% I' l
    16
    ' J( ?0 \8 k) w17
    5 Q1 X8 l; Q2 h- V18" n+ M4 \1 Z8 V
    19
    7 ^% T! ?0 V$ [  E5 L20: D) W$ Q0 \, c9 Y" Z1 E
    218 G4 ~4 k4 C0 h# {) k3 ]6 H
    22
      c3 i/ d. e$ ~: N' s9 T' N23
    . C' U# f3 |0 e$ |$ V8 e) J4 m( j5 p24; s# D9 N5 w+ |2 V
    25% X& m. k; `5 l9 a, y, v
    labels:区间的名字1 G, Y6 W1 t* ]7 T3 h( }
    retbins:是否返回分割点(默认不返回)5 x1 r1 Z  M  z- G+ B9 Q
    默认retbins=Flase时,返回每个元素所属区间的列表
    ! c# V% W/ F/ d3 u4 Eretbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值
    % v% {* j. ]0 ?8 y; g/ |0 N' l* b! O' y# [/ U) d! |
    s = df.Weight' R4 Q2 g0 K2 W4 o" I% w
    res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)" M8 \8 ?* c" V! U, e* j( ?$ W
    res[0][:2]
      h) C" U0 r8 t& q7 E2 h% Z: C0 N8 E0 o0 Y, F+ L
    Out[44]:
    " g" F; W8 y0 ~/ l; z0    small# a: r( t2 l2 E$ b0 e9 G( V! j
    1      big. K! Z) G! t' H9 h, ~/ ^
    dtype: category
    ! }' B* [4 N) O: HCategories (2, object): ['small' < 'big']
    - ]: s' d3 h: w
    ( j) _7 a( P  E  vres[1] # 该元素为返回的分割点
    % N) {4 W! u) {7 \Out[45]: array([0.999, 1.5  , 2.   ])
    9 h- u- u( N& u! p6 p8 r6 X6 m8 W9 b1% M, L8 f/ N3 z+ ~! A
    2
    9 r0 R( t# Q$ f& \) `2 ?. _) v36 w4 q+ {- ?0 {4 L, {( f
    4
    $ i4 K+ r5 r; b; L51 J" _  l% S, L: f. [& @
    6& g6 y& B3 v& Z9 n# M" h1 j
    77 E; s8 u  o# q0 }
    84 F  g- e* q3 Q+ I2 P" t
    92 f. X$ d; V- z4 `2 q9 k$ c" G
    107 w, P" |3 V1 A
    11
    ! k6 c7 E5 p& [! p& s12
    $ F7 m9 f- d- B0 a& Z5 nqcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。9 x$ `7 A! H* B6 Q" M) i5 |0 {4 h5 e
    q为整数n时,指按照n等分位数把数据分箱% a' Z1 L3 p( X( }; G9 l' x  x
    q为浮点列表时,表示相应的分位数分割点。
    ! P. }3 U9 l/ y, u  s+ f, D7 hs = df.Weight
    8 h: _& T+ }' b0 t; V) N
    - B' B* K/ I% d, b/ Zpd.qcut(s, q=3).head()
    8 ?/ _/ k+ z; |; [% _9 ROut[47]:
    2 W7 a0 \4 ~6 o7 y9 F2 k0    (33.999, 48.0]
    ; f, h# ]8 J. j% N1 i1      (55.0, 89.0]  J  y2 [) h" f4 W. _, ]
    2      (55.0, 89.0]$ ]8 ^5 P; @' `- a9 [7 I" Y
    3    (33.999, 48.0]
    : C! _3 o/ O! I2 V/ j+ ~4      (55.0, 89.0]
    , Z2 c6 v7 R% DName: Weight, dtype: category& s' F, j, t! z8 o. S+ J& d6 M3 x
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]4 x  c4 U% _. {6 P/ p
    + U; V/ H1 W! b( v& _$ U6 I3 g) I
    pd.qcut(s, q=[0,0.2,0.8,1]).head()* C* N" @: p+ l9 q' g  Q! q% _  P9 @
    Out[48]:
    ! q! W$ V  V' r! e) _. [; m0      (44.0, 69.4]$ R1 T3 g' G6 y% m9 x7 Y' h
    1      (69.4, 89.0]2 s8 I; U2 s& E! V
    2      (69.4, 89.0]' T- r6 h  t: C+ Y$ X
    3    (33.999, 44.0]3 z$ ~& E% {1 K/ ]2 _
    4      (69.4, 89.0]2 d4 Q) c# `. Y* {+ {: p1 S# ?
    Name: Weight, dtype: category
    ! u2 e% {+ c: N# r; qCategories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]. }0 e3 ^$ U- j; [0 D8 Z" m

    ' i5 G+ z6 u7 z! M3 b& O2 B16 s# ?4 W& o/ i4 ^! v
    2: Y: c6 f; }- X7 A1 S
    33 |- r! R* H5 s8 n, q$ i4 g
    4; a  o& ?" z+ J- ]6 _
    5; T' j& ~  F0 [/ f
    6
    : Q4 e( M* C7 G7 p. W  c  j* M" n7; H9 }( @0 v- C) p
    8* c+ W/ N0 r: V' ^! ^
    90 p9 i' Z/ x$ q0 M( n7 U! I" O
    10, [1 p. q' U5 y. }
    11
    7 R* j7 I2 W$ F8 M, q12
    $ r$ ^: C% y( i1 g  D5 w7 X13: b) J1 X8 Y0 m- M
    14/ S7 C( e# M4 H- r
    15
    * i3 T5 u1 W* z# a$ H16- h9 D4 M9 M8 ?8 h# _- J
    17
    ; n: d, j# @# E! z' ^18
    " a3 H9 n0 U5 P" D0 s) c19
    1 ?* O) V0 p; r9 `20
    ; F7 a1 E( @% M) J$ F( D% U/ _21
    9 @6 b2 L) h" a$ A9.3.2 一般区间的构造8 Q5 c- w; y. @# X& S
      pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。2 G- E$ B7 `) z" H2 l+ \* Y4 ^' w

    1 I6 V4 Q' e7 L* a开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。, ~6 a# |) T; n
    my_interval = pd.Interval(0, 1, 'right')
    - [1 x" s. D. |
    + m/ Q. L' J) B- }6 Hmy_interval
    ( o8 X' a4 ~* q7 X( T/ zOut[50]: Interval(0, 1, closed='right')/ l2 ~5 Y" w; j. A
    1
    & v! }) l7 T5 F3 x) S9 d0 _2 @, n2/ l, b( O! H( Y6 C8 T9 e) Y  C& ^* X+ c
    3- i; m2 m5 o: e, j% g; A
    4
    9 O8 u9 @4 S+ b5 a区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。1 Z) r' W* m7 r, ^$ w* H( f6 Y
    使用in可以判断元素是否属于区间! m8 F# S# G/ ]' l/ k3 A
    用overlaps可以判断两个区间是否有交集:
    % c! c) E5 ?2 A$ ]% u3 x( j4 O0.5 in my_interval7 o5 |& z) f# M- u% R) \, S

    - ^- p. G5 Q& W" W" r  LTrue: C+ O& k5 U: m9 U1 T! M0 I
    1
    + [) S! Q) h2 ]5 ]: M' d7 G20 r: L7 y% o4 ~& C5 q- `# u7 J. P
    3
    8 }* U. ~2 U$ z& |my_interval_2 = pd.Interval(0.5, 1.5, 'left')
    8 ?, e0 I$ \2 k- x' xmy_interval.overlaps(my_interval_2)
    6 {6 [# `5 F$ k3 f4 v$ {' p6 ]/ b
    True' s* r* w( m2 c) o6 t9 _
    1
      T+ S9 L/ F  F7 K29 i1 z+ `% d% B
    3& s6 O* s' o, r6 ]
    4
    8 y7 G3 _7 d; h- `( x& s  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:$ g" T" p4 K! `
    - r! _9 P4 m6 L* H
    from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:( o  t  A& a) ^+ R' b
    pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
      J- n6 t- a/ K! Q7 ^* D, i, O: _. A7 ^0 X
    IntervalIndex([[1, 3], [3, 6], [6, 10]],, ~7 _# j4 q" v7 h" w' E
                   closed='both',! [+ P' H3 v5 H, v
                   dtype='interval[int64]')
    ; [  K$ _' V; G1
    1 i; q" F( l" y6 l8 L20 F! B* P7 z# Q4 p2 H& S
    3$ h4 C" W# C% W% m; y" {
    44 j# I+ ]; e( M" A0 i' W
    5" x& {5 r1 [+ j) |
    from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:: S3 A+ ^4 ^: u6 q3 a
    pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
    9 V6 G. r" P1 U' u0 _
    ) `! o7 K- Y6 L& PIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    : R/ |+ j, f, r" p3 T0 b                  closed='neither',
    6 w1 ~; n0 s: A+ B5 i+ [                  dtype='interval[int64]')
    / B) y* D  t7 E% H1
    2 |/ d6 z9 ?* N& l" n% k2
    " x- `' [5 O$ j, j1 A, L8 s4 N7 y  M3
    - g  N, _7 N) V; i& M4
    5 H3 D. J3 H6 B$ t( J9 u4 R5
    ! [* o" [6 j) ^  T- Yfrom_tuples:传入起点和终点元组构成的列表:7 @* W7 D* c/ X" z
    pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')) [/ ~$ t2 {* u

    1 D3 x' Q; x! ]1 J$ nIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    5 q8 t. Q% H- W) M, r3 L8 I4 O              closed='neither',: l2 M& Z% |/ p3 l9 a5 }) z
                  dtype='interval[int64]'), Z' j( p, w* s, `
    1
    6 b/ p6 M- \& X% P$ f+ ~2
    * t  v. h/ W( n& g$ Q' }3( p1 T1 X7 ?5 Q4 s" p
    4
    . g  K- r- }& b5( f* m1 p0 D5 r& ~" y3 i. [% w7 m
    interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:
    ' Y3 \! m/ E' o( |7 U2 v$ xpd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数
    7 D/ w8 B# |1 c2 m* e7 B5 n. AOut[57]: + n2 [3 V8 F8 X
    IntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
    ( d% _5 @) |$ F2 Y5 j2 B& i              closed='right',( r6 D3 x: L: s# H
                  dtype='interval[float64]')
    / ?& G! |& i6 j
    2 U( y: M- w2 S0 o* w* `7 Qpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度4 P2 @- m2 S4 U) ?
    Out[58]:
    5 z2 o$ `3 Q# L* W4 E3 oIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],5 y3 |* R1 T; l0 A
                  closed='right',2 m7 z$ s; a+ l% s. O: ~' a
                  dtype='interval[float64]')* j. c$ n3 S+ o- O
    1- v) ~2 o3 ~8 ~, U4 m- k
    2
    ( ~6 _, [" c; W: P& P$ X4 \7 e3! l6 R  ]( {# Q' x! {8 a0 i. T
    4
    1 P. t, D% ?4 A5 Z57 P: l0 p% ^' i
    64 J6 k( L( z4 ^1 d
    7; @8 J- N+ c, s' b5 |, E
    8
    # N- p7 a' n6 W( u/ M: p- B) q9
    * [, S. E8 v1 J7 v10
    + [8 |$ d* _( ]9 l11# `7 H! j# Y5 R: v7 d+ H
    【练一练】) s- y1 k% {2 F0 `0 t
      无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。* ?; O6 N3 b* N1 H9 D/ @

    / G; |6 B2 d3 V% W/ F& `  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
    0 j7 l9 n2 ^' ?) w$ [( M7 ?$ ^8 N+ J+ M/ N
    my_interval
    ( z# B. X) G2 o9 S& T4 q" ZOut[59]: Interval(0, 1, closed='right')
    6 L; h. b9 c! W+ X- I/ j, i* Z8 F* Z8 G4 ?
    my_interval_2% M' }. M$ h! g3 N
    Out[60]: Interval(0.5, 1.5, closed='left')' O* u: Y$ M% f1 v1 T. p

    : W, |( m' I6 }" Vpd.IntervalIndex([my_interval, my_interval_2], closed='left')
    ; ~' M" H0 y" g" r* NOut[61]: ' s+ f* t6 r; t
    IntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    7 C6 h  y# @. [) O' W/ ?              closed='left',
    " W% J0 X' Q9 r" |8 H9 c% Z; f              dtype='interval[float64]')
      O% B) V  g2 W' [; D" R, X15 `7 X9 c0 A; _8 f7 f' U" t( f
    2) U+ y$ f5 b; A& O2 C+ V$ B
    31 P9 B9 [0 f" n4 D
    4
    # p% l9 M6 o0 Z( [% `* h3 R9 h5
    7 i  s; [! k( k; ]- j+ X, H  d6( ]; @6 X7 v$ ]4 W" [
    7
    6 R( e$ d: }" J" X8. m" H: r! r, h! ~% B, @' u
    9
    9 t3 h1 X1 ?: g10
    - F. a# H# J( r3 m% y6 g11/ O* _% y& {/ I5 Z5 \
    9.3.3 区间的属性与方法8 u4 R% S/ u1 [7 e. J1 x/ r
      IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:
    ! {8 s- S# T0 O7 t3 L0 C" Q; y4 U
    s=df.Weight
    ( ^8 ]. P) J# d6 w+ c9 ?id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
    ) @: ]; \% X- S& U. y, Eid_interval[:3]
    % y4 ~3 L  \, O2 p1 ]1 n, u# a% I2 ^2 p1 ^' ]
    IntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
    7 \8 A/ b6 a4 }8 C. R                 closed='right',
    7 ~* Q% Z0 ~8 J1 W3 T                 name='Weight',4 @+ E6 n9 X9 r' u' R
                     dtype='interval[float64]')
    1 c9 m$ [% T/ u5 X9 {$ u9 T1
    . [+ L4 i5 h6 O8 `- X+ v5 C2
    ) q! S& @9 g" S3* A' m0 c: w5 G: _  f% h- n% t
    4
    / O4 r. ^1 x2 a% _5 H5
    9 a3 I& D3 L' ]; l: u5 E' n69 T1 r  `" V1 L: K- d  g, u: M
    7
    ; a& d+ C# a" S1 T8; b2 d$ [# b9 I
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
    2 _$ k6 p. \0 j, x0 X$ a- q6 Vid_demo = id_interval[:5] # 选出前5个展示0 K" p* M' ~% Y8 z6 w

    - l( w" p: n0 d7 g" T' C% N5 wid_demo
    ( e2 ~( F3 C, p8 l- COut[64]:
    / F# R; Z" w1 ]& g* TIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],0 S2 S1 d9 ~8 A$ l, j# }& q. C! s4 z
                  closed='right',
    5 A& m8 x% v. ^% x+ J: l, k              name='Weight',
    7 y' u2 S5 L1 h! n5 r6 z              dtype='interval[float64]')& W9 I- c& P- c! y+ p: z! Z( P; U

    ) i' U# Y4 V5 ]6 L; v$ pid_demo.left # 获取这五个区间的左端点4 b* {0 O" `- r- R
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    0 }7 R. d4 L5 v$ h7 Q4 O: [' s7 j; }) r, Q- W& W
    id_demo.right # 获取这五个区间的右端点" h, s6 }% m$ }
    Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
      {% @% V4 z4 a9 S! J6 U& D2 u  B" g: C, Y9 e$ k! I
    id_demo.mid# |# q, C6 I/ d& p) g
    Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')  [4 H0 W. r  n$ g7 d5 h
    # L8 W$ V, C! N  L/ \  o
    id_demo.length  I6 O6 l# `& a7 ^
    Out[68]:
    3 o- \9 ~0 B2 C& P+ Z9 {& {0 o+ nFloat64Index([18.387999999999998, 18.334000000000003, 18.333,* Q- E) u& W. c! p- ?8 }& D) _8 s
                  18.387999999999998, 18.333],5 p; m$ a4 S/ k) Y; G' Q
                 dtype='float64')7 T0 F% A9 V* Y/ x( ?

    $ U' i8 B* l7 _7 ?# J* i- C/ d7 l9 v18 B8 }& Q7 H. O) o
    2
    3 m. {8 o5 y' \, P6 {+ v3
    $ [. ^8 I  q2 {2 F43 N! q" U9 }; p  ?" S
    58 @# I2 L, [% T% A5 y
    6
    + |( S0 |& C- U7
    . ]# w  H: h% B% M$ ]6 e80 i8 e, E2 X! Z. G4 M* m% t
    95 ^8 k$ M7 s4 j, N# ^
    10
    8 N& L. e3 J( D! `# T11
    ' T( [' b3 W* {' c2 L6 c+ h2 y12
    1 n2 v( a4 ]9 a. x13; p9 H' O/ u" N. J0 Z
    14+ T! M8 g' m+ c% T
    15
    ) {) @3 \. s- V7 E5 T0 _16. j/ }8 v/ _- Y1 u0 l; u" [5 \2 t  I
    17
    2 W% A" l, [) j' I( g18% |1 z# P* _  n; w
    19. x: k, o% X' v; c- _& w
    20
    . D/ d5 h  C$ g$ R21
    ; Z+ T, [2 g3 m( y; V4 l& k7 i% m22
    + x: E" `( C! n# ?23
    + L) }& a; P$ t5 rIntervalIndex还有两个常用方法:
    - \; n( _3 J6 ?6 ?; ccontains:逐个判断每个区间是否包含某元素! e0 h( p8 e5 }5 e# D7 W# H. q
    overlaps:是否和一个pd.Interval对象有交集。7 V4 ]) Z5 X9 }0 h" `( ?+ k
    id_demo.contains(50)- [$ I; q) D2 r/ m% R% Z' @6 T
    Out[69]: array([ True, False, False,  True, False])/ m- W! ]1 @+ J1 T6 f

    % O2 o+ A2 l# E& b) `' |id_demo.overlaps(pd.Interval(40,60))
    3 Q7 T. z0 t. a) ~7 }Out[70]: array([ True,  True, False,  True, False])
    2 i8 y2 A+ ^& N# v! z+ l4 e% ^1) S- V  P: T( U& o& ?' b
    2* e! |1 {! ~+ [( z, {
    3
    " Y! d& u. t' M% o, ]+ T8 x7 m6 R40 B4 L( H2 v; J
    53 j: U; F4 C/ C9 V( _& \' p
    9.4 练习& {0 e6 W% r9 D1 p6 m
    Ex1: 统计未出现的类别
    / m( F9 ?7 [% d- _) N8 L  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:2 j! P" k6 @, u. q

    + B& i. G% E8 `4 M+ {df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
    ' ^- _& V  F* T" A- z1 L$ _. \pd.crosstab(df.A, df.B): ^* c. C. Q% \
    " C# o+ Q+ |4 P( K# z# N$ Q
    Out[72]:
    1 X% r# T0 H3 d6 pB  cat  dog
    ( Q! Z$ P2 r, D1 `' qA          9 c) J7 Q3 H" F
    a    2    0) F6 ^; F6 c$ f
    b    1    0
    ! d  k) E3 E0 }: z( F+ Hc    0    1
    * b0 ?2 x* L4 j  c7 F14 D3 V# s# r+ T' k3 _5 j5 q" e
    2% Q" q& J2 ?+ a$ M
    3# n/ i$ R7 @& `
    48 X3 T8 r: N8 H8 w! K% i
    56 w2 k" Q. ?1 u9 p, f- }/ n
    6# B4 h8 J- w9 I( C/ i3 _1 c/ E
    7
    + o2 j4 S8 `; s$ g8
      O3 U' h& j0 K  }& V: G: Q9- G5 j' l# i' N7 C: X: S+ |
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:8 n; G- y+ ^: b$ u' X/ m6 k

    1 H: ^. h/ g8 V& p# J6 A4 ^df.B = df.B.astype('category').cat.add_categories('sheep')( t6 ~$ M+ c( j% x# j( n
    pd.crosstab(df.A, df.B, dropna=False); T0 ^2 u, W0 m+ ?- h/ U% T
    . y: m( K' T! V: r
    Out[74]: 5 s: [/ I! F# _4 N* E1 N
    B  cat  dog  sheep8 L& Q: j+ M+ X6 D5 G
    A                 , `  v% o: h7 R
    a    2    0      06 ~7 @) w' `; R2 x  c# |; a! |5 x
    b    1    0      08 _* n4 @  u, |9 j: c
    c    0    1      0
    - o6 P/ w: S! j+ X1
    4 N7 ?1 n# V9 D' ?4 [) r; |  {2
    0 H! @' e1 W7 L1 ~' Q3
    8 A+ {4 b% X8 M/ R+ Q3 B# X4
    9 [+ r- x$ ~5 ~* {7 f1 B5
    1 @, F/ p# [1 e: q  r- \4 k+ H6
    # y- d. }) d3 v) {7
    ) d5 _; k2 ]: g8
    % u8 n( O. x4 s) c9 a8 R9
    5 m& `! ~$ F; s请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。+ p5 p6 S0 }; k4 N' g$ ^6 [
    / w6 g  B2 H( `6 A' l) I) Q, ~
    Ex2: 钻石数据集, ]8 }4 ^4 s7 W  e3 h# A8 `& j- g
      现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:; s6 l3 |  H: l' ]$ S5 O
      E8 ^1 ]5 c# L7 w" `! `: S8 D7 e+ _
    df = pd.read_csv('../data/diamonds.csv') " e: Z7 W2 [2 y2 L* f3 D# ]# [
    df.head(3)
    . x  t  X& Z) x$ u
    , x" ^/ {9 a2 {Out[76]: + }2 u& f. u- X9 u1 ~
       carat      cut    clarity  price
    1 q" a  M! F# O0   0.23     Ideal     SI2     3264 q6 `9 G: `% b! \2 F2 ]2 N+ W: R
    1   0.21    Premium    SI1     3268 P1 W( G7 y2 L- Y6 s8 w
    2   0.23     Good      VS1     3271 @, C# m# {3 {2 `
    1
    0 X9 j" B9 G. c. Z$ o( z/ L" _$ [2
    0 w1 F8 s0 X3 I2 [4 Z- E3& A- i7 R) F4 P4 I$ ]
    4/ y6 u% R/ L2 r  w" L% h7 h
    5
    ' a3 a% P- v' q* R- \  A4 ^6
    6 H% q6 m& T( r5 O# ]77 d/ h( n+ j$ [2 A1 o
    8
    ; X" Z6 X) e6 V9 V: d' S( ~分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    $ M; h7 p4 r' g3 W: \! k钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。
    0 p0 c8 }$ m4 I4 ^  L8 \: h分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。( p) y- ^7 ~! L% ~, h( b) i  n: l
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。( \, i" W+ F" {, [; y; ^
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。4 J  V3 J, d& I6 h2 B
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。( I  C2 W7 T: I. H
    先看看数据结构:1 j/ Q! L( `9 P+ V
    ( o! i$ n% y. a* T1 I1 @* C  q" |
    df.info()
    9 W& O6 [0 W3 P  R7 s' b4 zData columns (total 4 columns):
    & @! G' d8 ^( F2 V) U #   Column   Non-Null Count  Dtype  
    4 B8 v/ r. K) q. v3 D0 \---  ------   --------------  -----  6 u& Z+ R, S* i5 p- u9 o
    0   carat    53940 non-null  float64& B7 I; b5 Q, i4 K8 K2 j* m
    1   cut      53940 non-null  object ' h# B, }' H3 }+ E& n
    2   clarity  53940 non-null  object 2 M& o7 G" F: z7 P7 {+ d# B" n
    3   price    53940 non-null  int64  
    , q4 m- ~4 S  _% pdtypes: float64(1), int64(1), object(2)
    7 |8 u1 p7 B" i- F7 ~  A& T9 T* r- P1. O3 `& X: F: _7 L. m
    2
    " g5 q9 I$ G  X, G1 r- ~( G3/ {3 [- J$ U' h& t6 G" `
    4
    0 S+ C0 ~: q& Y% H7 y5
    . I( ?5 E' T$ R6
    1 c8 v2 Y8 l( p7
    + g' M# O/ ^- C; O7 L* ^8
    - H7 [8 ]. ~$ `6 K* h8 P93 p& a3 l) ~7 D# Q! _
    比较两种操作的性能. P' d7 s4 M* K
    %time df.cut.unique()7 k. k  {8 b# w
    # X$ |4 ~! @* K
    Wall time: 5.98 ms
    , p, D9 L- S" K6 B. Q4 [6 Rarray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)
    % W* ]0 U6 C8 T1 ^% n. D! u9 v4 ~7 E$ u1
    : q- }  b6 t& `+ k" [; v2# z/ d2 B- t" _* l$ u
    3. U9 {/ t: I# A' R* n" A9 E
    4
    9 \6 ?" j+ n7 c0 M! ?4 x* s%time df.cut.astype('category').unique()4 I$ l" T$ R' q

    . v% D. m9 E1 B6 T3 u7 MWall time: 8.01 ms  # 转换类型加统计类别,一共8ms0 Z& \8 V3 {3 Q- X$ G
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']/ q; x) X+ ~6 W2 a
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']1 m: d9 w8 ^) [6 W- P7 @" T
    1
    - K, j+ C* h+ A& [2; r. d2 x4 Z+ R+ J
    3
    # h; y3 r' Z. h& X; i4
    1 m' a7 I) p( D  I/ ?: b9 ]5
    % K6 q( c5 }3 V! s9 X7 v6 q5 i  ydf.cut=df.cut.astype('category')
    7 Y5 l: ?4 s8 H%time df.cut.unique() # 类别属性统计,2ms4 X5 @, C/ J1 m! F0 A) I! I: _

    / s7 V9 A, ^. u. q8 RWall time: 2 ms' R8 {- a( L- ~% J
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']& u5 I$ U  p" T$ V6 o- [
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    " h" f1 h, w& X. u$ M1$ Z( \' s. k# J% W3 f: c
    2
    ; F8 B) ~9 r, F0 Z32 J% o  x$ j% u7 W
    4( b; {& u* S' l3 f1 S! O
    5
    + l& q, h" r; z) O$ `6; W0 V% ^: s! A4 k! R- w  l
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。. O3 k5 @/ A; J& F* S: l
    ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']1 J7 N2 W  P6 |7 a8 {
    ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']1 c6 i; {6 q, I1 K4 e6 a. ?
    df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    " z/ ^% h! ?& y& T1 L, odf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
    9 i1 e7 e$ Z" g& H7 `) [3 \" N% W* [/ E' N
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    ! t+ l8 }* x3 A4 Y3 a
    ! S+ p" H9 s6 L- d3 u- n4 K        carat         cut        clarity        price' `/ P' {. c3 `
    315        0.96        Ideal          I1        28013 b; w+ }; F& U5 q" |( q3 u
    535        0.96        Ideal          I1        2826
    / ?: Z; X  T0 O551        0.97        Ideal          I1        2830
    2 G: f. \3 Q! D: s5 ?1 |( Y4 ~1
      E  K6 i( w; P* ?  @2
    3 E7 R- r, f; E; p) |' m3 q35 [) g1 Q) q/ g1 U9 `+ T% a
    48 \8 ?5 l( h1 O+ d6 y9 B7 X/ S4 O
    5% z6 C  L) @' M
    6
    / `! M$ M5 \2 r7; ?( U1 C0 W! ]- F, f
    8
    4 ~3 q/ r. ~$ T' p0 e: {2 ^+ y9
    ! g' P3 v8 o- n+ Q  U; V2 M3 U10
    0 }9 u( C% P! n+ I0 x! S% [11) U: w6 j: h% G: X) N* S
    分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    " B8 O+ u" O% V) f$ p! Y3 C# 第一种是将类别重命名为整数" d% X* J2 x8 E- Z5 d
    dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
    ) J. H; y% F2 T( Cdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
    9 z5 q: O/ v/ R
    / V( [: W- v3 n) @6 Ldf.cut=df.cut.cat.rename_categories(dict1)9 P9 D/ O. F8 e
    df.clarity=df.clarity.cat.rename_categories(dict2)
    7 B8 M6 M; U0 s% ^df.head(3)
    7 ]! V9 G, j/ T3 N* g$ r2 n: V( E& V1 q- Q1 a6 w5 q5 S
            carat        cut        clarity        price
      W( ?7 N2 x8 v' {0        0.23        0          6                326
    1 g8 D6 U" \2 q6 L# E) l$ G( J' \' {1        0.21        1          5                326
    ! [. z2 Z% I5 W, J* I& ?$ s/ C% Y2        0.23        3          3                327
    , e& q  J2 j  h$ s/ V1
    8 Y. o+ k: f% x: r5 Y$ e5 b' X) Q6 H2
    : x2 _# u  o* h  E. _" A3
    ' L. M8 ?+ e+ ~% B( L. h9 ]48 L4 Q# A0 A5 s$ ^4 x
    5$ ~5 ~. D0 O- N  y! X
    6
    : P% m% h4 m; b) q4 a7 q2 S  q# v+ ?7* u+ V! t" y0 s6 a0 Q
    8) m- ]; I0 I2 m2 f8 p" @
    9& i: m2 H  S$ c, D' A- F2 Q
    108 L9 p  d$ T  Z5 q
    11
      A. R! h- [2 y* q12
    , y& C2 b1 {" G" r5 I# 第二种应该是报错object属性,然后直接进行替换% s4 M9 D; ^" ^" _; k$ B
    df = pd.read_csv('data/diamonds.csv')
    ( j& |3 s$ y. K; B7 Ufor i,j in enumerate(ls_cut[::-1]):2 W1 Q8 v+ B8 k# d
        df.loc[df.cut==j,'cut']=i
    , i) @8 \/ P: l. a0 U" O( S1 D4 r; U( [
    for k,l in enumerate(ls_clarity[::-1]):
    $ Y2 _/ e3 @1 G    df.loc[df.clarity==l,'clarity']=k
    4 z! Q: Y% u  `7 ]7 E% mdf.head(3)
    1 J( G- O, f0 a& k. i7 @: f& I5 ?; A9 i1 i* s/ ^, h- z7 j
            carat        cut        clarity        price
    , W# C% P) D! }0        0.23        0          6                326
    - X4 R& E3 K5 x0 x$ \! E2 a1        0.21        1          5                326
    4 m; L! w( D2 H+ ~2        0.23        3          3                327
    ; x& O9 `: z2 b0 b# L4 j1+ P) n7 d5 @& g( d2 `( M
    2) ~) u6 H, D: q0 V$ f: ?, B
    3/ k9 {8 R# W! T9 e7 R1 Y# ^5 L- V3 s
    4
    ; N6 _6 d% ^2 ]5: _# i0 g$ a  p5 j# C" v; u( N+ j
    6
    & [) W- T, f; w5 \( Z6 B/ V2 L7& \% T8 m; @. J0 c
    8
    7 I& a# u& L6 }. v: p7 G: Y+ L9
    8 g- p7 X# s5 s) e9 h# i* S/ ]. W5 Y10
    ' Z9 T, c& x, N# M# z, f' j11" S$ ^9 K* B3 t: u
    12
    : Q; F! }( C3 B13" p" j* D- _3 R; O
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    ( L9 M( [2 s' ?! S# retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
    * H; d5 q& q8 H; M- C7 ]avg=df.price/df.carat
    8 U, ^, Z  X6 n, t! I) t
    1 t, ]3 K$ d1 F4 Sdf['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    $ z' j/ S! J. ?# Y8 H' ?9 j9 i) \. _                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]2 {1 {( h. D7 B' W8 n* o& T

    3 g! l$ N) n3 A2 P- ddf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],- E6 I& F& `+ R6 S( x
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]2 z6 d; n* e+ n2 t, h
    df.head()9 n% j- k( o6 r) k& C6 V

    ) q) s4 h6 I# r* W! y! F, J& w        carat        cut         clarity        price        price_quantile        price_list
    5 E1 W- V- U2 H9 |( _0        0.23        0                6                326                        Very Low                Low3 E" E3 v, t) o3 w
    1        0.21        1                5                326                        Very Low                Low! a' q; b# c  _$ f" i5 k8 R' ^4 C
    2        0.23        3                3                327                        Very Low                Low
    - {4 [2 y, n  y. O6 N5 v$ L3        0.29        1                4                334                        Very Low                Low
    * Y- |; u, `$ p: X: P4        0.31        3                6                335                        Very Low                Low                                       / R9 K7 N% I7 E
    ; z7 w" j4 s: w/ }4 L9 N* Q% z8 X
    1
    0 Z( D0 B- B8 k1 _/ T# {. k2
    . O$ l8 h5 P5 k" W4 @3
    2 ]3 w( Z3 y% J  w8 I. Z3 H5 R" K4- u  b  O7 Y0 N3 u+ ~
    51 i* }( e& G4 ^: v0 N' H2 z1 p) Y
    6; q, r9 K# H5 ]1 Y- K3 Z; K% v2 s
    7
    6 R4 m: T: n$ ^) l0 L/ b9 P8
    . T* p2 ^  _' h, P9
    7 U, r. D( P7 B1 H% W# {1 p100 A8 H$ g$ z5 l8 t, `) b6 r' L
    11
    " }% }& K2 Q  {5 B  V9 y7 x12: \9 `" o# P: w* x: E
    134 D9 \4 ?3 q& i. h7 |" @; H  n
    14
    2 f6 A7 W% [( h3 I3 h8 W. j; R15
    # q9 U4 i( U$ i! `1 e3 A% O1 h0 h16
    # h3 B8 o/ S3 p9 J+ G分割点分别是:
    1 t2 a9 {6 v: D8 V+ p' P# D1 ?, S$ j: ?5 D& P4 r0 J
    array([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])
    % {; ^" D% j$ U/ _7 ?; V- Warray([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])% }6 V; W) L9 z/ A4 d: O$ n
    1, x% T4 i1 N5 r; n# T& t
    2! y# j- h5 N0 M8 R' l) ]$ F2 c
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    ' v3 s# @: S. F& ], `! Zdf['price_list'].cat.categories # 原先设定的类别数
    $ ~: \6 j+ A6 d" E! h2 AIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')& T' g2 Q/ q9 d9 \' |. f( z1 d- \' v
    . j3 ^+ }' q, k( z0 F
    df['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别8 f$ ^& N8 S! P2 V
    Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    ! ]* b0 E' X- s  X1
      f- r& }" s4 ]$ I( T' @5 y2
    4 P1 z5 b% p' j- n# W5 S32 K) d  j& y  i7 n3 Z2 [
    4
      m7 T3 W' Q3 D1 P6 O$ Q1 S) G8 ?$ Y5/ u! b1 \* o1 ^& y& f
    avg.sort_values() # 可见首尾区间确实是没有的
    $ r" g3 P/ t5 l" t& h& ]2 s7 b; h31962     1051.162791
    / Q, n" J0 ^, {' N$ V$ n15        1078.125000) d8 L6 s: X; L* ]
    4         1080.645161
    # Z4 T: M8 z: T% t3 }  p! _28285     1109.090909+ ?# N, o8 i/ p. b: M. \
    13        1109.677419
    7 Q9 O; z$ x' S% Q2 b6 D( S             ...       w, V- w7 h- a5 O- `' c
    26998    16764.705882( q3 j# i- I- y6 Z) y
    27457    16928.9719630 ^  N5 r+ z1 [% h7 C
    27226    17077.669903
    # d6 l. y4 a  [+ K4 x27530    17083.177570
    & Q: V  A0 ?/ O# g+ P& N- j; _2 |- l27635    17828.846154
    3 H, @! o: g3 a; f1/ p' s0 V3 |1 r2 I( o# s/ @
    2
    & O* \2 g* D: c* T) H37 K7 E! {% W4 M6 ]* Q+ ?0 `
    4  [8 I  [; ^5 s& A0 [$ c
    5
    $ t+ I; d( q# F/ }) i8 w6
    ( ?# o$ a9 N1 [7
    4 P' M, X- a1 D' K0 V4 c8
    . G  M$ K0 E8 A0 ~' u' n; |95 G: H% _/ H) w3 B8 s8 d  i
    10
    $ L$ [' U6 X) v1 _: [11
    ; a" _3 i: I; ^. n1 y" a12
    . I$ U) }* m/ B- Q6 d: @/ d. q对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。
    3 d0 B1 d. B" I. F: |6 P$ `# 分割时区间不能有命名,否则字符串传入错误。: H0 L/ W5 d) O4 S
    id_interval=pd.IntervalIndex(1 P! A! k! O5 u2 Z
        pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]: c. \# H) }' q0 X. Z
                                ); ~) `1 q; x( ^  }# n  S
    id_interval.left
    # q) B) `! C  w- Y; `2 L. wid_interval.right- M' `* F/ D6 t' ~7 B% Z1 N
    id_interval.length                           
    & z! c9 K9 K0 o* W. \# I+ ]) P1( ?( U& x2 {  K+ T
    2, D9 f' p# ?2 U( G5 j2 {
    3
    / E  v. u& {0 ^. R4
    ; L+ a  p1 L/ c( s2 [5: g3 k, y4 K( J. _! y4 F
    67 [  ]: e7 U$ z& n7 c0 E7 o9 R
    72 v9 Q$ l" _' N' `( o/ t# [7 P2 x
    第十章 时序数据
    + g9 o  E$ l$ i+ F" l8 t2 u* Nimport numpy as np
    ; {+ z7 L5 z+ u/ K1 m3 d' M7 |' qimport pandas as pd
    3 [/ [5 q* F( h" ~. }1" V  b2 ^4 E  |6 q: H& @
    2
    0 e  s1 [& ~1 U6 h  e6 ^; C
    4 }& |! e1 k5 C, p/ O0 H" W
    " a; g. c4 c6 j3 _) P: U10.1 时序中的基本对象# @) d" S9 v4 M. h- `
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
    3 s$ b) V! Z4 X' e7 U4 C: x( A+ c) t& z+ P- p
    会出现时间戳(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的简写。
    ! z$ a; W9 |% t' V' x/ C1 i) K  P  V3 f! L6 `* W; t
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。
    3 r" V/ j1 r1 }, \
    7 A# f5 r1 b  f会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。; u2 b& z' _9 a( y4 x

    ) U& N9 g8 g1 [. A# M" `会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。( k7 S3 E( {: M/ }- t* e4 B
    4 |3 u+ w6 t: r, v1 f$ B( @
      通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:8 C5 O+ r$ r% W0 g7 D
    2 v& V' r7 ]$ v- K
    概念        单元素类型        数组类型        pandas数据类型
    0 L! I0 P- l- X/ Z! l) ^Date times        Timestamp        DatetimeIndex        datetime64[ns]
    " C* H3 A+ b4 \1 w3 u. k+ j( E# a* m  xTime deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    ) c1 ~; V5 {& O1 ~- ~. s: s4 gTime spans        Period        PeriodIndex        period[freq]
    & W6 }. h) n& ?' B- a) {! ODate offsets        DateOffset        None        None
    2 q! g9 ~7 i7 d  由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。3 Z1 I( n# q* w3 q
    9 e  Z4 w  C1 ]% `' X
    10.2 时间戳2 f* T! c5 X) }1 p( h! a0 r
    10.2.1 Timestamp的构造与属性
    7 L) {" ?) {. {/ ?7 t( ]单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
    ( |$ b( i9 s) ?! H( c
    / o; z" F# Z6 y3 ^/ _0 w$ G! Mts = pd.Timestamp('2020/1/1')
    ; q# @6 n" R5 l) A
    1 f% u' u  ]3 ]" D! Lts6 F8 Q3 M$ T. J/ F2 J. a7 z
    Out[4]: Timestamp('2020-01-01 00:00:00')5 g; ]* d( s4 V. V

    6 K% O0 a1 S9 K$ x* g: lts = pd.Timestamp('2020-1-1 08:10:30')
    ; P5 Y" u/ F; e+ l! l
    + U' g: M' \0 i: Its& z" x- \( w+ @$ l# [$ v4 |
    Out[6]: Timestamp('2020-01-01 08:10:30')& e$ |' y% c+ [! |2 ~- h' k* U
    1, Q& S0 Z$ }) x4 D
    2" u8 R7 H! X5 X. X; X9 N( o
    3$ l7 i6 I% x9 D& {, t- s! F! w7 K
    44 ~2 o+ O) A, i5 e  d; J
    5; u' |2 B2 n$ X4 v" [; T0 U
    6, [/ R* `, c/ a. P% S
    79 |, k( ]: A. z2 v
    8. q$ A# O1 _- ]
    90 f8 B7 i% x8 u, b0 x( V/ t5 ]
    通过year, month, day, hour, min, second可以获取具体的数值:% D$ `7 h  ~9 d8 k0 k

    ) J, `: d& E# p7 Zts.year6 V7 h9 a6 ]* s$ j& i4 S$ s
    Out[7]: 2020# C" l! @& [4 b- S

    4 j/ M0 a( J6 pts.month; L- o/ ]% K! W" L4 Y
    Out[8]: 1
    , ^4 y1 J6 V' y# _" L$ K; k' J- ~" ]/ H  v2 x
    ts.day' U9 \# z$ J5 {* B) Y1 C
    Out[9]: 1
    4 t  ^7 ]; g3 i) {2 r- n/ ^/ a/ D$ k
    , h" [, L4 T0 X" Sts.hour
    ' |0 B0 l. `. @/ I, K5 [Out[10]: 8
    $ G3 \) t) z% m- y+ ?$ t; |9 j0 C2 S) Y# g5 \
    ts.minute+ N& A8 Z% v' k+ ~- ?
    Out[11]: 10
    # V8 G3 Y% D- t! q+ w+ N
    - `  \- f( T" e3 o) l& w  X6 fts.second+ B* r0 M& ~) j' |2 a7 o0 T0 V
    Out[12]: 30
    " y* L6 R* U1 k" I0 T
    + F$ D$ F6 V9 l; q* I. K0 [1
    4 T5 x; j3 ?' X. c* o; [9 o2
    4 H3 Y" P5 I0 b* Y  {3. q, r" E2 A. O9 D1 m- ^3 ^. D7 v" _
    4' U. Q) V) i3 @! z$ \
    5( Y& T4 L! ]; L$ I' Z" |
    60 G; j* R% s  I9 X8 @
    75 ^/ O' d/ t) k
    8
    ' z2 o3 y- B6 G% G" u1 X6 \! K: w9
    % J, c7 q- l  o* u- p4 B. `10+ ?2 r! f3 f, i6 q" e  C! g7 L" I! W
    11
    ) ~6 y* ?1 M  Z' y" X12
      H7 h5 h) h3 m2 R( L; a  s" b13
    * l4 S. l% |5 [14% x5 G& c# U8 [
    15. T7 E) C* @% F$ J, T0 T+ ^( _. T
    16
    0 E( i( {; k. h) P( a0 D9 D17
    ! J7 {% y. n* b. |8 [$ t% A; S# 获取当前时间, w0 R( d; a, C4 I; o, a/ R
    now=pd.Timestamp.now()# w5 @; J/ t" U" O
    1' y. H0 ^7 u1 N7 K
    2
    , }8 P' o$ G7 |* X2 P* D, a0 H+ l4 ^在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:
    & Z* s) D. {/ ~+ iT 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)
    0 w' T' C9 ?; TTimeRange=
    6 k! I9 u+ S1 A& N5 b6 x; v10 ! c" r3 e' _( R: e& c4 [
    9
    & C$ l* P9 Z% U ×60×60×24×365
    : }. u4 a( n& u( I2 ( e, J# g$ Y7 N( z8 y4 F
    64  k1 ^4 ]6 P8 o: X

    * f8 d& ?" W' e# q​
    6 T! [2 j5 w, U2 x ≈585(Years)
    6 x2 K  T5 D) L" K% y
    , V, ^7 ^. S6 S5 s通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    ) O$ J5 s# J+ z) m6 c
    1 ^4 {$ i; V4 I& opd.Timestamp.max
    - x* v# z9 D# c" i, s4 GOut[13]: Timestamp('2262-04-11 23:47:16.854775807')
    ( P1 Y/ m1 O1 I9 k7 Y2 ^$ ?' t7 }; c1 S
    pd.Timestamp.min$ N. Q, e% j* J: b6 A0 u* \3 U
    Out[14]: Timestamp('1677-09-21 00:12:43.145225')
    , ~+ {, I3 T4 R$ a5 K0 h: }( g% j/ T: }$ z
    pd.Timestamp.max.year - pd.Timestamp.min.year
    & e% Y; ^' H( n; m5 {/ R& r4 J! oOut[15]: 585
    * Y$ k+ y3 y1 _$ _5 l2 D$ N1
    1 v, J' s. L2 ~% M+ l! y2
    4 B3 Y6 j7 k( C; D35 s+ n/ J1 d& @. F
    4
    4 r: r. P! I' l5
    " ?. x' d/ Y5 s% H. S" n/ D6
    " x1 C- s2 L+ e, M# G7
    6 `& Y  p2 `/ l6 o: _8+ }& Z  s; d# H. l5 R( D( I
    10.2.2 Datetime序列的生成; ^- `7 V6 J4 @( P  p: O
    pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
    ' j/ m0 u5 f# }$ o                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
    : g- p9 W8 B7 x  e9 j0 \' F18 e. i+ B( D# d2 i* W
    2
    ' s9 l. r/ \$ O3 _& O4 wpandas.to_datetime将arg转换为日期时间。8 @& |! U- v7 `3 b6 H
    7 a, W3 S; q5 Y
    arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
    ! e7 I0 Z7 a" O  Y( f7 @errors:
    7 e, X% g; ~; J9 `# x5 S* X- ‘raise’:默认值,无效解析将引发异常, o& F" V0 R: M# f& e) @
    - ‘raise’:无效解析将返回输入
    : r0 a; R# d% y, N- ‘coerce’:无效解析将被设置为NaT* J2 m' n2 S0 t, i7 r
    dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。( j/ _9 ]% \0 M" L$ }! K+ I
    yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
    " v" y" w# T' [  r( |utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档( x+ c- @- q4 R. N
    format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
    . D9 Z/ N7 c) g3 lunitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。! c! M& v# X. w
    to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:0 G& E2 z4 Q# g" P
    pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
    2 x3 ^/ u0 i( r  {% b. \  A( z& V" r2 v! H* J9 D& K+ U8 y7 d6 ^
    DatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)$ d# h; k! e0 Y  j3 N2 Z  W1 p
    1" }% G7 I. {# V& w" N
    2
    4 F- J1 d0 A: G+ E3$ G* A2 H7 b9 F  F
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:5 ~( f" Q4 V9 R. O/ M2 U
    # S( j. X0 ?- w% l2 [2 b
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
    - S0 {% J7 x0 ?6 f- ltemp% g8 w$ A, E. V% S# {/ _$ H0 D
    # ^% a) I8 T6 T# E$ G
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None). i/ m9 Y- E% A5 F6 Q2 V
    1; C- |- \- N2 u6 ]8 U5 H9 {
    2
    / X" ~  ?" x0 W5 J2 ]3) Y7 b( v& Q  h
    4/ _/ H: Y5 Y! ]/ u
      注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
    6 K: D" t: C( P5 u, C2 b
    / T! F) @5 p5 ?+ C; T6 T: q* [pd.Series(temp).head()) H3 w  l3 u: q/ X. ~2 S' e  V

    ; u# `; E* c% H. `3 Q4 i5 Z' X; w0   2020-01-01+ T( z5 ?# ~! b' C6 \; ]1 e
    1   2020-01-03! D% s. N! k3 W* w' A' G: M% f
    dtype: datetime64[ns]9 P* J3 [5 X0 s+ O& N9 F/ ?) X' f% }0 u
    14 M3 C) J  r$ G8 b
    29 N2 O! D$ z. c* `
    3) {. \4 D! X# ~# L
    4
    4 S( J( y8 o. ^$ j5 n5- G& x% o8 v$ }
    下面的序列本身就是Series,所以不需要再转化。
    & F/ p- W8 o$ e" n! {9 g9 E* E$ K, I0 y, g
    df = pd.read_csv('../data/learn_pandas.csv')
    ( Z3 G4 w0 ]: w7 ~, F! js = pd.to_datetime(df.Test_Date)! e+ `$ M0 c% M1 x
    s.head(): ]0 Z& C9 u+ T7 G( N4 _

    " _) |0 s1 k+ l" ~* \0   2019-10-05
    , l* i: H  y* m! s0 l1   2019-09-046 V5 J: z( [: U% b+ f$ V7 ]( I# h
    2   2019-09-12
    2 h4 K3 _& i# S9 _' f& g5 t3   2020-01-03# L8 O/ p, s1 [+ n
    4   2019-11-06
    " _/ U/ P% A; zName: Test_Date, dtype: datetime64[ns]
    . ~. Q# H+ h& K+ V/ H; F12 u; S4 p) ?- J9 r/ d: C7 V
    2
    1 J, W9 ~/ o8 ]3
    & M% g4 \5 r/ [/ V) {6 Y4/ r0 ^/ ]3 }0 `# g4 u  g. n
    5
    . c) N8 J# n1 x* Y2 j$ f. V6
    , h" |4 l7 F* z1 M79 X5 X2 ?  M; t
    8
    6 o9 n- ]$ X+ b3 G  o! f9) l% |6 u" h9 S- `* j
    10
    , \+ j' H' V& d% T& n5 g8 h把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
      Z- u6 ]9 l( Z+ F- _df_date_cols = pd.DataFrame({'year': [2020, 2020],
    3 ^/ D7 U3 }" Z; }: q                             'month': [1, 1],) }. n4 H9 e' [  E
                                 'day': [1, 2],$ S) {( K# u4 s+ S+ g7 C( }
                                 'hour': [10, 20],
      y0 ~* z* A2 H                             'minute': [30, 50],( S" p; K" j9 W' i4 v; Q) \
                                 'second': [20, 40]})% G7 b" Z9 R( Y; u
    pd.to_datetime(df_date_cols)3 K6 y1 c2 M; U/ x. t; u

    8 [( H' W6 s. p6 a- E0   2020-01-01 10:30:20/ j6 p* v7 ^/ {% G" g9 S
    1   2020-01-02 20:50:40* h$ i; B. w2 x! V& ~# _1 y
    dtype: datetime64[ns]
    / l! K' Y0 L7 b1
    7 V4 R3 K- W# k2& U0 O8 F/ B- K1 h; i5 l
    3% @; S% w& G2 u
    4
    8 W  S1 o' E1 t8 H4 E, J. k5
    # {' }  B- f5 ^  F; j7 N67 Z! k; y% A0 `, q" P4 G# [' K
    7
    + r9 P. r$ x" h$ l2 h% F0 c6 g8
    ) G) c* ]" a; a8 X# Y9 B0 {5 s9- X; O: j: ]3 F. q" s
    10/ i/ G$ f. f0 K4 }, ?* F" A. g
    11
    : J- D' K% y8 Q# k+ G" g! \date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    7 F) m! T2 H6 P4 @0 g5 R9 Ppd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    . ]( B( t8 [6 h1 x# d9 nOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    $ ?; h1 Q: ]2 s, I: \3 k8 |, E% `% ^" K" g/ l4 m4 C; v
    pd.date_range('2020-1-1','2020-2-28', freq='10D')
    ' F# b! @& R6 C$ }& ?% vOut[26]:
    $ E& w- ^) ~7 N7 ^DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
    ' T' Q6 S6 N* o" W               '2020-02-10', '2020-02-20'],
    ' B* R' _9 e5 M4 \) f              dtype='datetime64[ns]', freq='10D')
    % e, `/ b  L- K& X: R' B* X) @. j7 f/ }- @
    pd.date_range('2020-1-1',
    , L8 r; Y6 [4 f  o              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天
    ! m* T; A& `7 R+ _' P) B% E! w  _6 H
    Out[27]: 7 v5 j2 _3 |8 K7 Z5 q8 U. R
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
    ! s2 i& p( w8 f% N4 B               '2020-01-24 04:48:00', '2020-02-04 19:12:00',# |8 o' U7 H/ j* u1 k0 y' P) O7 {
                   '2020-02-16 09:36:00', '2020-02-28 00:00:00'],
    % Y$ S- T; f% v' K8 f9 j              dtype='datetime64[ns]', freq=None)" q1 G; h2 ~* A- r  q

    ; W+ ]0 R. {/ b9 Z% p& j1  l0 Z, u) h$ K9 r" V
    2
    9 A! }$ W2 T6 Q! r6 f3
    5 {% r4 O0 Y8 I9 Z, M, |+ E$ W40 D% e+ W- R& v. r7 \3 J* P3 c6 ?5 o
    5
    ! `/ x8 D  A4 ?  E& o1 \6
    4 L8 A8 d+ e; u% i/ r/ T7' z. c( u+ \( }. _; [- p
    8
    0 I/ W3 E4 x+ l7 B' a, M" w9
    0 J5 {" {8 ]+ j% P+ y10. A8 q8 k& ]. [
    11
    8 e' G5 ~2 v! [& H3 v: q! c+ d12
    $ f: ?* h; ^5 w" P- B# j# x5 a# y13, \1 _+ m0 y* Q( X( C
    145 M+ I0 x& ?) X8 Q
    15, l1 C, K! u, V% T" W4 ]7 y
    16
    % m4 r+ U- |& Z& w17
    # j& a4 L: T8 p$ w这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。5 _$ ?: ^4 h4 p; u, ~$ Y! Y
    6 ?' F7 j3 O, n" g* ^2 P
    【练一练】
    . F& j8 q" s9 n6 s+ q- ^$ u# C) d: Y3 ]Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。# [( o7 C6 Z& E5 b5 L

    " y& o9 j* A1 n1 d" Tls=['2020-01-01','2020-02-20']0 L, b  S7 T6 ]: F
    def dates(ls,n):
    # Z2 @! a4 o- j) s    min=pd.Timestamp(ls[0]).value/10**9
    , w$ a6 K( _( G# m    max=pd.Timestamp(ls[1]).value/10**9
      O- v) R+ b1 F8 }6 N, g! m    times=np.random.randint(min,max+1,n)
    . @& H; x8 N6 C    return  pd.to_datetime(times,unit='s')
    , V, x; j8 {. t' V( Tdates(ls,10) 6 q4 A( i5 ~, N' `

    # V" u( P0 h0 ^: q6 ?/ RDatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',: j3 R9 h/ f7 @1 W
                   '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    9 u, O0 a  w% S' _4 I               '2020-02-15 00:18:33', '2020-02-11 02:18:07',8 T: A+ I, R( u+ ?1 |
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',) d% d$ T4 z. Z
                   '2020-02-14 20:55:20', '2020-01-26 15:44:13'],
    . L' @/ U  T  s/ X# ?8 J/ [              dtype='datetime64[ns]', freq=None)
    + d' L$ ]+ y" c2 n1/ S- O2 M" k+ t4 g6 T1 L0 D
    2
    : W  L% y- ^' U: S/ }7 m$ t0 \0 Z3
    ! [% W8 ?4 t" Z- P7 d4, d+ h9 |" F% R7 E3 y
    5: D0 I( W7 P* }1 A3 Z$ j, f
    6
    1 i' s. H& [9 H4 G0 N! J. m- I* {3 d76 f/ U7 h' Q6 n
    8
    8 q1 D+ X3 G7 K, s) w  ^: _9
    / R/ E4 U* d; A! A10  K- N# m! q! m' r" v
    114 K3 [) T2 O2 L5 g( Q
    12
    % T4 t- ~. R  H13# i& D5 ^: T! F3 P4 x; _
    14
    / U' t5 U5 n! p2 T& Wasfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:- t) J1 F* ?' Q, K
    s = pd.Series(np.random.rand(5),( w. ^& z6 P4 K$ J$ o3 N
                index=pd.to_datetime([
    5 x, f& @* m% [7 o                '2020-1-%d'%i for i in range(1,10,2)]))0 X3 ]% {* q' I; x1 y+ z
    4 W0 a0 t$ w3 M) z7 k2 F

    % p# ?) {6 S" l4 K; W% \9 |( Qs.head()- {! \( E/ l' v
    Out[29]:
    " Z9 E2 P6 D5 C2020-01-01    0.836578! p, }: q+ f) I' K7 Q
    2020-01-03    0.678419' D# a' [0 V, N( {, m: q, C9 J
    2020-01-05    0.711897
    - V: B5 M: {  G2020-01-07    0.4874293 t' f" A' y0 X5 B2 H4 S
    2020-01-09    0.604705
    $ N8 W( z! p# {: n" `dtype: float64
    7 {5 P0 M0 @$ ?4 o5 @+ O, u3 J; s
    . n& D3 ^. e; t8 js.asfreq('D').head()2 x! q9 q. q+ F% b# \9 K9 \
    Out[30]: ( v' z4 E' s2 |
    2020-01-01    0.8365787 ~7 J' a3 S6 y; P9 T* ^& i! D# M6 z
    2020-01-02         NaN
    5 h9 ?; B1 w+ i* j2020-01-03    0.678419. ^% U* L# G! l; f3 r+ ~
    2020-01-04         NaN, \7 r5 S7 ^, O. g& C5 o8 r' p
    2020-01-05    0.711897  j( l6 e. `& m7 j" h6 H
    Freq: D, dtype: float64
    + }4 S, m) T8 \. {; m1 @) j
      c9 F. d) t8 C6 w! a& i! L, Cs.asfreq('12H').head()
    2 R2 Z! a  a! F$ HOut[31]:
    ; g  u4 S1 Z2 `5 C% g2020-01-01 00:00:00    0.836578& `) `( _9 y& Y9 m2 ]! M/ p3 D
    2020-01-01 12:00:00         NaN+ f1 t) e. o9 s2 L5 d
    2020-01-02 00:00:00         NaN
      H' r, ~7 v, E, q5 v  l8 o2020-01-02 12:00:00         NaN" U: J$ x* E% M% @, S
    2020-01-03 00:00:00    0.678419
    5 n6 c$ F! W% ]4 m6 Y* h. S, `% YFreq: 12H, dtype: float64
    5 C$ l% _+ D- p% O) X
    , X# D; w; |! m1
    $ Z+ x4 c  o; s3 y0 E" @1 P' h! z2
    5 h3 a2 ~) x" z# _$ c3
    $ }' K) u/ T2 Z- b+ C& L43 {& s0 Z- H- Q7 M! e
    5
    & V* t# [; W' j9 Z6: V( D8 {+ J% n% i! M
    7
    3 s, h/ Q/ ]9 p" _' p8
    7 y; }+ t7 L3 F" C94 b2 x% K* F3 E
    10# h* z3 z- Z- ?0 O# w# ~) {/ J7 J
    11% \3 {: H* u) g0 H( H
    12/ N. N8 V  M6 J3 ]! T: b! o
    139 C8 G+ _6 j& j: I% y
    14* |& [$ [2 _( y/ q3 P% ?- C. Q$ T
    15/ Z  N1 x: t! D7 a) A: ~" j
    16
    ) {- H' H# W! h, x. F' m. j( a, C177 P. V2 j' \4 z5 E* d+ U. @
    184 L/ X0 o1 C# K$ k: Q) T
    19, L0 c/ u$ u/ C4 A
    20
    # E" P* H* u  g% t) r21: M$ ]! [8 b% t0 B# m
    22" a( H: ?" w# T
    23; Z1 N) i' O# f, W
    24  x) n2 u: \; i& `7 [9 U. a
    259 R- Q8 {0 E& L) [: b
    26% j  n5 |' l) H" M3 K
    27
    1 _  ~& ~. M, A28
      n$ `( G6 l( G5 L295 j5 i* N! N, z$ t; B' h1 W8 u
    30
    . |$ O+ l8 Q: [( x  _31% f* l( M" {( d) _8 [
    【NOTE】datetime64[ns] 序列的极值与均值
    0 `5 P* ~* Q' g# m7 m8 _  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。9 r1 r6 R2 Q% R* l7 a

    ' e& }" g" b3 n8 u; T' ~( F" t10.2.3 dt对象" o5 Z- b( i/ f9 n9 p8 `
      如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。: Q6 R$ |1 @) m2 A

    ) Z) Z: H; w! ~  L8 S第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
    ( g' Y& s# ?; T0 \s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))
      g& p' O5 Z$ n2 V# x- o3 B: V0 f' L; B6 c* N
    s.dt.date9 _0 F9 p! ~; T0 y2 e9 u6 T
    Out[33]:
    + K8 ~7 k5 f8 q, x: A/ S* u9 n0    2020-01-01
    ( b& r/ Z3 ~  t+ {& X' i- v1    2020-01-02" m7 y' }6 {" y: ]1 }' T! J8 e
    2    2020-01-03% m3 Z: s6 F+ \3 O! F
    dtype: object* U$ p4 f& o( P% r) S$ b$ r/ g% l$ Y
    6 m2 Z/ Y, G% U$ V
    s.dt.time, W0 L: d, O6 q4 S  R0 [! s
    Out[34]: 4 I$ R! G0 }0 u, q
    0    00:00:00* Z5 |6 l: Q9 Z* [, M) K: h
    1    00:00:00
    ' L- V8 g. C1 R2 o2 L" j2    00:00:00# B' R' z6 g2 l1 X
    dtype: object
    * e1 Z! ^. A+ ?" q) ?& U' a* B  X7 q% _+ n/ _8 z! D8 a
    s.dt.day: B5 O1 d% s% t0 F6 Q
    Out[35]:
    & w5 v& G: a9 V" V0 G' E0    1& Q+ h$ n% {* v* c6 N& d5 J
    1    2
    5 [4 l0 y- {; I% O  [. A& Z  V2    3
      K, o8 F- O! [dtype: int64
    0 M( p# M0 G3 v) Y5 f1 b: q) O8 N* R, u" D: t6 h. y& v& b- O
    s.dt.daysinmonth4 Q$ P' {1 |6 s, i8 g4 S
    Out[36]:
    3 D1 X: [( A! x& ]0    31* D( ~1 N  l% J5 ~  O) L; ~
    1    31- `! x, i+ |3 c% x5 v! I
    2    31% Y. L+ P$ w6 n
    dtype: int645 v2 z- {# O$ h0 g% O
    * U. b1 V' X3 a- K0 N
    1/ _/ m7 m7 ~" N" ~1 i
    2
    : i$ F- h4 {# ]) C+ {3
    9 o* n; R5 D, i" T- k; {. b, O* K0 `45 `. y3 a0 C! O6 i8 q
    5! y+ A. G. R- K* ]" ?1 C
    6
    9 C8 h1 E$ L6 w3 f5 s/ ]7) K% H* Q. e# _! Q; ?2 R5 f
    8# s: j) w5 G( d+ O' k5 u
    9
      R! m/ J  N5 m6 a$ W1 k+ I* B10
    ( y2 M( o. T& `4 S11
    / Z' R6 f$ o" c12
    ! w$ T' e  C0 S6 V5 j3 P: F130 k- x+ F2 b! h; t, w, j
    14
    6 G# h) r9 {& X' W+ x7 [15
    1 U& K" U* r; G+ V7 m! P16) o. u) b( ^( x3 M2 r4 v
    17+ Z, s5 K/ A- v7 l
    18, `: [, K3 D; v% s2 `; Z5 g/ W& V
    19+ M; k% C6 N) O4 }9 j5 J
    20
    ( n2 [5 G  j! d$ Z21$ q' X6 f  Z2 g" k
    22
    $ H% q* Y+ n* _2 P6 U23
    4 u/ k3 s. _2 q3 V" s$ @# b24* i, B, E0 c: K( V# C) r  C
    25
    * ~( n9 X: M7 z% ^3 S# w9 m26
    3 {& S$ _( |2 D+ I' C9 P+ U27
    . q/ u; i" R* b28
    ) G: a2 A& `& z9 q' U29/ K" J' k. A" H
      在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:0 y! Q' X; p2 _2 s4 b  M* K
    3 R) E8 X* ~! d) b' E
    s.dt.dayofweek* }! v4 {* M. b& M2 d
    Out[37]: " \5 i# o0 z, R% F1 p
    0    2; _$ O# U- t$ {; m
    1    3  k+ e7 _! I. _( t
    2    4  f/ o& X' @6 T/ b! X
    dtype: int645 g, t2 w, a* Q- H5 I
    5 a; @! r7 `- R
    s.dt.month_name()/ I! ]5 t- g4 Y% w) X# C4 ]6 V
    Out[38]:
    9 r; Y! V. l  y+ t; @0    January
    + Y  P1 S  S, u1 F" m1    January; s  N* v) J* @: ~! c, \1 H
    2    January! y$ G, Z, Z: K8 Q
    dtype: object
    1 \' c$ a; j8 E8 ?' P
    : t  h* }5 {+ R+ `3 ss.dt.day_name()
    2 D2 U& q0 m0 C, f4 K! ?Out[39]: 2 S5 L9 i: M9 h. Q+ Y
    0    Wednesday7 t# l% w1 B) j/ u& ?
    1     Thursday) o7 Q- O; f7 v+ h& W, R6 P
    2       Friday4 e0 z6 W% K. A' q
    dtype: object
    * G$ l! a, S2 b" U8 }2 p, U- x2 ~
    0 ?7 ]4 @/ w& y. D6 X! D1 c1
    5 |% [, d+ D  @, F8 s0 R' G  @2
    1 g1 Z; P" `; S, ~) C  E2 W/ c! T3
    6 D* P4 h& p3 }6 ^  @) X; r1 m4' y4 r  Y, @7 _' ~3 H$ ?1 o( R4 [
    5
    : c$ S! [7 P  h5 M6
    0 w& c! _2 q  f$ G' h7
      R4 e, H9 X1 B; X0 z5 v8; _) k0 a5 y4 ]3 u
    9  D/ f2 r8 j: j: i/ W8 `! R9 t! Z; \
    10
    - c/ l! h- N# T7 N11' G3 ~9 j$ E8 `3 Y
    12- [6 Y5 Z0 M* H2 ]2 K2 X
    13& J6 \, H1 w! N8 h% Q
    14" i4 \  \8 L( o
    15' W- {. `3 d% ~6 ]4 Z: b6 |8 k# Y/ M
    163 k; ~% ?0 F! y1 U
    17
    5 Y/ N3 G% P, M1 ]5 w18" f# \, z6 z1 h3 ]8 Y
    19
    ! B4 C7 B$ y4 h7 O+ j" t! y200 E' y7 |% ], w9 W
    第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:
    % j% b2 w/ H5 o! s0 H' _s.dt.is_year_start # 还可选 is_quarter/month_start- C% _( W4 V9 S  D. W8 p$ C
    Out[40]:
    % D: u7 r6 v5 |0     True
    ) L1 U% |# r3 t5 o0 ^9 E1    False
    0 J( r# x( s, o5 K. |2    False
    * a2 q6 t6 j' P2 Z" V7 Ddtype: bool% N- Y+ p+ A+ v# ?9 L1 `( b6 u

    2 e6 e( z# K& Z0 G# r2 b. X8 ^s.dt.is_year_end # 还可选 is_quarter/month_end
    6 x  W/ n$ |6 O( m0 B3 U3 Z* COut[41]:
    ; t, E" t) G& a+ C0    False9 `8 ?, _1 _( t) _0 Z
    1    False+ g/ C: D" ]  a( e1 {" I$ N
    2    False
    6 U% U* W$ ]' s! ^8 sdtype: bool6 G7 y: P1 D+ _6 g+ q+ S! L
    19 y. O$ y& K9 H, |- }8 _
    2! f/ N5 H" P5 w
    3
    7 I  r8 k# N3 a; R+ l$ }4
    % ?; ^8 U1 `: d( b* {8 |5( F, D7 q9 \/ y; B
    6
    ) g2 S9 L( L! M4 y5 m# ~0 s5 Z77 I+ @- i: Z* I) r
    8: O; X1 {% T3 Z8 x1 u
    9
    0 m7 K% l) O" `/ b- V3 U10
    3 {3 q# t* u% r! ~2 }5 V11
    + X) k; u  J' J0 o8 n0 S12
    , @1 p8 ^% n0 i( n3 g7 K1 |13* `- Z- z8 I  p6 }3 \( _( B( I5 l! o
    第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
    " c! g2 B* t3 B. qs = pd.Series(pd.date_range('2020-1-1 20:35:00',
    ! `) `8 @. D0 c& Y                            '2020-1-1 22:35:00',
    . j3 L" [  ^; K/ h; r                            freq='45min'))
    ) P  ^+ s: l, n- i/ R7 G7 W! R# a" f$ F

    # B' S% Q  P3 u; s) r2 Bs. x# U2 d2 W6 ~; P# V1 s" A
    Out[43]: ) I" p7 S& G1 N& j; V0 I
    0   2020-01-01 20:35:00
    $ N$ y, W4 |* s) O$ `! b  Z5 a, d5 v1   2020-01-01 21:20:00: s" L$ M& C& B3 p. L6 j) D$ g
    2   2020-01-01 22:05:00
    3 Z. E& ~9 _! O* C" Gdtype: datetime64[ns]8 A) X( P; }" f8 E7 T' L3 J
    5 `. j! T/ d4 l" {* ~, V6 z( z
    s.dt.round('1H')" e0 G  e0 Z4 f5 ]: L: v5 R
    Out[44]: 1 x' l5 E* M# N7 B
    0   2020-01-01 21:00:003 S4 c: x5 C0 m  c; F
    1   2020-01-01 21:00:007 E. |3 x; L& w% E/ S; q  }9 |! a
    2   2020-01-01 22:00:001 X; p1 i' j2 H( w8 e
    dtype: datetime64[ns]$ M* F/ t, r- s+ p  p2 Z( K' a

    1 @% H5 S: T, a: `9 R  a0 Rs.dt.ceil('1H')0 K8 Q  ?' h! i: K+ H  @
    Out[45]:   M0 E# y9 P: y4 c" o
    0   2020-01-01 21:00:00
    3 j: A# L( x! Z7 k. z9 ^' J4 ?1   2020-01-01 22:00:00, Y; V  T5 Y0 h( }+ y% ~) @* }3 p
    2   2020-01-01 23:00:00$ s# k  G3 f+ y: @- f: \& A& C
    dtype: datetime64[ns]$ c( O# z' _0 c$ O
    1 u! S4 n( {# y. J5 r
    s.dt.floor('1H'); i+ O$ n) p+ J0 }5 r% n& k7 P  z
    Out[46]: / `" g  c& c# G+ \8 \
    0   2020-01-01 20:00:00
    , A5 S8 Q, y2 |9 K" s2 @3 _# f6 a1   2020-01-01 21:00:00
    ! B2 ~9 J5 `% b* m' w+ T) ?2   2020-01-01 22:00:007 L! }+ i, G* S- L8 \! K
    dtype: datetime64[ns]
    % k9 u; u! ]& P$ S/ l4 g7 I5 U# H0 ?% I7 B
    1
    $ B! S1 M1 A; n5 {" Y( w, M3 D: B2+ \" Y( j% r' Z6 n$ r3 b
    3) s, x6 L! ^( U
    4
    - C; w6 N0 E6 X0 O$ v5+ g9 L% {( t  H. m' O
    65 G' w0 B: A5 G- K9 n
    7+ s4 o" M, K0 |9 F. Q5 s  I, o
    8
    % `/ u9 y4 x  p& O) i$ G9: @8 r7 e$ O  ~/ [
    10* F8 t+ G' f  C4 S& G
    112 l/ Q2 \  _. V1 l5 F. g( T
    12- d; r4 i/ x0 ^4 ]. T
    13
    ( H1 c- V5 E- Q8 }# B2 @+ @14, a0 h' X4 f& r0 _4 d& ~# \# E
    15
    # j* U$ m3 c% ^& r  w  ^16
    & [9 f/ c4 U' @+ p: k, x17
    + a) k) C9 Q/ _) s- J9 J18$ P( H7 r( o+ t+ G
    19. f" P" o) v+ }' y0 s3 i. R% I
    20' I5 R4 E% K2 [" |
    21
    4 c' R$ M4 e9 |( m- O, v. R3 _* N222 k! a6 r  b8 C; x9 f
    23
    . q/ k  A: L5 y24" }  p4 p# O0 j, i; N9 x
    25- R5 C; G, H* }$ _4 ?5 Y' T
    26
    3 d* P3 ^% y, B9 G2 }7 F27
    1 i8 ]: ]7 H' c& s0 l0 K# o7 o  g28
    6 W. m4 X7 m2 }$ X; U: T29* e$ S: Y& r! E7 ~* e$ k+ X
    303 l* n3 K7 C& h' x/ A" Z
    31
    3 K$ H9 \; _1 P3 ]. b0 k! r5 D+ |32
    * d' F0 i& B6 L3 _10.2.4 时间戳的切片与索引
    7 [3 e: a8 H7 D! ?7 }) J% o& N, K: [  一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:& C! E4 i- J! o" I
    ' @9 w& W) A% v
    利用dt对象和布尔条件联合使用
    3 W7 o& x$ y1 N+ B% `利用切片,后者常用于连续时间戳。' o- I( K+ I2 u
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))" w8 c# S2 {6 g* y
    idx = pd.Series(s.index).dt
    $ ]" F7 R/ y' ?1 n, b4 d- [s.head()3 R5 Y) n2 Z7 }9 Y5 a
      k# B8 @  ^: s9 F4 [
    2020-01-01    0* w* @5 Z$ L8 E6 A! q
    2020-01-02    1
    / N7 c8 X6 I8 L* G2020-01-03    1( j8 A/ ^/ x/ ], D4 F
    2020-01-04    0
    * j8 O# A8 G% T3 l0 I# s2020-01-05    0
    9 ]5 b+ ?  Z% q' x9 n) s0 yFreq: D, dtype: int32
    ' H1 ?- k1 J3 L1- g) e  m- e5 v4 p' K: C! N
    2, w- p" H) Z) s( W+ ?  D  r
    3
    & `; l+ X/ I6 b, v$ ~4
    ' @1 a+ w$ F. K5
    1 O  T  K4 `. H# X7 w' o6( M* D  O" F7 J! a3 |  Z
    7' u3 P! ^, M+ a- s6 p7 \
    85 N- Y/ A! @9 J0 v6 O. n- T6 [
    9
    * c; g, |0 w0 r; \7 r8 s10
    / E- x  c, _5 [9 }$ i% |# u; k8 bExample1:每月的第一天或者最后一天
    , Z$ ]! G4 T' j4 L8 I# f% L( }  E  H, Z) Q0 X8 L1 S, b
    s[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
    . n- P# Z9 ~6 |  Z& L1 U0 iOut[50]:
    9 F3 v9 g% M  R. h7 {2020-01-01    1
    2 u6 e, E4 e, A/ Y/ D3 [/ T2020-01-31    0
    5 q, K/ J# I5 K2020-02-01    1+ |8 _# K# R/ M. g/ y6 i8 Y
    2020-02-29    1
      s# A$ v5 O$ L0 N( L2020-03-01    0
    , P" H, N" l. |) z: z+ Rdtype: int32" n  K; h* ]& b8 L
    1
    9 H- K/ E, Y2 K8 c. i4 m2  ~* l- @5 o. m
    3* s- I3 Y6 i; a! d: P0 J
    44 a  D/ m$ b" O, i
    5
    6 Z* n; h. J/ l/ j) M( H( L6
    ( r% Q( I" q/ d* J1 l- }- l9 c& o7) F- `3 D3 l- Q! Y1 d7 k
    87 e2 \: f7 U; s- z8 m
    Example2:双休日
    0 g: B" [6 b7 y$ q7 C3 S9 p
    , u) c2 X3 @/ \9 o3 i1 Z# i4 ys[idx.dayofweek.isin([5,6]).values].head(). f: o. e; O1 f0 d+ l
    Out[51]: 9 q& {% w- X' S$ ~
    2020-01-04    12 E9 ]+ n0 c( L& q! C
    2020-01-05    0
    # w& b( l& j: R+ {0 ~) V2020-01-11    0, ^, e$ `' w6 J; c
    2020-01-12    1
    8 _6 K3 x  v" K8 R2020-01-18    10 X. w2 X4 O; V1 h; O
    dtype: int32
    4 L4 ]$ J$ C5 ~* }( `$ J: i+ ]1
    * v* A8 u/ _, @( v0 v: n: q. _2
    1 _, f4 j3 C- O2 l9 c36 c, n/ k1 Y' H
    4
    ; ~6 H) z+ }% z5
    1 ?4 Z, x4 [# }60 m; G) S  v0 {) E  c( }
    7; q" I4 \% A0 C5 F5 W
    8
    ! E0 M3 R) g4 e! {7 |$ FExample3:取出单日值
    . w' E/ S/ [: @! ^" G: `  a: O; p1 k# f; b  t5 V( I7 e4 M9 P: u
    s['2020-01-01']
    / `/ t  o; U' ]) f3 kOut[52]: 1
    " U* @# ^: D: m/ P" p6 P
    & {' B: k# A$ M5 s6 E: A; us['20200101'] # 自动转换标准格式
    - Q2 U/ J8 \. {" f5 N) }Out[53]: 10 H" Q! _5 x) R' ]; ~/ ~# C
    17 {, p, o5 G* t1 l
    2
    8 T% z  H8 {8 k, z- ^* K+ `3. r* w& w* A4 R) R( s, e
    46 {  g: {/ M4 n; _1 M
    5
    9 J( h! ?. f/ ~, IExample4:取出七月
    , j, S& e4 O" X+ k
    : Z9 h& w- W6 t( c( [s['2020-07'].head()/ ]; v: I  }* p
    Out[54]: 7 |4 A$ {- D3 A" C8 y
    2020-07-01    0. N7 H9 s* S* |, ]* Y- s
    2020-07-02    16 |7 @' w$ i  Q
    2020-07-03    0
    8 e, E- R" q" a- R9 T2020-07-04    0
    ' @% v+ M) l9 @3 T# S4 n2020-07-05    08 R' {$ G/ d. G4 [) _9 h9 v3 {
    Freq: D, dtype: int32
    8 Q( `$ n+ E: F2 C9 k1
    , F2 r; l8 }" X9 n9 R+ |2
    # N8 C9 S* f$ K2 ~- j. ]3+ b4 {8 I8 u! y% j( m: j+ l
    4- \( R8 v0 G% h1 ?& ?  i
    5
    7 `6 j/ Q4 w7 v3 @6 a6 z" F  v6
    # g$ B3 Q( Y! l! q! y  r. m7
    ; c- A& T4 }& ^$ L86 q' \+ G: j& b, w5 i
    Example5:取出5月初至7月15日2 o; E- W& E' q2 ]: b8 p
    & v* O/ O* Q! s. R- T8 f1 [7 I
    s['2020-05':'2020-7-15'].head()
    ! D1 O) c; D' o: k; I. @Out[55]: 6 M+ @+ d+ \' d! [  @5 @
    2020-05-01    02 k: i" J( @3 X
    2020-05-02    1
    0 t( q, |" E; E, }$ k2020-05-03    0
    9 F& _2 S' _" Q0 r, d6 S" p0 e2020-05-04    1- K+ S! P+ n3 |6 b3 T# S/ t( [
    2020-05-05    1
    6 Y* J3 H/ q' V% [Freq: D, dtype: int32
    . V( f; K7 n1 j( t8 ]2 R7 B) s$ ~) q
    ( |" S5 b7 e/ h! xs['2020-05':'2020-7-15'].tail()8 i' R- P* h2 t8 v3 i
    Out[56]: & K5 f7 T; u+ X# N9 l
    2020-07-11    0% \/ e0 r1 g% y$ g$ R& D# X
    2020-07-12    04 x& j; w& y) ^0 `; d7 N* o
    2020-07-13    1" T9 d9 E0 X! l2 E8 d9 g
    2020-07-14    0
    ( Y/ _5 T) J) D6 r1 E- {; p2020-07-15    1
    * l5 E9 k! ~: r3 YFreq: D, dtype: int32) Z0 c/ Q8 d7 Q% u8 G3 }
    ' t3 x  J3 P3 B' E2 @7 R7 p( t
    1
      \% v2 k6 P- S' e2
    ' ^. z4 n9 ]8 n- i% S# C/ G! H3
    & ~+ {" o# Q6 P- s' a, D2 L7 `/ ~! @4
    / I5 c# v1 y7 T6 _) O* Z52 G7 M' W1 Z, W1 E+ A
    6( o6 x9 [. ~& _$ B* q3 q1 u
    7  {# E% R( m4 c( H
    8
    + z! R; f1 J& A9 F+ u9' f5 w& p9 Z' U' j1 k
    10
    3 `) E; _0 t: v# E11
    ! a# V- B. V! h126 Y- M* D+ s9 E- t9 ?! I
    13
    * U7 d' i; u9 j! O9 `; ~14
    8 I! M: b' y# q7 y& x( c0 {15/ ]! Y& }5 i5 K" b; }
    161 w* C$ u# Q) s' _. F
    17# z$ H" L% |& F# V
    10.3 时间差" A% c! e' [3 `9 y( D& X
    10.3.1 Timedelta的生成
    " @4 R$ U/ G+ L8 tpandas.Timedelta(value=<object object>, unit=None, **kwargs)
    9 f0 w5 A' N2 f% F  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。
    0 X2 _& T5 |) r" M* d  可能的值有:
    ( f, ?2 r# V/ J1 ]4 q/ I, C
    ( C9 W, Q' u" z* Q4 ^4 X‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
    ! a7 \5 X: j- Z‘days’ or ‘day’7 c7 _6 ~/ b! Q# F
    ‘hours’, ‘hour’, ‘hr’, or ‘h’
    3 n3 Z/ S/ a# b* E‘minutes’, ‘minute’, ‘min’, or ‘m’5 k: B6 u2 S# x
    ‘seconds’, ‘second’, or ‘sec’; h4 O: D) a: V5 t2 t
    毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’
    % x( z3 y1 w% r, E) p微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
    4 q' z; L9 M( m$ E1 U- t纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.6 w# @. ?* w5 l  F% ^
    时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:
    9 g/ j! x- u! L" |' T" Q, h7 [pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')5 r, F1 b" k  j. v
    Out[57]: Timedelta('1 days 00:25:00')( ]3 e$ R6 `0 b8 y' H
    # z4 O2 u0 K( ~
    pd.Timedelta(days=1, minutes=25) # 需要注意加s
    ! o) y8 {& g! y( x5 C& {5 KOut[58]: Timedelta('1 days 00:25:00')6 T+ O3 k* A0 ~0 d# X
    8 O7 x/ ~+ g0 b" X
    pd.Timedelta('1 days 25 minutes') # 字符串生成: ^8 F* M: t. a! R2 M4 I) p
    Out[59]: Timedelta('1 days 00:25:00')
    # _0 t* \9 i/ c1 K( g0 A; o; N) g, ~. J' k. f$ @" A7 U
    pd.Timedelta(1, "d")
    4 z% B2 b* e2 [Out[58]: Timedelta('1 days 00:00:00')2 @+ J- O+ `3 K" A7 A
    1/ L3 r' M# }# Z- s5 v0 _" V
    20 ?" }  \( @! B7 H5 ~) k
    3
    + F# Y2 _% e5 z( H* u8 X6 X. O4
    * F4 Y  Y; i8 Y# f. l  M56 j" S, y' L) W$ @% e8 H
    6! O& S# h" c. m% t# _
    7
    - @& U" L; D- g, Y3 n' }  h  }8: q2 H2 e, O7 Y8 l8 P) B% ?% J
    9
    ) y3 p! }3 a  w+ {10
    . o' L4 S4 ^% F4 u" w2 p  V* G( S11
    8 O2 x6 Y# ^( b生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :
    * A1 L1 J: B# \8 {' }% es = pd.to_timedelta(df.Time_Record)
    2 _, G0 r$ i* I6 @- x) s$ c. F$ Y9 x, x9 M8 C$ P3 ^
    s.head()
    " g  q9 P, t/ i5 N$ {  a0 COut[61]: & J" E: \$ k9 N
    0   0 days 00:04:34
    9 @, e3 a" f# ]/ l+ C1   0 days 00:04:204 \; h4 ]( d; \0 N9 ?
    2   0 days 00:05:22
    $ q: S" U' l# {! m3   0 days 00:04:08- C5 T0 K& s6 V
    4   0 days 00:05:22& s! Q* k2 X  Y# p+ W7 f& O" E; K
    Name: Time_Record, dtype: timedelta64[ns]
    + q( X/ \7 f. D, W1  _( R: y& u! M0 m8 w: z. h
    2
    6 V4 F2 A0 Q4 o' Q( ~$ \3, Y* k( G# @! c3 h
    4+ ~+ v3 Q: p/ @& b9 e5 B
    5: t6 M( X0 Q+ X. N4 o; q4 ]& @! f+ T! {
    6
    % n! B9 o, ~+ o! a9 g% p7
    5 t! h3 t0 x+ Q8+ U8 q$ L0 m& m! m+ m" ~
    97 h: v5 L' w$ U3 l
    10
    / Y! k% q( U1 ~与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
    " m. q( z3 F1 P! t$ Q4 p+ Epd.timedelta_range('0s', '1000s', freq='6min')3 {% H6 ?1 ]- |# }  A/ ?
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')( ~3 V9 p- w0 X( ~# U- n: P) z

    ; r! @) {, T4 S1 x1 \1 Y% f2 C5 hpd.timedelta_range('0s', '1000s', periods=3)
    9 S" k. g; [( K5 t9 i1 UOut[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)2 d: w1 }6 Y+ ^3 F9 o! J
    13 @# q+ ?+ E" ]* }4 ]
    2
    6 s' I# q: M1 q$ p3
    , k) n5 E6 A5 u4
    4 e! j8 o/ ?4 L5 S0 P1 U# ]# Y54 h: S0 e' N/ q) h5 q1 a1 v  R$ l
    对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:
    + \3 g3 c% }, I  z( Cs.dt.seconds.head(). v2 ]; ~/ I; {4 R- u& c
    Out[64]:
    4 P$ g+ h- S: E1 M0    274
    & z5 \0 v( ~" E5 |1 v# ^  }$ i' N1    260
    : g$ g/ b# J6 {9 D( D" K2    322- O  L. A. N9 D7 v
    3    248
    " x4 R: M% L+ z4 b" J' i: J4    322
    # O5 B5 E" @( z" k; j- s; C! G# VName: Time_Record, dtype: int64$ t- }0 }2 D, n# g/ ?
    14 \5 |5 G) R/ W( a# o8 ?
    2
    / A! S1 \: q) D1 K! O3 k( _3
    , D/ L/ @0 N3 T' i. x. b" d4
    4 R* s3 [* ?0 R3 n52 @9 X% z. B% A
    6
    5 \/ g. u+ O9 c( _7 w  v# g7+ l% A' J  ^7 Z. Z
    88 S$ V4 s1 v4 a' V' g
    如果不想对天数取余而直接对应秒数,可以使用total_seconds" s& E7 \5 P8 v3 g: O2 L
    0 t9 k. r% N; Q9 x7 N$ B
    s.dt.total_seconds().head(): U1 F" ]3 H+ I6 x, a$ D
    Out[65]: 8 K; g. R1 _  F: x! j( {
    0    274.0
      u+ L1 \* g* B0 |+ M  Z1    260.0* J: h1 n: {# c, c  v% ]8 Q
    2    322.0
    + O$ C, ?6 y: J/ p6 r1 X3    248.0
    7 z: i, Q' [& l  _# Z4    322.0; c* q8 z" z* o9 v
    Name: Time_Record, dtype: float64
    9 b# S5 p: R" s7 B/ E; I; Y1! o5 Z3 t4 f9 K" t6 S/ ~1 C/ E
    2
    $ n: V$ N( K/ T. ^/ D3
    6 x7 S+ L: p1 q; i5 k% _- V4
    ( O) P- e" D0 O) y50 l* g" t& V, j1 y: J. K- E- `) T
    6
    * x6 d: b8 W7 |1 P7( C- ~1 d, d, C8 g" H  Z
    86 j( ?" `4 U/ I$ V1 d9 G& ^+ G
    与时间戳序列类似,取整函数也是可以在dt对象上使用的:9 v% P5 t  H6 ]
    " Y6 P# k4 i" C3 F
    pd.to_timedelta(df.Time_Record).dt.round('min').head()) K/ k1 ~- s7 I# }, P
    Out[66]:
    4 s9 ^+ T" N. N% m  a0   0 days 00:05:00
    : |$ N1 {4 D9 K  w# C1   0 days 00:04:007 p$ Z) k% S) G) y0 |! n, F
    2   0 days 00:05:00
    8 ]4 S% |  o$ K  ]) ^  \! i3   0 days 00:04:00
    # g4 A' I; l2 w, r9 |4   0 days 00:05:005 ~" o  a8 M' X& ?/ u! Q% `) h
    Name: Time_Record, dtype: timedelta64[ns]5 p/ n& O# k2 W. Y9 F
    1  t1 X+ C' z' D  N1 A( ~" z2 _/ g
    2
    & {  s' `( _0 n9 H( W* D6 |+ ?+ @3+ R* ~6 e5 n7 d; v
    4
    ( P, |' [5 S. a# A, D59 R+ U- _  }9 s5 S: u) c
    6' \1 A1 U. O" |, O; i3 q6 u  C
    7
    " Y7 Z" s0 O' U, r7 o$ T$ u& n$ ~8/ O! I. H. T. v/ k+ g8 `; I
    10.2.2 Timedelta的运算4 w* J* L! }; d1 q
    单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:5 w4 p$ L; F8 |  K& j3 V! r
    td1 = pd.Timedelta(days=1)2 l' e0 q; _! O, |( G7 ^
    td2 = pd.Timedelta(days=3)3 W9 o& a; i5 \
    ts = pd.Timestamp('20200101')4 q0 W4 ]' e3 Y
    7 K  h& e3 W4 `. S2 o) N3 x
    td1 * 2# f$ _* {3 Z) l! l! j
    Out[70]: Timedelta('2 days 00:00:00')) `4 T4 _! n  c' f( d& ~
    % w7 y0 B) n3 M2 S$ g, k" Z5 q9 i8 u$ N
    td2 - td1
      y. M: D; V1 G4 t3 ZOut[71]: Timedelta('2 days 00:00:00')+ @1 k# P( W3 ~+ c2 L! z

      r) E6 e0 C' h4 {ts + td1
    4 z* N* v7 N$ t6 `Out[72]: Timestamp('2020-01-02 00:00:00')% E+ z; S, k. N3 A  F$ Q1 W0 @8 T

    ; ~# t7 K9 w( U6 H+ dts - td1
    5 J* U# c; R. J, B4 BOut[73]: Timestamp('2019-12-31 00:00:00')
    8 c) ]6 X# q! k' ~( u; C# z( F1; I& t2 d2 V3 J1 S
    2
      ^1 f1 W, t$ w. T+ j3
    ) h6 d# Y* L$ P8 S4 p6 ^# \4  o. x( f! W2 U6 Z
    56 n3 t8 [8 ^# P  {! l. _
    6
    ; B& z$ j; v4 g/ N7, d2 V+ F* g6 J, ^5 X* c; h
    8
      c/ B! d( p9 y% x; N. ~9
    0 n% _  X; u) t$ d% V7 {10
    7 ^8 S, t, e6 ]; q8 b  a6 ?) ?11. c- t7 J- ]8 F2 v% y: j
    12( z7 C& I* c7 L7 M0 |3 F
    13* C! R/ V  B* u/ B& e
    14
    - s1 a3 j& X6 e" S6 F15
    / [- D' q9 m/ }" Q4 p: S0 R时间差的序列的运算,和上面方法相同:3 |6 x: b+ L6 K) X
    td1 = pd.timedelta_range(start='1 days', periods=5)  c+ E  g" t- J8 [) ^+ }
    td2 = pd.timedelta_range(start='12 hours',
    - M9 I2 m$ Q' ?! q1 F                         freq='2H',7 I, [7 s$ [# r% @  k' L
                             periods=5)
    & [3 n3 E* W! w7 n1 e1 P: m7 `6 Kts = pd.date_range('20200101', '20200105')7 e0 f4 w) }8 Z3 L9 r* U
    td1,td2,ts8 a, Z8 h5 ?" V+ B3 S3 \4 l2 K( p

    . E; c# @% I7 {; b+ w" i- ATimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')
    & R7 o- Q$ u3 r- V$ q6 M- F9 I7 [3 ITimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
    ; Y, R4 Y- B; T& m4 Y2 m                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H')4 z& F, w" [& S. N: D5 @& u
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',% i% C' }; h# v. y. h
                   '2020-01-05'],8 D' v& k& G7 M
                  dtype='datetime64[ns]', freq='D')
    9 ^6 R" _* n1 l% _. e1 }1
    / A" S( b  b' Y* N, K24 x! c' S( Z; m1 n5 C$ K8 w- J
    3
    7 ~; N! L! X& M) `& @4
    * X+ W1 ]7 f- [9 F8 _5
    7 Z9 \  B$ {; \- x& B66 k2 N+ O+ _: h8 L2 e0 b' v+ }1 p
    7
    ' `) h. Z& v& w0 s, r0 _! \8
    # h* r7 K1 L: v0 \& _9
    $ Q* G0 d+ B) I0 _) i( Z3 {10* f- S# E, B0 @* y0 l/ \
    11. a; b1 N" l8 j) h' [! {* f8 Z5 `
    12
    8 f8 d: |0 z4 m% ?- K  B13" \5 z' }. j( V/ \
    td1 * 5$ q* e: T* ?) b3 N' D( O
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
    - o5 ?5 v! |4 g( ?& x# {
    3 X, B( Z2 b0 D' q! Q$ c& s/ l, F# ~td1 * pd.Series(list(range(5))) # 逐个相乘/ m' t: `; r. z/ q0 e4 m
    Out[78]:
    2 Z9 ?, g0 a( i! T! W  e. i9 e( }0    0 days
    % I1 }% G$ f* p1    2 days( _2 l+ }  q& D0 j& ]- v
    2    6 days
    # s, [5 i6 G# t2 Q/ z- }, T3   12 days
    4 \4 r6 b) @0 s  b( k4   20 days; j  C) ~; h. i( |- X/ E
    dtype: timedelta64[ns]+ E% n' w( ^3 n* t- |: h

    - D7 ]% F! Z/ btd1 - td2
    1 `0 z' y3 N' _4 T6 |' t4 M, A( ^Out[79]:
    $ R& K( x' g  L# O, c! [7 ITimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',
    ) j3 x1 l/ O' }+ a. w6 L                '3 days 06:00:00', '4 days 04:00:00'],
    , l" w+ @. Q" ?5 j. e" N, C               dtype='timedelta64[ns]', freq=None)
    8 Y) v" E& H+ |& |* D  z' r9 Y
    4 j" E/ E9 K  v9 X  }' Std1 + pd.Timestamp('20200101')
    + C1 m/ ~' m% f, zOut[80]:
    % A' E. M4 N0 [7 V  Y& F  v  gDatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    ; d0 ~5 [8 Z: _               '2020-01-06'],dtype='datetime64[ns]', freq='D')
    + w1 s* n3 k+ e: }$ z( d: z0 _! L& t0 J$ V
    td1 + ts # 逐个相加
    4 @" Z: D( ^: @( n. a/ f- B, C/ K) qOut[81]:
    2 Q* }/ t$ i" ~- A' e/ E8 ]8 c' \! FDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08'," v. j& |; F. {5 A- J& ~% W: z
                   '2020-01-10'],
    ; p; S% w/ a# r' `7 p* M5 e  f              dtype='datetime64[ns]', freq=None)& T+ S* r* `; S% u% ?2 k7 c
    ( p" j3 S$ \, \2 d1 M
    1
    9 R# v8 y& `- N; T2
    6 i. e( \* c: Q% R- p3
    # P4 f6 U& N: h; Z6 W( d2 ~% H4; C/ ?* y5 p% {9 E8 _# y( c
    5
    9 M1 l2 M/ T/ E' m6 X7 d% O6
    9 X: v( @% ]( K" w! k7
    - u6 g  \9 D; z, c8 i* y8. Y* V- [1 u3 h2 P( w0 j
    9
    - r& O% \  |( G$ C+ d10: L' S7 S1 d! K$ ]0 k' g8 I! B
    11
      d5 V7 k% e2 o% O: y- Y12
    0 D& _& o  C+ e9 ]13$ y# h+ H- n/ ], n- q7 D( Y# T7 h
    14
    5 d  \4 l; h# M( n8 ]15
    , }0 R* ~' [7 B) s- V168 Q3 [6 i1 z! ]3 I6 m# Q) x
    17% s5 p+ u4 a6 ]9 u7 }1 M+ i3 \4 D3 S0 F
    18
    9 f  e5 e- A: B5 K: m19
    2 ]# o0 `1 x/ G3 v20$ i, t( X! ?9 p' I, o- @( ]
    21
    7 ^0 @9 S5 N8 b0 j0 B: i221 O; m" @8 H0 k( g7 Q; \
    23  y3 d7 m" l( f3 V1 r& F1 O9 z  ?- e" c
    24
    : e  l8 M) k* ?" {$ Q25
    6 v, \( S" X, z+ p( d3 i268 i: O( G) k8 z* g5 Q( t4 x, ?
    27
    ( A3 k9 c$ c- M) x28
    - x) a9 c6 O2 ]7 q10.4 日期偏置
    ; J0 F  u. n, _+ C- g10.4.1 Offset对象
    & {! O8 W2 a% v$ s; T% K  日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
    8 J- j, }! l6 X2 }* Q% C2 k0 x% K, U, u) `8 J( I
    DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
    8 K& y3 u9 |1 S5 x- L, E2 f9 d: J4 z6 S) h# Y
    s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本3 l$ x- ~+ w1 y( z9 ]9 F9 o
    s.kwds:{‘week’: 0, ‘weekday’: 0}1 d2 O% ^% o; X( ^3 G: N
    s.wek/s.weekday:顾名思义, I4 I- z/ U# z$ |! F
    有14个方法,包括:
    ( @5 R, ]6 W& T' i; q4 K% ?1 \
    # q/ n- k  D" [- f/ wDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    * E1 p; d6 G- V3 Lpandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。* e- ]: N: D/ Z- J( Y: |1 U

    4 q. ]2 B! Z$ W( f有两个参数:0 j: I: k% p1 K; ~3 c7 M0 t; v
    week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。  x$ M# t9 x* N! X  |+ _" B+ S
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
    " k8 q! M; F! O' D  l6 ]* h& r6 e' Y7 cpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    & B1 `2 F# L1 V* ^) Y2 S5 J7 d. D* z& q! M7 Y  g, }/ }
    pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
    ; Y& O8 t2 R. G1 rOut[82]: Timestamp('2020-09-07 00:00:00')
    7 F+ \0 j, v" g4 B
      [8 O9 w4 ^# J5 c2 S% fpd.Timestamp('20200907') + pd.offsets.BDay(30)
    " ?5 S4 @$ f" Y) a& N9 @# AOut[83]: Timestamp('2020-10-19 00:00:00')
    8 @% `, I1 j. {7 m1
    % r! b' v. A! ?8 n' s2
    5 h( N  c" i( }7 b3
    9 Q/ w* ~& z7 O7 Z& }  l" K4
    1 z: Y/ Y1 R1 u, m% w% a5
    $ q" J( b" {: w2 F' X3 t/ @5 ?) F5 l  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:& d5 q2 k0 m1 u3 h

    1 \8 m% f% u+ {pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)" X) `( n' w: \' R
    Out[84]: Timestamp('2020-08-03 00:00:00')* p' c, x$ Y* v; K$ c4 N) s' J+ k

    - g( F5 u  p5 b3 Ypd.Timestamp('20200907') - pd.offsets.BDay(30)
    - I9 s/ u! q- ^6 {- R) Y) \Out[85]: Timestamp('2020-07-27 00:00:00')
    / Q- u) c$ v: \- B
    ; z. b# v! T  @- Z  g% y' L, Xpd.Timestamp('20200907') + pd.offsets.MonthEnd()
    % t' Z1 `6 k" w, g: zOut[86]: Timestamp('2020-09-30 00:00:00')3 k# t6 W  A5 x- c6 t3 @
    15 \4 o% R. @6 P. P
    2
    ' P5 X: L6 n6 @& P( c3
    ) A8 x- Q1 t; e5 ?& V  w7 [; u4
    $ O8 n4 F3 |8 O% a4 ^; e/ T5
    & |0 Y" a  K: J+ V5 j5 E! S6
    6 O: C+ ?3 h8 ]8 G& R+ x- ?7' f' Z  Z+ e8 \% b6 E
    8
    1 N& Z+ U8 B0 V  常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
    ( s6 V' [/ M- P" M  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:3 X, U1 z, K& ^! _3 R& Y: z# Q  e

    6 K" x6 l( b5 I0 Dmy_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])) C" d/ h2 i3 C' f4 G
    dr = pd.date_range('20200108', '20200111')
    $ Q" _" u! A% [7 Q2 {& _9 K; a$ E* [# ^6 |; l) C; Q6 D6 c- O
    dr.to_series().dt.dayofweek/ y/ ]. d5 G( S0 {5 u$ s. K6 Q" p
    Out[89]: % @4 c9 R+ U. D" G3 N) W
    2020-01-08    2
    7 E6 N0 J! v. }2020-01-09    3
    ) d" d0 U6 S; n2020-01-10    4
    1 j2 ?! x7 S3 L2020-01-11    5
    0 d# L5 M! m0 ~( w3 AFreq: D, dtype: int64
    8 y" M. H# d8 \- C2 F
    ' H2 `$ a1 L, ?. D0 a[i + my_filter for i in dr]) c1 X- l+ l2 z" Q* x$ _# k
    Out[90]:
    : K! J+ G* N9 ?5 X[Timestamp('2020-01-10 00:00:00'),
    & e& R5 Z' J' x4 F0 k- n Timestamp('2020-01-10 00:00:00'),
    4 W& n# j/ p5 F Timestamp('2020-01-15 00:00:00'),
    + l" q4 x/ {, e) L/ u Timestamp('2020-01-15 00:00:00')]& x) m- T/ X2 S! H2 A3 E: Y
    # x4 j5 Y* g; d0 {& I$ P
    1
    + i: d+ [2 O" [  x/ \29 l# Z- M- R! s" ]4 q% P
    36 y* U3 A) N; t" T% h# w8 T- b
    4; D, v# N0 \4 U  ~7 F2 M: T
    5
    * v& W% H( X$ w6
    ( b9 G# N$ T& o; |" U7
    : N8 J' c# P) s# U" j3 x+ W' D8
    , A  [% O5 ^" V6 M5 j9
    , H* d. @- S' M" W3 m  x* a, }10) k# x2 Q3 f5 R$ j' R: p
    11
    / \" M3 x# n$ \1 [8 k# g  I121 B5 L( }; d+ ^. k4 \) U0 [
    139 q5 h5 ?3 w0 A1 B4 X* N2 o4 O' ?
    140 v3 Y1 y8 B+ o& K
    159 p: d: ?- l- g7 V6 X. z
    16
    + ~8 E! ]( y" V, D$ @- _17& M, S0 M6 v% W
      上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。, [% j9 H; m  W; l3 S

    ' d5 u( `2 s" H7 `% N# T【CAUTION】不要使用部分Offset; I+ E8 Y3 C# W  }
    在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。4 S2 x' v1 Y* C: J$ `- t
      l8 o( L3 d# U3 h( z- R
    10.4.2 偏置字符串
    , l5 Q8 T% C6 v/ O  a; I  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。( Q1 `9 B8 p5 E8 B
    6 N4 \  m& s! l& ?. W8 D* x0 }" P
      Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
    4 H. A8 u+ ^1 j( x, v6 m8 O8 r+ H1 M7 F& r! T) d, G( G
    pd.date_range('20200101','20200331', freq='MS') # 月初
    ! _$ T" G0 ~) n1 a2 xOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')) b* K+ E1 G9 _8 h7 T" a) g) v; X
    $ A8 P/ T! `0 M1 }7 W' \( C3 {  o
    pd.date_range('20200101','20200331', freq='M') # 月末
    5 G: |5 R/ r  l" z4 D3 h8 P" hOut[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')0 z  j' Z  ?8 V/ [' d! i5 M
    # }5 M, q) X# {# _6 ^: M7 n' j
    pd.date_range('20200101','20200110', freq='B') # 工作日" O: ]' Q+ b3 _
    Out[93]: 0 u1 T* W  n9 \' c
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',( l' G7 H7 z* e' _
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],6 K( H0 o! ~+ R8 y" E6 V
                  dtype='datetime64[ns]', freq='B')
    $ t" |2 ?. Y; a: z: @) z- Y& `6 }2 V+ p
    pd.date_range('20200101','20200201', freq='W-MON') # 周一7 I: E7 h5 T, q$ v! M
    Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    8 x  S" H! G2 i
    # {$ j1 P% C) x$ H$ R! z; D4 xpd.date_range('20200101','20200201',
    4 p$ [& D" E; }, E! F              freq='WOM-1MON') # 每月第一个周一
    ; _- O7 D) K9 c2 t- a3 s, }' m: A3 u) T) @6 _) ?1 V- M" N% L0 l
    Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')3 R9 Q* ~) t6 {9 I; t

    + ?  g' s) s% w( d( J' m1
    : |. n/ M9 }# m8 o, @0 B+ M2
    + t8 o5 Y" t- X- y! M' h2 o3) S7 T3 s+ N& H, F, T/ U
    4
    " Q4 E. Q+ O$ M! X' p+ q4 |5  ~3 r4 w3 [8 v$ X% T# {1 ~
    6
    7 V9 ?* `& b8 }2 ~, l5 a7
    ( n0 K: |2 r$ {- Q8
    ) l; d: [) X- o# O9( h: |) s+ V/ ?1 C, c: A5 ^7 \- y
    10& Y% B$ |3 e  l$ N4 t7 L
    11
    # V$ K, @8 C3 i* p12
    & g$ F' c) L' c3 b# i13
    $ C- o( Q, q8 a6 Q3 s" z14
    . \5 w' K; t* N/ J9 m& E+ a: ?. j. h7 l15( q9 s, L( f  k* x4 }- v
    16: D# I1 y% I: g' t
    17
    " L8 B' P7 U) P( P7 W18! U, ?/ {% l- Q. ]
    19
    3 C0 s. u* h  A上面的这些字符串,等价于使用如下的 Offset 对象:
    & x5 P5 ^4 Y  r' {) x1 l1 x2 p$ _) N" z. r( y
    pd.date_range('20200101','20200331',
    3 J5 i+ c9 Z# G, p7 r4 U' ^0 P4 }              freq=pd.offsets.MonthBegin()): _7 n0 Y9 Q. y- ?

    $ ?6 \2 v+ d- e- p% E  H+ YOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')' \5 t8 q) J5 g- q2 |
    9 l- m0 x; v2 D/ e1 K$ [
    pd.date_range('20200101','20200331',( y' l' p* `7 W% M
                  freq=pd.offsets.MonthEnd())9 Y4 S& {. V8 a0 S
    : H3 D7 O1 T, D8 m- o& S& n
    Out[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    , a/ a4 W9 G4 b) d) w
    ' S. c' C4 P. `4 Hpd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    & e- ]0 M- T, sOut[98]:
    : Q6 V/ a! `; v) ]0 q3 eDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    5 b# I' R' v7 s8 v: i               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    ( p7 r$ |3 h7 H7 A0 P              dtype='datetime64[ns]', freq='B')+ V& A8 `( {) F: [1 {% n4 e

    3 Y; v! {5 `! Q" y" N7 J0 ^4 v+ Fpd.date_range('20200101','20200201',
    ; J% A+ s6 t' q7 k" p# `              freq=pd.offsets.CDay(weekmask='Mon'))
    5 O& S) v; ~2 x# X$ _( k! i; j3 O) Y( Z5 J
    Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')7 u$ I( j+ W2 }
    ( Y4 ^: E0 ]4 G+ e
    pd.date_range('20200101','20200201',# \8 q( [! a  u4 D. X, C; ~8 E
                  freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
    % l! n& k- f! r& e3 h; n0 A
    ) B  p$ G0 n8 W. g  XOut[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')  p. l1 h' t4 l4 {

    ! F8 L% ^( J7 v7 r# a5 l1
    7 D7 @# Y$ d1 S& V2
    ! ]0 G2 C# n/ o( `32 f; `) g- z' ^! L1 Z
    4
    ' O! S/ I+ D% {5% {; `" l1 H+ d. a0 H8 e
    6
    , A1 m" T+ e0 x7: R1 V, ?" N8 Y
    8
    ( N: r  z) N7 H$ B8 \9
    : k. D, o3 O' o& Z* M10
    0 c) C- o4 s2 T4 Y0 X: B11
    ( `. X7 Z: W% S. d. n# E  s; |12
    1 }4 F3 I. F  t, c$ W9 T2 z13
    % y: |0 C- g5 b14: L5 e  R! T- P5 K) X
    15
    7 t& f* u$ d" ~) P2 }16; M. }& @' b2 K+ I+ |1 [, G" H  `
    17( \" B2 `5 Y% A3 Z1 y  \
    183 o, A* G8 Q' G) u3 x% f, f( |
    19
      T: ^" k& i+ z9 ?20" @. h  j* D. X' W
    21
    ' m' U# Q4 N" }% v  J5 _22
    1 P. M/ O; r# T" R' d23
    ! E4 K# x) k+ ~$ F, ]0 {+ Y1 O% t24, C" U. O2 @9 T1 l9 N+ H
    25
    7 e8 W$ G; s' d' S2 q( k& z9 c【CAUTION】关于时区问题的说明
    3 x  `/ J) i! x9 C+ K1 y3 }  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。# |4 T9 H; t( n: T* d* N6 e( r
    7 e% i; u& j# ^& ?  u( m% o& f
    10.5、时序中的滑窗与分组
    0 t+ q* l% i& f3 Y/ ]10.5.1 滑动窗口) E6 G2 S7 M  o/ I
      所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:' B+ s) C: D5 Y1 A3 v

    1 A. S" r' D( x6 j1 x8 \" d! Kimport matplotlib.pyplot as plt
    5 a. O/ ^9 X/ E% |6 Tidx = pd.date_range('20200101', '20201231', freq='B')
    9 n2 B( d/ ], W4 ~; S+ cnp.random.seed(2020)4 x" c/ U! J! d1 `% h. t5 E
    & S# ~; i9 N+ Y7 M
    data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加" T7 c4 v, j$ G6 R: J5 z
    s = pd.Series(data,index=idx)+ p9 b0 E" S# K$ j! R0 c
    s.head()" T- T: N- h& e4 ~7 X3 o
    Out[106]: . y8 R- e# {4 f3 V
    2020-01-01   -1
    6 n5 r* c! e1 _/ M& a  P* e2020-01-02   -2
    * H( ]* _2 m' ?/ ^0 I  n5 g2020-01-03   -1
    2 T3 n$ c" f9 H2020-01-06   -1
    % h/ g$ ?, X: A( e2020-01-07   -2
    6 E7 K) G0 M# J5 e0 c; ]5 {" PFreq: B, dtype: int32
    + d$ U: x: Q0 B, M$ Y: Kr = s.rolling('30D')# rolling可以指定freq或者offset对象
    % F0 G+ B: Q# G/ x9 A( @& ?
      _9 N  |4 b3 y/ x+ w7 `6 a* d  Nplt.plot(s) # 蓝色线0 h# k  U; r' h1 c& U  W# v  u/ q1 Z
    Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]5 B5 _, @- i: k" J$ A8 x3 c7 }  e& h
    plt.title('BOLL LINES')8 G2 X) B7 x* N$ b# _+ I( y
    Out[109]: Text(0.5, 1.0, 'BOLL LINES')
    ; }" `: f3 T0 l) z/ i5 r! i7 q; z) H3 T+ n$ i4 T8 Y9 l
    plt.plot(r.mean()) #橙色线( q# G8 t$ C) B/ W& L. S
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>], ~% P: W2 z( w$ |
    $ c" l, z* [- J9 R' w2 w
    plt.plot(r.mean()+r.std()*2) # 绿色线2 p/ e8 Y0 [* f( M
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    ; [: u) K8 u3 k5 T. s. U) O; i1 G! W7 ~5 i
    plt.plot(r.mean()-r.std()*2) # 红色线
    / n2 A  v: |$ J) ]  FOut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
    0 O& X: ]. {4 L  y
    . Q3 U: e9 y8 P% ]) S1
    # N+ y$ N' G! N" r. H2
    4 ^3 U: x5 @8 e* M2 j! ^2 C3
    1 O2 r& ?! w( y0 S5 e4' X; s8 O; Z0 k; m: B6 d# o! v
    5" l# h, k5 O8 \
    60 u* F* h$ Q" ^6 c8 V4 n
    7# n' W8 R; r3 [+ F
    8
    - w( C$ V; `5 x* D. [) T/ S6 ^9+ l9 V  u; V) U) w
    105 `- @$ g7 G+ ?# ~1 l: ?
    11( P( \* f( r' @) F2 N
    12  y% x1 D8 W' s: I3 Q; U
    13* ]6 w+ q8 v2 y0 L+ B% A
    14
    $ U! _6 o  y$ G" a3 K, E6 {15
    ( g! n+ W  V1 b. g, V6 i7 D5 h" ~16
    . r9 e0 r% f; x% R% H17
    $ I6 ]5 l4 [5 P0 t1 T  o180 M) S! k) l2 s' b: X
    19- k& p- s( B+ e+ Q
    20, c4 A& {# a* k% }  K: J! E5 m
    21* E( P3 R  `* L. f3 {# z3 x
    22; x, V- u' h6 b
    23
    8 l" S0 O( m# F* z  k; n24  |3 s2 G" E, @0 u
    25* q5 {* [7 o) Z$ u! G) Y2 F6 h
    26. N$ g) a! `- h8 S; _+ Z% O
    27
    ( j. I2 j$ |' h; q5 D% J9 N* L; f6 y8 U285 K3 `1 _& J; u6 ]" z) ~5 ?' m
    29
    2 G; z& V+ z5 p7 [5 B
    . s* k8 p4 _  _8 w   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。
    " M+ i/ J5 L& n8 f/ q; f: V7 X( f' ^   首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。1 i. {7 q# I) s5 [
    8 Z/ C0 [5 d, W. {. \
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]
    7 Q* S8 J5 A. Bbday_sum=select_bday.rolling(7,min_periods=1).sum()+ ]; k$ Z+ W$ r
    result=bday_sum.reindex().ffill()+ I; x& ]5 x5 B+ u: S" c
    result) o! g) K3 P% [

    - l/ F. o/ U9 L: B2020-01-01     -1.00 t: I; U4 z& |' f! p. q( p
    2020-01-02     -3.0. O# J. K' n& N
    2020-01-03     -4.04 e5 g* p1 R. Z
    2020-01-06     -5.06 X- z$ V" x3 S. n
    2020-01-07     -7.0. V3 y+ w1 K: B" Q9 l8 h
                  ...  1 n# ^! [; z0 E0 ~
    2020-12-25    136.0
    / X8 E$ _6 d! K4 z2020-12-28    133.0$ I8 {2 m4 l2 J
    2020-12-29    131.0
    " }$ U, ]  |7 m. V' G+ A" w2020-12-30    130.0
    , q( {. S# B: o0 e- B% H; O2020-12-31    128.0
    4 L5 C. O+ t  s4 {, T+ IFreq: B, Length: 262, dtype: float641 ~' v2 j( q, x6 r! h/ A$ u( w; e' ~- b

    - m  `1 n$ ^6 e5 N1
    7 \+ b( b9 B5 U: H/ _24 `0 a6 B3 R% ?, h& S
    3* F5 E. T1 j" d7 m
    4# o  Q; a; A8 w
    5
    * v, D& R/ d% O) Q1 W& k; n9 H4 [2 }8 m6
    : C, Y. T; V0 e* o. {9 `$ C7, a# N! l  H1 D1 f& L, ~7 E& h, \1 q
    8# Y, d4 s, G4 V1 ]' q1 K- s
    91 a8 S+ e% ^: A" J
    10
    ( n; e  S, T0 c0 a" \( [11
    + s3 H3 C/ ^1 ]; l5 D4 Z0 C120 B/ }# h& ?1 V: J9 n
    13; O! E' ]+ W, H
    14
    1 T( h5 P6 g5 V# w' i, U15
    4 a3 C1 }8 w- q: P4 r6 d16
    5 s* A; q) C/ v6 }4 x17
    ' b: v: @; m9 {% Z' O# b  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。4 F3 l0 q" B9 d, u! ~
    + z. x( G& v; }* M+ }- Z. E
      对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:6 p8 j3 n( K) e$ G0 A  ~/ d* j9 I
    9 d% ]3 I5 ~$ a! L0 j
    s.shift(freq='50D').head()
    & ]: V* v' N: sOut[113]:
    , m3 E# a, l! Z& i9 B2020-02-20   -1. U5 s3 T5 }& ?1 ^; `9 u
    2020-02-21   -2; n) S% I' Y# ^1 c" A( i
    2020-02-22   -1# J+ ~8 E" K1 S" v8 A+ F* u
    2020-02-25   -1
    , H4 b5 S& r5 \% T4 j# ?5 K& R, x2020-02-26   -2
    - Y# F7 }6 ?4 }( M3 _dtype: int32% \; z+ B8 j" K+ B' F
    1. o5 S# D8 `. c: u' z: Y% T
    2
    7 S& r# n5 e: |: X; \  W" N* Q4 V3
    # D- \0 Q) z& n4
    ; F0 o+ e/ {. w; W5 K5
    6 {0 y( z" e1 f% j! B. f6
    8 l/ {/ G$ `* E2 e- J7
    - C- G( i0 e* _2 |( S+ E8
    0 J9 g, }" V$ v) ?  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    8 k6 f3 \! Z: U1 x
    # S* S/ s- r" f) X/ rmy_series = pd.Series(s.index)- m/ k( |: v' c# L
    my_series.head()
    % r) \- N, K* |9 R: s; iOut[115]: + ~$ ?  _( Q' N- m8 w
    0   2020-01-01! f4 c2 q- e/ M' w2 a' x- d; N
    1   2020-01-02
    + J; q1 \  r7 U2   2020-01-03" [' X) W8 ]# F# F; d
    3   2020-01-06
    ) R6 E0 Q: e; b8 }4   2020-01-07
    + Q3 p: I/ h" l( Gdtype: datetime64[ns]: f: E9 B9 O9 n6 _

    # d1 y( ~8 b! R& M$ Qmy_series.diff(1).head()
    8 L% f; W' y+ mOut[116]: ' B+ j; ~: x* P; ]8 J5 k
    0      NaT
    , t/ f( }9 a+ q1   1 days  q+ H3 ]- S) f5 r; m" a4 G
    2   1 days
    4 |9 k! G$ E2 y. Y! c4 `3   3 days
    . \& P# U+ q( W5 ~  ?4   1 days
    6 I4 t1 g/ f4 @8 udtype: timedelta64[ns]
    ( d2 @- J8 R+ x$ j- z
    3 I# v( a0 {  b1 N: a. k1 d$ N1  g) I" N+ n7 Z
    2
    * F  i* r4 o9 \! g- f37 O, v: _7 D- C- h! P: a
    4
    2 [! O* o% p2 `$ y% k8 h/ o54 v1 C" n& j. b7 u  i( @
    6( `7 L) X3 P2 R. f7 G& u
    7& R5 _6 Y" b; s, k' [  k- o4 A
    8, S# T8 L* ^7 w# t$ C1 ]) y
    9
    5 j) d9 j2 }1 O/ d- o; w2 Z10
    2 P! {2 \  B$ R7 D& b, O11
    3 }' I1 K# Q  ~! |2 P12
    - W+ O/ U; `% W! r4 P8 m. c! O13) E) {! n8 E6 p9 ]) x5 T
    14
    1 O) i) ?- F. d1 U151 L& ?. [6 H! J' }# L
    16: ?; Y4 S6 K9 S# F" y+ C
    17
    . c1 i. K. p! T& W% ^181 C  m1 B9 [& Y& D1 e, m3 W
    10.5.2 重采样9 i1 t1 H7 X; b
      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)* L4 ^9 K9 c" l$ L3 f, f* G/ Y/ o
    常用参数有:
    ( _/ d, ?+ X; z
    ; a7 M( x0 N5 \4 J2 R$ Wrule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    2 X* B( [6 L8 ~7 H3 U# A" w; Waxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样# }+ Q3 d$ u6 w( @9 i
    closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。6 {8 t( m0 w7 [: ~3 p
    label:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。6 D* j- `9 Y  T
    convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。- a4 T  n& i, \- X; U
    on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。6 w1 U5 Y1 C6 l( a3 C$ M
    level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
    ; f! O3 r( M# T5 \& Z+ \" n  iorigin参数有5种取值:9 l, G  M. d" U6 D5 t4 c+ s' D
    ‘epoch’:从 1970-01-01开始算起$ \4 X4 q3 \& X, g2 p& C) U' }% z
    ‘start’:原点是时间序列的第一个值
    + b' g8 \% K- e, O4 D4 I‘start_day’:默认值,表示原点是时间序列第一天的午夜。& B' l+ b8 e! R, @8 R5 d6 E) ]1 E2 k8 o3 E
    'end':原点是时间序列的最后一个值(1.3.0版本才有)
    0 x) }& A7 `3 G* F# _, ^8 M‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)2 P. z: v2 M3 T& W
    offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。2 I7 h7 X! e' T  i* n- K+ w8 y9 v
      closed和计算有关,label和显示有关,closed才有开闭。
    / O- J2 g1 p& n: d: H: l# j0 w" b  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。+ ?2 P$ m; r+ a$ K) `
    3 l# m' K4 O/ T) L, d& s
    重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:
    % m9 y! @( ~: as.resample('10D').mean().head()
    ) m6 }" s- [! m: a; qOut[117]: ) N. k  a; T! y2 K% }' f# p
    2020-01-01   -2.000000% F5 C0 q$ V! `0 m% m, D" L
    2020-01-11   -3.166667
    ; b; X% z( Y% \  D2 F6 ^4 e2020-01-21   -3.625000
    2 Q! k" D/ O+ i, D5 I2020-01-31   -4.000000
    8 n6 z; k4 `, Z7 {2020-02-10   -0.375000% Z9 C7 w7 J% i. ?' x7 G
    Freq: 10D, dtype: float64  b, C6 d* p& k/ l! d6 s. D9 g
    1
      Z: A3 u0 j9 f9 H2
    " v3 Q) `) t5 Y& D# c  ]3
    1 h% `. k7 [, y3 q7 o# ~" ~: [4
    4 r3 ]- n- h9 w% K5& x5 d+ l0 c# o* F0 w" z6 g$ }6 m
    6
    1 ]) l* h* u% `7( U; O+ }/ |4 R0 ^6 m2 i  X
    8
    , I5 {1 u# o- ]' u1 m可以通过apply方法自定义处理函数:
    * g1 G4 F# y; O3 E+ Rs.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差% g9 H$ n9 j( _- b% Q. Q- S" t% ]

    5 Y* K4 Y; U5 q% vOut[118]: : f$ |; D7 C+ x3 B8 A
    2020-01-01    3
    * o9 D. ]" F$ I, m; ?2020-01-11    4
    6 G; {$ n/ R9 V% s7 r+ |2020-01-21    46 N5 v& ~/ S7 d# e4 E: S5 Z
    2020-01-31    2
    4 d5 Y2 O$ R, f3 r3 B5 T) w. O2020-02-10    4, V! V3 j: x/ m" r6 s
    Freq: 10D, dtype: int32: P8 I2 P) ]7 H5 G4 C; i9 F( Y
    1. _  S( P( a/ h$ Q; X2 f/ J6 y
    2; S9 s6 U; P5 C. i
    3
    " L% F  ^( B. H; j" j+ E6 [) f41 p+ l. u# K" V+ w
    59 a/ v! \& d/ p5 f" M& ?, ]% \( p
    6( R4 {; W1 N; I' u4 R& n, k7 v
    7$ M  E3 p8 o9 y. M+ e: o% b
    8  w5 f" z7 R- ~6 ]5 v  d
    9- F, t2 T' \1 `$ ~/ |$ f
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:
    / V( R" y  B2 L) B
    ' z1 J% I( x5 w2 B3 \- a. G+ iidx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')4 F1 R( N9 O, L9 {
    data = np.random.randint(-1,2,len(idx)).cumsum()7 y8 P. f/ _9 G1 [( t
    s = pd.Series(data,index=idx)7 w# p, {+ q  X6 [& [- A8 t1 z
    s.head()
    7 @8 x: L2 ~+ Z. w+ M; L, c5 Q5 }8 e& S" Y" m. o$ _8 q% W' w% p
    Out[122]: 0 b. f7 Y6 U% f* w
    2020-01-01 08:26:35   -1
    6 u/ P" p( O6 ?8 L3 E2020-01-01 08:27:52   -1
    & E: N2 h) ?9 C" u' W" o; [2020-01-01 08:29:09   -29 d* s* r4 M! Y* {: ^. w. J
    2020-01-01 08:30:26   -3
    ! d# F  r  t+ z2 z: Y( ]/ ^2020-01-01 08:31:43   -45 G# y; A- C! D
    Freq: 77S, dtype: int321 E5 p/ j. z" D% c, \8 K6 s* h, }
    18 u6 z; Z) |) E6 ^
    2
    2 @! ?! i' v1 W. Z3
    , A. q4 Q' L/ I% A% k  V4
    - A6 w4 ]/ Q( s  r* k+ J8 d8 M+ \5, q* [1 |* k' Z5 a1 q2 \; G
    6
    1 q# k/ W  ~- D- _73 e% e" ~1 `% s1 @6 O- n- `
    84 a5 j6 y4 T9 _% [& G
    9: M6 m$ u6 Q" @% d* b8 S' A
    10) U, H$ R% H. j8 q
    11
    # @9 A$ \* J6 l4 r$ t; R! l+ b12
    9 S  [  h; Y% v  下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
    6 T+ ~4 v- F5 E0 w- g9 K, E$ D! }5 i, e, V! {5 a) r
    s.resample('7min').mean().head()
    ' ]2 }3 d, `& O, KOut[123]: % G9 {* Q5 U; [+ {
    2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值
    , X! h/ n7 e! H: t- Y. p$ l% l7 i2 n2020-01-01 08:31:00   -2.600000
    : e) e; \. B. b& @8 `$ {! h2020-01-01 08:38:00   -2.166667
    ' o+ V$ W# Z! R1 B, r2020-01-01 08:45:00    0.200000. s% q; a. h, Z# J- c
    2020-01-01 08:52:00    2.833333
    4 r7 L3 p- i8 f" {9 q- e& X7 hFreq: 7T, dtype: float64
    2 e3 e9 S# B, `# n- V' \1 v* c1
    1 x; v0 d% B; ~. W2
    + R; N0 w0 A$ W3
    ! N& B  N- s& M$ ~4) L' ~" N7 _7 s2 g* V/ T
    5
    ( C! P, O" t7 f4 L" K4 H; ^2 _5 J+ z6
    ; `" l8 v/ o* U5 `7
    . i, O/ w/ _, q4 O0 g' U3 H9 r, Z8* f: |1 A8 y0 Z, o2 b$ l
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:
    : L3 j7 m% k! g% h! m6 {) R. Z" y  ~( Z5 Z; X+ [
    s.resample('7min', origin='start').mean().head()
    1 e4 k5 Q! ?% J; \Out[124]: 1 W( w8 l. ~3 e3 w! i. `1 z
    2020-01-01 08:26:35   -2.333333
    - ?9 ]: P) A  D$ O2020-01-01 08:33:35   -2.4000002 G0 a9 |/ u& I0 a6 B7 [( g, E5 r
    2020-01-01 08:40:35   -1.333333+ W) {/ K# t5 M4 L* a8 f& a: H
    2020-01-01 08:47:35    1.2000006 \( a) r0 u1 r" U1 b9 o( D  U2 w
    2020-01-01 08:54:35    3.166667% o4 y( i/ B8 v+ e
    Freq: 7T, dtype: float64
    8 S; M2 h9 u! B7 g10 L4 `8 v* X" B
    2: p9 \- `* |& }( d+ r1 e/ @
    35 b( g1 {# e+ J% }# l
    4+ e4 z& k, B1 N; I  _3 H5 V
    5
    $ ]: P2 a6 }5 L) l* ~1 v9 V. u+ D: v# L7 b6
    : t! N: D& J( c. a7* ~5 ~0 N9 ]( }2 X
    81 E# L( ]2 ^7 K' W$ Y9 X7 L
      在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
    1 E! w, Z$ G+ o! x- N7 o- D% j7 P
    ! Q$ U5 k0 a( p0 g) G4 x! f- ~s = pd.Series(np.random.randint(2,size=366),
    ! v. K7 {1 y/ b, L! D! w8 U$ c- z              index=pd.date_range('2020-01-01',
    8 r  B% ?, k! N: [  e                                  '2020-12-31'))8 U$ j" Z+ N) g3 s" N/ H+ p) `  C
    0 o4 L$ _2 f* c3 b+ q$ `
    5 S, W8 W( Q* F' b0 z& K8 ~
    s.resample('M').mean().head()7 {9 X) p8 g2 k( O/ S
    Out[126]: 9 x* f( h" V# u0 e# q0 p6 C" j
    2020-01-31    0.451613
    2 A  D3 V( g2 e! F2020-02-29    0.448276( I+ k: y$ M, C7 v" B
    2020-03-31    0.516129+ l' ]( G, n( i+ T7 }: P2 z7 k
    2020-04-30    0.566667
    / ~3 J' P" C% T* e; l; `1 X( Z2020-05-31    0.4516136 m- v4 a# C7 f, d! s
    Freq: M, dtype: float644 K( U; h9 V8 L

    , P/ ~8 v/ |* X( l+ b. As.resample('MS').mean().head() # 结果一样,但索引是跟正常一样) S8 Q! l; k  D- ?8 J* g
    Out[127]: 6 X' Z' w' x" f9 M) O
    2020-01-01    0.451613
    4 V, c- r! ]3 R+ Y( g' i2020-02-01    0.448276
    0 b& b% S; G7 |' M2020-03-01    0.516129
    ! H4 N$ ?! k% o6 u2020-04-01    0.566667
    + j6 g; t' ?6 _3 R2020-05-01    0.451613
    5 O( R2 [! A, b+ z7 A" Y# `Freq: MS, dtype: float64
    4 s4 d+ a8 L, _
    ) q6 d: f5 {/ e( z& t1
    8 V( |! `4 o3 k2
    # ]: @! s+ H$ S1 X$ N3) M: m% o. ?( \+ m
    4# j2 ?6 N) x/ c/ M) }( Q
    5
    ) m( q; M; c- {6 R1 Z. W/ f2 M6 L6
    $ W% D; ]1 s" d, q& n6 q7
    7 \/ A2 A, d9 F0 ^, {4 H* v, C8
    9 |: s6 u( g4 I" N5 x8 y3 B95 W" J! P* a! k1 n; Z
    10
      C7 p; s+ m7 P$ t/ L! E113 w+ E, e$ d2 F; y
    129 h7 R. D" o" g: \. F/ B
    13
    - R1 C9 v, G3 S/ ~1 A* A14
    3 d3 r6 G$ H6 s! Y15) M4 a8 R- M0 `  I5 H) [
    16
    9 w$ n7 x1 W" y, E* x3 ]5 M17! W0 D/ P# _; U8 T) z
    18
    3 Z% h* L9 I) W197 I/ h, j8 |8 ]
    207 F$ S6 s% V6 K5 N
    213 p( |6 s  ]5 i- O: Y
    220 k( D* F, u4 ?
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
    + y: E" x) x, X' m* @d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],$ K4 e& y, r( e. c% u2 l+ h
         'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    + s' s0 |8 Z8 L( `- t6 _( ^& Jdf = pd.DataFrame(d)( a$ a/ f: M1 u/ l/ n: [& x% H
    df['week_starting'] = pd.date_range('01/01/2018',/ l$ ?9 [7 B6 @9 U  |, O
                                        periods=8,7 |: m2 G- L( ~# X7 b. z" |& t0 y4 i1 e; R
                                        freq='W')) K" _( l( g" G" W* a. H  B1 _
    df# S9 }: @5 j7 s- S3 K" D
       price  volume week_starting2 t8 l- ]0 s. @( B& s8 }
    0     10      50    2018-01-07* a# Y( L. a/ B4 g
    1     11      60    2018-01-14. [3 H$ z; N& N% G
    2      9      40    2018-01-21
    0 `- x% N& G6 o  c3     13     100    2018-01-282 v9 z7 l. @3 [; e0 l
    4     14      50    2018-02-04
    ) Y4 N5 k% f) I5     18     100    2018-02-11
    / H2 C- l! m0 o/ J/ ]6     17      40    2018-02-18) f+ A" E7 J( R% R( E
    7     19      50    2018-02-25
    ) R, H% p5 }' Y: t! vdf.resample('M', on='week_starting').mean()
    ( C* u7 y, P; _, a& _               price  volume
    + ?6 ]8 V8 J/ a/ h9 K3 \2 kweek_starting
    3 o0 K7 z* _$ @* m6 v- j) }; ~  g, w% r2018-01-31     10.75    62.5
    $ {+ M7 q7 l4 |, _2018-02-28     17.00    60.0% D5 W  g% N9 O0 |, {% l; s

    ; R2 \( _+ S: R: I* p  K1' a0 N3 s# J& w. @" J  q
    2
    3 E& c# |1 D$ \" M- O- H3
    - o7 k, {. ]+ P: ^4
    . m3 K* k+ c' `3 P' Y- O5
    8 T9 e0 O! b/ t9 W+ ?6
      H7 B% s3 w- I; {( ?& @5 f7( |7 w, j% f9 B6 ?- |+ J0 x
    85 T3 L. k* j" p5 T7 M2 ~" ~. b- G
    9$ [, ^( A! L9 f3 x0 }+ Z2 [
    10
    6 C. z- B8 r: _7 ?11
    2 [  ^' I7 z# \0 O5 V. D2 i( u12
    ( ^' {3 m0 q) |# R! A9 J! ?13* o, Z' m# G8 z
    14
    $ Q( S; N0 k1 J) ~( w( ~15( |/ \! [3 V0 i/ n* E& j+ T
    16( p3 [# S$ L7 x6 M8 S, U; y
    17% L* J' D/ n0 r' ]
    18; i( z; E7 V% T/ X. b4 \  ^" L$ j( d
    19- H2 i( X9 o( e$ x  j
    20
    0 j0 S+ N" s6 B5 Z1 \( J: H21; n5 J& [% e* p
    对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。. r4 K  v' H- t# t5 {+ E) }, {1 ~
    days = pd.date_range('1/1/2000', periods=4, freq='D')
    2 V: B9 K1 m. ?1 S$ Ud2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    - S9 j" x* {8 t# V# C0 J; O8 A+ q      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    ! T' ~0 b5 X. {9 k2 u5 M% Q+ idf2 = pd.DataFrame(
    9 B9 |' {; V7 K' z' {6 l' Z    d2,
    ; H6 _4 c+ I; Y3 j    index=pd.MultiIndex.from_product(  `7 i$ w* d1 n( c4 C% e, j% x
            [days, ['morning', 'afternoon']]
    1 M. O" Z! y. b- A- J2 I    )7 r3 a6 B9 ]$ a) ?3 {' i/ F
    )
    8 F/ f4 z" q/ k& P3 b2 g9 Qdf2" b. v8 u5 t! X6 h; K$ ^2 h& @4 U
                          price  volume
    " e0 r( L7 S1 X* F5 w2000-01-01 morning       10      50( y5 p6 v$ W: W+ a3 Z
               afternoon     11      60
    2 J' X( Z+ \+ Z2 U# O! O2000-01-02 morning        9      40
    % b" I. d: n0 _3 _           afternoon     13     100% ]" {* V: C0 L! i
    2000-01-03 morning       14      50
    8 z5 ]$ ]. x3 H' M           afternoon     18     100
    , M. Q8 G4 x7 R/ m2000-01-04 morning       17      40
    1 R  g6 y7 [1 Y           afternoon     19      50! B# o3 d' Z/ D: s5 o) c
    df2.resample('D', level=0).sum()
    5 ^- w9 i* V9 [. O6 ^            price  volume: g/ t" T1 ~7 ?# B  q2 E' `
    2000-01-01     21     110
      |$ H1 C1 U5 m! d$ s" T! o: ?2000-01-02     22     140
    0 M% [) U; Y8 S2000-01-03     32     150! W$ {7 n' f3 D2 K
    2000-01-04     36      90
    # e2 z/ x( m2 ?4 F9 ^8 [/ x) u: R% ?
    11 L2 a+ Q+ Q# q. E
    2% i& \3 R' ?' `) C2 _
    3
    . A9 K* ^. }7 E5 l2 {1 h  s7 w4
    0 u$ m2 Y6 m6 F5
    : w" q  {9 W+ P2 D69 B0 O' x1 Q3 w! v: ~
    7& @# b+ W' Z9 t
    82 Z  |$ Q% C7 ^/ `) w
    9
    $ y, I! m; R5 w& R" ^10
    + Z& u* T, t( Q7 D+ I* [114 R" r* z/ k. o2 g
    12& P% @! w, z% e: x
    13
    1 R+ x" G& u7 k4 q/ T, d14
    , p7 z2 K1 G  B" }* I3 x154 ^. a0 c7 l- a* V
    16
    " D2 W/ v) K( u; Q17
    2 D: F: u) o6 \( b$ {18
    / i& E8 h" U7 s" z9 N) Y6 L$ E8 O19# D1 \1 j. r5 f! |7 }7 T
    205 p8 U  G' a3 F1 f& S
    21
    8 g" L( E1 k2 \& |( I; a( G9 ]' v22$ c; P! @& W6 [/ B$ v' D( v" P
    23, m: }, Z0 J) Q% C
    24
    8 m3 g" W6 N) [% T25
    , r6 @. p3 P& I" R7 O根据固定时间戳调整 bin 的开始:
    1 `. d3 N8 g: V( i: bstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
    * K- K; K; ?3 a. x! D) [8 F7 @; Y$ P2 ]rng = pd.date_range(start, end, freq='7min')
    - M% p& z8 k) h' X+ g0 [- Its = pd.Series(np.arange(len(rng)) * 3, index=rng)
    . R* Z( s. B+ A2 n2 q. k7 ~ts
    $ m0 j' }0 a. q, |2000-10-01 23:30:00     0
    & W! |9 X3 L" c0 s# u/ p2000-10-01 23:37:00     3
    ( m1 T2 g* b4 v2 ?8 G7 E2000-10-01 23:44:00     6( x& n; i1 }- Z4 _- F- z
    2000-10-01 23:51:00     9
    % L( Z( f! V# Z  l2000-10-01 23:58:00    12+ k9 o4 U0 |7 {) r/ W& e$ _
    2000-10-02 00:05:00    15. Z8 V: h8 y7 Y  [$ N4 H
    2000-10-02 00:12:00    18
      s8 X' \( o. P3 [. A( L5 A. p& Q2000-10-02 00:19:00    21
    ' k* X0 ?' y" [6 P. V5 a! E: A2000-10-02 00:26:00    24
    " F/ U+ U! `" }; B: w0 T  B3 yFreq: 7T, dtype: int64
    " H4 X$ K  L; D: Z% ?
    7 [. ?9 E2 W- J; ]3 @ts.resample('17min').sum()
    0 |6 j4 P. D3 ^3 v- s7 F2000-10-01 23:14:00     0
    2 v* @& j+ j7 b2000-10-01 23:31:00     97 r$ f, V7 I( A  K% D% k! U
    2000-10-01 23:48:00    214 G  J, C6 R' L  `6 \
    2000-10-02 00:05:00    54
    6 b% E5 l: M1 {1 m2000-10-02 00:22:00    24! K! M2 e; E& N+ L" |
    Freq: 17T, dtype: int64
      T1 I5 A5 k1 q9 L
    9 A( G+ n7 L/ }) ats.resample('17min', origin='epoch').sum()
    1 q5 _; l. [* N9 o9 K. q2000-10-01 23:18:00     0
    % h. H! i# _9 t8 C/ R2000-10-01 23:35:00    18
    5 u0 j! `1 C6 i# L' _6 C6 C2000-10-01 23:52:00    278 d+ A* a' r. ~4 Y' Z! j( J/ O
    2000-10-02 00:09:00    39
    % y5 b+ ?$ J! N& m  s$ X! {2000-10-02 00:26:00    24
    & C8 r; o7 R7 P" n5 R9 y% Y5 pFreq: 17T, dtype: int640 k1 U! h/ @5 r6 q+ y
    ) c3 _9 q' b; q1 k5 \
    ts.resample('17min', origin='2000-01-01').sum()
    3 }1 K  p4 ], J: H7 @2 u2 l2000-10-01 23:24:00     37 c: p1 f# ]1 l8 w. U2 W  w
    2000-10-01 23:41:00    15- y$ H+ b3 v' o7 ^
    2000-10-01 23:58:00    45
    9 \" T. v( x4 N8 M* b6 M2000-10-02 00:15:00    459 d+ ^+ {) {7 e" |
    Freq: 17T, dtype: int64
    8 z7 B+ o% y9 ]
    2 Q" |) ~1 c3 e' H3 |3 H6 f0 i6 P7 b10 X9 y; G1 d0 G
    23 t; G% z  F+ F8 Q. z
    32 f' l9 v) \4 W% v
    4
    / f' W  N. j( }6 E  f# H4 l& u1 p3 l5
    & H5 c3 @! z7 P6 P1 u- `6
    $ d1 W) i3 d5 {9 ]0 \0 |0 j7
    9 W& c- Z2 V" x84 D% l$ T& d9 Y9 o7 N0 ]2 k. p- `
    9) I" l0 k5 C( {2 J
    107 b( ~0 O! z1 `
    116 W( O+ H0 S$ f9 x* S. X- k
    12: c) W& c$ C, E) j4 g7 E4 b
    13
      z2 e: K( O! ^; x% w4 n: R5 b0 s14
    4 f8 r* \0 S* W" E# X15$ a  m% y. \4 |( ?3 X9 X7 }
    16+ I+ D  B- G* u1 V, p! }
    173 v" H. Z' C% R( \( f
    18' ]5 O& ?  t" I) [* ]
    193 A# a# ^$ F5 x- {  ?. a4 L0 o  D% F* S' p
    20" [, m  g- I1 y' S
    21! O2 H* W% r" R# m6 ?  G5 F% L. P
    22+ K" X5 M" B8 Y+ D2 j
    23# Q: ~5 U. s  H+ |
    243 G; g% ]! b- |6 |
    25
    % l& r: \$ i5 T! i5 Y26
    * D" ]3 L* F) g4 r) T27, Q7 v3 j5 Y: f4 L4 N0 V
    28) c# R& C7 T' y( d/ \' M
    29
    7 j6 }6 ]$ i) w0 n+ R30
    . J) J" ]" C6 Y31
    + L% O0 _# w# v, |+ K: I32
    + Q; a; T% T- e9 l$ ]5 y( `33
    3 B5 h' J0 Q% y* o7 r0 K5 u# S34; W- ^6 k: g; Z- k
    35$ D6 g6 G, P8 V
    36
    & V' Z. ]2 h, C37% Q/ k" ~. ?) j8 o
    如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
      J+ b" S5 H( W& `6 tts.resample('17min', origin='start').sum()
    , i1 q3 v4 s. F" pts.resample('17min', offset='23h30min').sum()$ g6 C+ Y& M; c3 N
    2000-10-01 23:30:00     95 G7 _* a* w2 o$ K
    2000-10-01 23:47:00    21
    " S! ]3 M% d- S0 T5 p2000-10-02 00:04:00    54
    . z. ]/ f  z# u4 }+ v1 ~) F2000-10-02 00:21:00    24
    1 P2 c2 D7 b0 s) R; {7 GFreq: 17T, dtype: int642 \6 [/ a5 T- ?5 Q% x  Y1 i  e
    1
    * h3 R/ s8 B) G5 |7 M2
    % k% y5 Q% [+ d  K' d7 ^% ~  |5 k3
    : W; u1 k* l+ g1 P2 ^3 j  c7 C  l4! w3 Y( i+ F; L% k3 x
    5
    # ?! e" Q' @4 Z# L6
    . q( a7 @3 I7 L0 t% V5 z# H0 d7" k8 O! \9 B! T: G2 v- R# J
    10.6 练习' v* E+ v, I2 t7 b: E4 i$ Q, m
    Ex1:太阳辐射数据集, }0 P9 _  i4 W" q
    现有一份关于太阳辐射的数据集:) w8 R& V: N# ?. O6 b0 l

    5 K: @, ^# O- C8 o  @& S6 Y( \df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])# d9 x9 F3 y/ J1 X! W, s0 p
    df.head(3)  \  X5 g7 G% W) p% M& u8 `
    * q( e1 ?- }  A" `- {; D& n  R) A
    Out[129]: : F" A6 x+ r4 e. j
                        Data      Time  Radiation  Temperature
    4 e' j7 `1 o( K4 q0  9/29/2016 12:00:00 AM  23:55:26       1.21           48* G) D9 }$ y  y, s% g- Z: E
    1  9/29/2016 12:00:00 AM  23:50:23       1.21           48" h) N6 x# W) Z  J2 v
    2  9/29/2016 12:00:00 AM  23:45:26       1.23           48
    ' C6 X$ n+ c; y5 k  U1
    8 d) S4 Y% S* P0 G' X2
    8 J) h& q) @& O" h3
    2 e6 d  Z! b8 S, P4% l) Q5 M3 x3 L4 E" |" H5 |7 [& u
    5- h9 o! {0 J, v8 i* V4 t
    6
    7 J7 A4 |5 A% l7" l  o& q; X. V
    8
    * e& S. }) W2 t1 M9 v- ?" y将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    , `! j# W+ W& Z* n! G8 W0 Y每条记录时间的间隔显然并不一致,请解决如下问题:& }' s& l7 \4 t+ P/ C: l
    找出间隔时间的前三个最大值所对应的三组时间戳。* X1 J6 e* g* R/ y9 e0 x
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。+ \7 L( I1 y$ O& l' n- [( }
    求如下指标对应的Series:8 i: I" K- F* l* x5 M; }% @, b
    温度与辐射量的6小时滑动相关系数8 [: h- a1 ?& b7 J9 {
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列& f, F1 L# r: E, l4 N9 O6 F
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    , x) D8 f: j( g+ ]4 cimport numpy as np
    6 Q3 b4 B5 s. E' @3 u6 s' {9 ximport pandas as pd$ y. d3 {% v# E. s
    12 f! e. F7 X2 [
    2
    ; w' X* ?/ [4 O' I将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。/ _$ D) V4 }: H$ y
    data=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
    . Z; [4 N& o8 H7 jtimes=pd.to_timedelta(df.Time)* E3 l, t; W, x8 `- F# n5 E. h
    df.Data=data+times
    ) o# g  t+ p' C% w4 R9 h1 _6 Odel df['Time']' Y/ r) T8 t# h# |/ n% a- M( t! T' Y
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
    : a" h- T, j: _' F, W& Y# y3 ydf! r6 e; d. l- R( }/ I+ o; `
                                            Radiation        Temperature( ^3 \2 ]. q- u& j: t
    Data                , b, T' h  z7 Q- \. [/ l
    2016-09-01 00:00:08                2.58                51) D4 j! [) e( y7 g
    2016-09-01 00:05:10                2.83                512 ^& h- [2 D$ |! C
    2016-09-01 00:20:06                2.16                51
    - O8 u8 B6 j* M  E2016-09-01 00:25:05                2.21                51/ ?: ^9 Z5 R( `. u, S
    2016-09-01 00:30:09                2.25                51! h2 q5 R# R6 Q6 ]
    ...        ...        ...3 [5 m* I# x4 \! \2 @/ N
    2016-12-31 23:35:02                1.22                41: s1 e5 u" T9 E2 D  h- @4 h3 _
    2016-12-31 23:40:01                1.21                41
    3 E) d+ j' S7 G5 ]) h2 Y5 M2016-12-31 23:45:04                1.21                42! q- f$ C  F6 U" r) y$ {0 I; ^2 r
    2016-12-31 23:50:03                1.19                41- ]9 `0 K' ?$ t& Q
    2016-12-31 23:55:01                1.21                41
    , r9 k' {0 v- j' G: w
    - z. G# @" ?4 w0 J6 ?* Z' `, b1, ?1 l9 {3 V1 \) x% r9 Z9 v
    2
    0 M, w# A7 }) z, b( o3) o, I/ Y* B  ~
    4# |* }% }6 K1 K+ g& A9 {7 i
    5
    ! M5 |2 _) a: c9 ?  U; u8 H65 N4 J8 i; x! r
    7
    2 g+ O- `2 h( w! d. m$ n8" u% |0 o; J$ s
    9
      {6 s+ J0 k* U+ W% k/ r* E1 E10
    6 L: U/ r; h0 A  N2 M+ C115 b5 i7 R* r% p# {
    12
    / B! x& V/ p( D3 P# o7 l$ I  R13
    $ n. U' S, N+ ^, `2 }3 U% B14
    ' R3 ^# X3 m( |8 \5 U15
    / G# W: [8 A# G161 k0 B3 r# x0 x$ c2 P/ m
    17
    7 q' M! u: W% p( l* ~. }18
    6 `5 ^0 @9 l7 {19
    # h" J# H, V/ H1 l每条记录时间的间隔显然并不一致,请解决如下问题:+ b' k8 N7 M% F; N/ t
    找出间隔时间的前三个最大值所对应的三组时间戳。
    6 r6 l5 h5 {9 N4 d# 第一次做错了,不是找三组时间戳
    3 g2 N1 s  @5 E( eidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]* e) f! N2 z: E' f! g
    df.reset_index().Data[idxmax3,idxmax3-1]* l! u& ~! _" H6 f* l

    $ z) n1 w% U  S$ T25923   2016-12-08 11:10:42* b& u' ?. B  w8 l
    24522   2016-12-01 00:00:02& F* d1 s1 l7 f; Z4 e( e* n% w
    7417    2016-10-01 00:00:19
    % m  `. E% \9 F& ?Name: Data, dtype: datetime64[ns]
    6 k7 p, p; w" h, }1
      t0 u9 Y+ _9 Z6 M2: H' B& h2 i. O8 z) Q/ _' _- U6 ^
    3' C# W. Z. w2 q! Z
    4, `( M8 |- X/ J+ `0 f  ]& T9 v
    54 t5 [( \$ k( |* z8 m
    6
    : o" b; C- K& n1 a% k! {5 ^4 ^7
    - H# ?3 Y* ~3 g8- d) D# m# h3 T5 b- R
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]9 c- H$ ^7 `: F- Z: C- M, Z) e+ Z
    list(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
    ' d2 s5 G* A) w7 R# T4 T( u
    2 n, x- n5 E8 V, U( R2 d[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),3 o4 ?9 {( t$ G6 N* N
    (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),5 }5 M- d# ~4 ]4 ^7 b, J
    (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]* f1 P5 _3 y% ^/ N
    1
    1 s! ?& n% j, f2
    2 _/ m, p" j2 _& }7 D" r" f% _" v3& Y6 e7 M# P/ x& O6 G
    46 a: b% t4 p3 P. R8 z/ d
    5+ D) p% {" e. B( C. j: h8 U
    61 k- [( j1 B- O1 f# \* K8 X# u$ p
    参考答案:& p) w+ n- w% T

    5 d3 f5 G+ t  G0 b$ P4 y8 us = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()' w) N9 j% z& ]: ?  y
    max_3 = s.nlargest(3).index
    $ _' @3 n* e! {2 j" I" [( fdf.index[max_3.union(max_3-1)]8 ~6 }, K* u8 c) y2 C  g9 M

    1 [! U1 d8 R" ?! V4 j, L/ yOut[215]: ; U- e/ L% }+ _% Q9 ]1 P
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',
    . D4 B2 k: O0 f, b2 I               '2016-11-29 19:05:02', '2016-12-01 00:00:02',6 t4 g3 [. s% n1 I  b8 f4 R- V' V
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],2 h7 S5 J: u+ ]* U6 M: x
                  dtype='datetime64[ns]', name='Datetime', freq=None)5 X' A! f8 a1 H3 \1 Q+ R
    1
    : F; a; s- M$ n8 w1 A! e- o22 c1 q' }  B0 _9 N3 x
    3
    , J6 f* [* _& f+ g. A46 j3 K: |) Q1 |7 o% O" r
    5. t7 d# l# `* o
    6" ?; d8 j5 _/ V  p7 J- d& \
    7
    : z, c1 T6 y! |/ ]5 e82 R' E8 e6 n5 c% T' Q  |
    94 y+ Q) g+ _: b1 m: `* U
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    $ H9 S9 v7 L2 N6 p# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间4 J8 ]9 Q  l% S  a! j
    s=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False): h3 k# ]) Q. q3 N' I
    s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)5 \9 T1 j5 T6 c" k9 A0 K1 A* a
    8 S* _# C2 k# O8 Z( F" M" z
    (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0)
    3 @/ D1 `) r; r5 O9 A$ }  o& C1
    4 {& c  e: K' u3 ]& S' T% ^/ O0 _2# Z1 H$ Q6 _- M: N$ U$ J3 x
    3
    , V3 q* ]& z5 U% K- {: c8 u! @4
    " T2 ?) U: a# R9 e5
    / d, k/ w3 p5 ?0 r6 ^! G- ]%pylab inline
    6 o3 P) [. B- D* e, M! Q/ R_ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    7 X  c" ^  o( ^( I! M$ mplt.xlabel(' Timedelta')
    " r- z/ C2 H! g2 O" d/ ]5 zplt.title(" Timedelta of solar")
    ! K. l8 i; _; t! k! Q. r$ @1
    ' D3 M1 V2 F) ]/ K2
    : ^- q/ L3 q/ f  r2 f37 P9 n8 |! E) ]
    4
    0 [$ d! Z6 H1 u1 K! l1 p3 l/ {- C5 R, g1 g9 Z
      g, w. C/ j$ h: w
    求如下指标对应的Series:1 W  u. P% \, C; _7 |1 i
    温度与辐射量的6小时滑动相关系数. K$ S/ U' w7 B! g9 ]
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    ; P, w! H5 ~9 v; Q每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)3 Y' F8 V- ~5 q+ Z# a. x. }
    df.Radiation.rolling('6H').corr(df.Temperature).tail()% z' |4 k% o2 I3 l. T7 q. O

    ! [1 `+ A* C) }) Q/ b' m. P$ cData
      `* L/ E$ @: C3 R# w) J) F2016-12-31 23:35:02    0.416187- ?4 u6 z5 Q/ H1 f! j
    2016-12-31 23:40:01    0.416565
    0 ?0 A8 a: w, B1 x# C7 x2 S% _2016-12-31 23:45:04    0.328574; Q: g3 ~' c& E- p; ?/ e# ~# r
    2016-12-31 23:50:03    0.261883
    ! N" z1 ?+ i  s/ h  J( e, S7 H2016-12-31 23:55:01    0.2624064 L  F' |! l1 {
    dtype: float64
    ' t) n3 u: e8 d9 S& y. U- x1& ]# @3 w* \- K( @- w, t; A
    2( {9 o" O) f" k2 z+ K0 p
    3) @3 t0 M- v' e3 ^, d$ i- {# W
    4
    " Q0 ^8 C$ @5 P5: g& E/ z9 s- W( t5 k* a5 w
    6
    ) n2 t7 x% _) E6 _0 t' _! S; l  e" N7
    * I. V  h$ u# l7 s" b8$ }' H0 m- C- t/ {* F; X8 X& Q/ Z& @/ x
    9: d6 q+ Z0 ]9 g+ H
    df['Temperature'].resample('6H',offset='3H').mean().head()0 y( W. ^7 L4 }: L

    1 B8 d0 f: T$ v, W% t- }6 xData
    3 K/ _5 c1 q8 {& a2016-08-31 21:00:00    51.2187508 R3 E8 V8 B( N' q, o  J8 t, M- `
    2016-09-01 03:00:00    50.033333
    2 M# _/ v6 o% j2 K8 U2016-09-01 09:00:00    59.379310$ j: L8 _/ B9 c' h# ]# }: x
    2016-09-01 15:00:00    57.984375  p6 q, d. }1 k) o3 R
    2016-09-01 21:00:00    51.393939
    " w5 _( @9 ~. ^0 AFreq: 6H, Name: Temperature, dtype: float646 R1 o) P5 j6 K/ Q$ L5 N5 {. `3 {
    1
    : c8 J+ D1 k. l( N* V$ l2
    & O( O9 b$ A) a; L9 t5 ]3% L( z$ q9 B# |' C0 r& W4 r2 m
    4
    ; p5 w" L+ P0 P6 O$ ~/ O5( O3 S+ N4 F1 @% G3 m' \; x+ y
    63 a5 }& Y& Y+ S5 e! Z
    7
    ( \) \! l1 y" A% L; o  z8
    + `5 Y% ?- U2 x1 n9
    % C+ a  P9 G2 r' I/ B8 n最后一题参考答案:! n, d, b+ J  A5 N+ _

    ! J( r- l# A8 e0 i5 C# 非常慢& K; v# Q* s0 a3 C) q- I
    my_dt = df.index.shift(freq='-6H')# `3 Z) S/ o( [( r8 N6 g' G) A9 W
    int_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]3 p4 h6 E& `1 [! l' ?2 ~$ v1 M  o4 U# @
    int_loc = np.array(int_loc).reshape(-1)4 f/ S5 l+ z% ^# M) R
    res = df.Radiation.iloc[int_loc]  i( U$ v2 k  ]9 n# r2 Z* D
    res.index = df.index" w/ q+ V5 B# e" V, Z3 j! ~1 V" k
    res.tail(3)8 w! W4 O% h1 ?2 ]$ E
    1
    % |5 Z% {$ |# |" K2 e8 V2# l( T0 \, D0 ]! ]
    3
    2 Y2 g1 u1 w/ A1 s$ `3 q4
    % ]8 l3 S9 G3 I: Y9 Q57 O2 U2 G3 B, f( o* w+ p3 x
    6
    2 w+ m* U. q$ Q. r+ f5 V+ u* ?7
    $ l' n! E/ i; H) a9 k- h/ Q' ~# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级) l8 V" i5 p, S' Q3 E1 _/ E( i9 g
    target = pd.DataFrame(
    : W/ f8 W) v" g/ L! x2 D    {
    2 M* J9 i; g& [( b% b4 Q        "Time": df.index.shift(freq='-6H'),
    4 Z9 y$ P" t( |" P" s( x; ^" j        "Datetime": df.index,& }& f+ B5 p* K/ R7 E8 S7 v
        }
    1 c, |. r2 P- v/ r: S  _5 P)
      R, i- `6 I* D9 h* T% j# {
    2 C8 `: A" }9 Cres = pd.merge_asof(
    & ~* u7 U5 J1 E    target,& @' J8 p. x8 y
        df.reset_index().rename(columns={"Datetime": "Time"}),
    8 R6 w3 p# W% e; W! I    left_on="Time",
      T/ X/ Q/ n  C+ w6 C2 Q  R    right_on="Time",9 p) x8 f3 R2 Z
        direction="nearest"
    9 P0 z3 X# ]" D5 r, r; G).set_index("Datetime").Radiation) N* `" A4 T" t' d; w: W

    # o& k, T' z# @$ u0 V0 T3 sres.tail(3)
    # j& U/ b, M/ g/ T- u, S1 i3 V4 b" sOut[224]:
    4 z& L: P* j+ h' r# |Datetime
    * H6 i' m0 L) q7 Q5 T2016-12-31 23:45:04    9.33
    ; x* P- k, h: s; _2016-12-31 23:50:03    8.49; \& Q9 T% h" |6 S( Y2 {
    2016-12-31 23:55:01    5.84
    ! [2 N5 u- }3 ?, ^Name: Radiation, dtype: float64% D) p. M9 d& ^/ b# v# I/ N% e& _

    6 y( q/ L8 g& x: N3 h% M! c% ?1
    * ]; A" \3 }$ j9 t" V. y: J3 @' C5 d2
    0 c/ c6 Z1 K0 F, U) q) H3
    : i6 I- n8 ~& S( m5 ]4
    & q5 Y& s9 h- d) }5
    2 h$ N: a, K7 ?" B3 v67 P, x9 l% U. Z" c  ]$ {
    7
    - h( M# s% W; e7 B; p8; `( r$ ~, M- @; n3 {
    9
    . A5 b1 |3 G; a+ b10% S1 X, H/ ]7 v- I2 a8 C2 V. d5 }
    11/ O& u6 f! L$ a7 u. E
    12
    5 b. f) ]9 u- S+ R1 E13
    & Q% j: Q/ I+ D! e) P( v4 S/ F8 y14
    7 \: Z1 C' W& N0 e" U3 J15
    8 h3 D6 q2 o+ _+ V2 `/ A- K16- J0 F* ~- v" v" w" Y. b' V. ~
    17" |; k$ p% z9 l( G- \
    18& h$ Z; D. X! _. r
    19% B+ U( V9 b! n
    20
    . o& y2 h8 X5 e! `" i; R3 U21
      ]  p; y8 g/ b* q22
    - r/ b" H9 S% O  W% g( O23
    3 F& ]% w3 d) Q8 V7 H! P3 |Ex2:水果销量数据集
    * S/ R% S( p5 K! d0 `' B3 v' l+ x现有一份2019年每日水果销量记录表:
    . i" q( c( b/ f* i- u. V# N/ ]4 C. Z* [, C6 v
    df = pd.read_csv('../data/fruit.csv')! k2 x+ |) T1 ?
    df.head(3)6 o, S$ `2 \" E  p9 u" }$ F

    7 s! ?# ~8 A2 k: U) }: _  XOut[131]:
    2 ^1 k9 t+ l. f+ Q         Date  Fruit  Sale6 C3 Z( R+ B5 ?
    0  2019-04-18  Peach    15& j6 B* K0 I. o, I$ B- }" Z8 Q5 C
    1  2019-12-29  Peach    15
    2 r6 H5 K: ?2 y( F; a3 E2  2019-06-05  Peach    19
      `4 p( a: [# V9 m: |1" [7 h1 A; v9 t* a
    2/ K' a* v( d. m3 t* a) D6 {. p
    30 V( B) l. b) N* q' _
    4
    9 l& a3 n' r4 ~; c" ~6 ]5
    1 l7 C4 i' H, `, z( L6
    6 l- V# d, A' V. j/ U; D) z7
    3 _! V5 b8 ^% E$ y; t8
    ( B: G0 I' e1 P/ m: h9 v* o统计如下指标:3 v- Z  l+ g9 F$ G9 Z1 v1 h' ?
    每月上半月(15号及之前)与下半月葡萄销量的比值
    0 v8 |! V4 P  s+ N每月最后一天的生梨销量总和
    1 U) I: u  e7 V每月最后一天工作日的生梨销量总和. B! A9 I" n6 |/ H' o
    每月最后五天的苹果销量均值
    6 F. [, q, M$ d5 O3 g2 @  Y按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    4 K) C" w2 Y7 ]- g2 q3 a按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    3 L/ t6 Q/ T- S& i4 o" F+ Iimport numpy as np6 j7 e5 k* s2 ?
    import pandas as pd
    % K! H, |/ f6 c# f8 t7 {1
    ( ]4 J' v5 ~9 p/ u- x  W27 f! `& p0 ?' h+ p$ j! {( r
    统计如下指标:$ e& D3 x9 \2 ~/ O& N2 o
    每月上半月(15号及之前)与下半月葡萄销量的比值; L# K6 Z& _3 q
    每月最后一天的生梨销量总和, x; {) |+ _6 h4 x
    每月最后一天工作日的生梨销量总和
      e7 ~( x* G* _! m) Q4 |# N每月最后五天的苹果销量均值
    ! O! `/ U0 v* l4 L# 每月上半月(15号及之前)与下半月葡萄销量的比值
    ( k9 l- g$ F, g; ^df.Date=pd.to_datetime(df.Date)
      d. U) z0 L3 x  n7 H  jsale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    ; |6 W9 S/ a; tsale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊
    " ?# m" v) O9 D- Ksale=pd.DataFrame(sale)5 N" k  Z6 T* ~
    sale=sale.unstack(1).rename_axis(index={'Date':'Month'},1 n/ l9 x7 j9 \' }& z4 ?
                     columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名4 @" ^3 u& _( d9 z2 u+ B1 @
    sale.head() # 每个月上下半月的销量# K( T2 ^# K) c9 G$ `
    4 i6 _0 o* A' p5 x1 j
      Month        15Days        Sale
    # T% _' l, l; I0        1        False        10503- _! l6 x7 Q! U
    1        1        True        123417 B2 K8 z1 i: ^7 U: K# z
    2        2        False        10001# m/ w8 Q$ p6 _- ~# I& c
    3        2        True        101065 a* ?! R; t. H- k. Q7 J3 F# b) ]' [
    4        3        False        12814
    * m, ?5 {2 q& Y2 Z! P6 x
    7 Z- `1 ]! t, }$ |5 |3 S7 Q# 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序# J6 B- z, o4 A8 B: o; O3 ?' z
    sale.groupby(sale['Month'])['Sale'].agg(
    % |9 m) Z3 C! H0 P+ [                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    # B: a' Z7 _' k8 ^; s& q1 [8 V  C& r' h# ?" k
    Month
    & F3 L, ~- Q+ Z* {- {. F1     1.174998
    ; w! ^3 g6 I& C3 m9 U: ?* _2     1.010499
    * `0 [" V! k/ l; [, O0 m3     0.776338
    5 t6 F7 B+ L2 E" C. b! R6 x! O4     1.0263455 S; b# s" R) u, U2 J
    5     0.900534
    " [6 u3 j' y% r# k. y5 m  k6     0.980136" M$ P: V$ o/ Q, c$ Z0 Z8 m; i& b
    7     1.350960
    : e" Z4 l- [. ]7 z4 h3 V( P) ]% p8     1.091584) j5 I6 ]" s! p! u' `/ x6 L
    9     1.116508
    8 @/ Z; W) E) R4 X3 C10    1.0207847 [& O* E) j$ H, Q; s
    11    1.275911
    " `/ _6 U$ d' b+ `* _' ]: t& ?6 G  l12    0.989662
    , d- E( ~$ }7 b. e% p" A' YName: Sale, dtype: float641 v8 l4 S, X# F2 Z7 ]- T% {9 T
    ! d  q9 u9 r% \! @% \- _; ~. G
    14 Z9 Z. U6 Z  ^8 N6 c, r: R# X9 ]
    2
    : O( U+ }% }2 Q3. H/ L/ [8 E; j* A+ |  A! L9 B
    42 f9 [) ^2 `  I7 Z+ q. @! A
    5. x8 _) \- t4 a2 k
    6
    6 n9 o4 t5 A9 E7; T' |5 ?6 k5 W8 N
    8! V" O, I" L6 l, t4 Y; K
    94 o* Q# G9 @" ~/ R# K9 p
    10
    & y* \$ T: u0 e6 L/ \  S- f11
    - [, n, L" v, G: h12
    : B2 B! N# Q5 y- z4 i8 O9 q/ x& k$ Y13
    0 B' t) z" p1 s* z( Q, e14
    ' f9 c/ I  i5 c; n" d2 m" c; Q/ W15
    : _& g. ?/ ?: r" e16" [% R/ B1 }; G* y/ K
    170 w5 |' ~7 r1 Z- V
    18
    $ ?$ s) C# C; J% r  C19
    * f# b$ n$ D, \20- ^, ]+ q% W) C% Q4 r: a
    21
    7 z: i$ C/ D+ m5 g! j3 f) w/ {22
    3 P, h: X# r; f! E5 Z236 P& V! |) U, N- z5 h3 U" ]4 i
    243 [5 r6 p$ \* \. b
    25
    * r% B+ }4 i# g' Z# h+ q26
    2 G: G( Z( K( r, s* z27
    , S' Q* A/ Z8 h% w7 J3 v286 x, A( ?9 L6 @, B: ^- ^) X3 h0 ~9 s
    29) k+ B( x8 L" K6 S( G
    30
    ' a% X$ O( v5 ~( V& ~; w; i! s312 _6 u9 `/ `/ G- `. O. \. \
    32, w; |5 H, j4 P$ G
    33
    0 ]4 G4 }1 y0 s6 u348 {$ ^, L+ t. W5 b
    # 每月最后一天的生梨销量总和
    / c7 q7 p+ P$ O) q/ A5 N+ m8 Jdf[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()( W- \: G, o+ I' d4 w( K; N' Z
    : r; d* g) ~& P/ t
    Date8 @* y" }/ E, I  ]; }0 d
    2019-01-31    847
    % z5 Y9 l) y0 W1 D- ~2019-02-28    7741 i, B6 ]% a* z# b& y% e$ o7 p
    2019-03-31    761& O7 g: x+ l) E. b# Y2 `# Z
    2019-04-30    6485 b. I6 u: `& F  a, h& B. `
    2019-05-31    616
    0 r7 D+ W4 G3 R0 e6 }1
    9 M- X* G! e# Y1 l- T# i+ B9 r) w2
    & r  x" Y: |. S1 `3
    9 P" Q4 Q- ]9 b3 _% {4+ b  w/ ?2 B5 d- j" `( H
    5
    : p5 y# C! k6 K' [3 W6* z8 t5 K7 \3 I5 m% _: i
    7* \) y4 h- N  ^& N( F1 J; ]
    8
    : d- I% A+ n' o$ ?! p! R# A9- l$ e. j" O/ R' F7 o# ^
    # 每月最后一天工作日的生梨销量总和2 t: J+ W0 d" m
    ls=df.Date+pd.offsets.BMonthEnd()$ ^  \2 W1 W1 Z1 L5 k" N) ?4 G. h
    my_filter=pd.to_datetime(ls.unique())
    ( t1 C2 q8 G! F7 x% {; z' P) n: Kdf[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()! i9 H  `. ^/ Z; p

    0 t0 ?  ?$ h7 kDate
    ; u; U0 e* K. i' V% D) g& t8 f( z2019-01-31     847% z4 n3 f1 g1 C7 n+ p; G1 X) G
    2019-02-28     7746 @& l# v+ a! t- w0 W  H  ?  P; y
    2019-03-29     5107 l: ^+ [4 R) L7 A
    2019-04-30     648
    1 v  v5 c! @6 @7 O2019-05-31     616
    2 Z, u1 L! |4 U% f0 ~& u1
    0 l& G; H: i# U( s2' f$ L1 P9 R( K9 B
    3
    0 l- T) ?8 `6 z$ q0 j/ G44 ]4 G7 {0 R3 p( b& ]* M* ~
    5
      X3 j4 J7 E4 P% b& s# l9 w! A6- Z( }0 E$ k, S& E4 N2 y! c
    77 g/ c2 Y- E4 I& H8 h; M. b2 x( Y
    83 ^9 e2 ^2 D, J( _/ s. v9 d
    97 |9 d. n7 Z2 X7 F: n# n/ u+ r
    106 {! J! L# |- E6 c% {; M! {
    11$ x; r( e) @% j% h1 a
    # 每月最后五天的苹果销量均值
    5 W8 A6 u$ O; U& I1 P0 n' |3 ?start, end = '2019-01-01', '2019-12-31'+ e+ \  K( z. i) e0 j/ j1 v2 l! H
    end = pd.date_range(start, end, freq='M')2 j* I. q7 K- h: m  U% U
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差7 @. n0 W) |: U4 \0 H
    / I8 ^: Y5 T/ Z& z
    td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)1 |3 v9 _# v8 U" z, c
    td=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天! i+ x' O" l) g* n3 T
    end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    , H) q1 a$ t2 V) M: \
    ' E1 f" Y( y, w4 bapple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量
    $ K; U& X& J" D' }( aapple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()& J' I- j$ N+ b, ^6 E

    % W! i  Y- `- Z: \& ?# h( |Date# c+ |* n+ i. D- m  j/ v5 p
    1     65.313725
    5 r+ I# ^; o, Z) J, I+ F2     54.061538+ Z/ e$ B0 I/ D( M5 @$ [
    3     59.325581* n  n# W+ }) U  h' G) h
    4     65.795455
    8 ]9 K  M9 x% g" l2 m( t6 l" T5     57.465116
    / K0 A1 ]9 N; \$ \0 L1 m' `0 V: |8 i0 n" O3 v" U# d' i
    1! O; a. ?1 l' @1 }3 B
    2
    ! r6 @6 d  m, w' T/ q3
    $ @( O6 X. ?2 H* S2 X4
    ) L1 x* L. Y: g, A6 S54 h& v% w6 s$ E/ ~1 O
    6( v$ I+ Z) I4 J
    7
    & H$ B/ C  @! L& p1 ?, [8
    ' S$ n4 I, O' L% s; v9" i: X! P7 P3 y4 k6 o7 O; U& [  v% B
    10
    9 i* z1 G4 E! `9 u1 {11% T) [8 ~- k4 j  r- d
    125 n7 B: j4 b8 x3 Y
    13# F* F) s$ S8 J8 f) z7 z! T
    14
    6 {. Y4 k# @, }9 y* ]15- r7 ]0 o# A% K2 K
    16# J% w8 ?$ q& l9 C+ R6 I
    17
    ' G& c4 I; y4 ?- D3 ^18
    $ s# ]1 H, A7 A) |6 d+ y# 参考答案:
    $ w! t7 |; Q# Btarget_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(% Z# X/ h& K, l4 ]; W( m2 F
                ).dt.month)['Date'].nlargest(5).reset_index(drop=True)# I4 E4 g* L  s2 e# e% ^

    # R( H9 A6 M3 L: F+ r6 I6 v* Ares = df.set_index('Date').loc[target_dt].reset_index(
    3 x5 H) W$ y6 c, a            ).query("Fruit == 'Apple'")
    5 I3 W3 o* I* u5 F# x: }( I0 ^, w; \- q0 P* S) l: B# S. g
    res = res.groupby(res.Date.dt.month)['Sale'].mean(8 {% ^/ \6 ?$ w+ y
                ).rename_axis('Month')
    * o% e' m6 _+ C) ^/ n" o3 h# C! M4 U) S2 s

    - p+ J* g% @# d  g) N: Q0 ~2 \( yres.head()  b# ]/ z4 l3 W
    Out[236]:   d  s$ `# [8 Y3 u/ b/ r  @# w
    Month3 X. x% h- _) z. I: d5 G
    1    65.313725
    ; Y2 g, g/ z7 c3 P: T2    54.061538
    ) `9 _7 k; |- o$ b3    59.325581
    8 E, X, I3 t& V4 a& Y. ~4    65.7954551 Y/ g) B- N, t* N" R
    5    57.465116
    ( ]) B8 B" o) P2 j+ e+ C$ u# yName: Sale, dtype: float64
    ( |, Y: ~9 w; c$ f
    . ]) F" U! Z& V8 e1 m1
    9 R) M$ S% E, V: e; O  F2
    * g9 t( W  |" x) z3
      v3 {" ]1 U% t) i& }  i4
    ; \5 r3 r& l2 O3 o) n) M) N5, e5 G4 q5 i$ X/ k
    67 @9 y7 N, f: j1 i
    7
    ( k9 y0 g! b" y8 V5 k2 c4 V+ ^8
    & N( x: a6 }4 `9
    6 T$ h7 I/ R5 F* y# n8 X& P106 d0 N* m8 V2 o
    116 P+ `6 ?1 j" A. d/ a' n  ^
    129 c% j, N* H) J  S4 o0 f
    13
    0 p- P, n% l: k9 |2 X14! B+ [) `, j8 S  m: D
    15# T% T6 D; F6 C8 k/ K- g7 v
    16, x& A- L3 P! G! r
    17
    5 ^) s# O$ g" ?# c# u3 _18& h  Q8 q) O* N
    19+ L# e5 M& c# [' J5 s
    200 n% E" s" ^" n1 }0 C
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。" M. i! l. r3 m4 i
    result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.3 d& f6 b5 M6 i  S( N0 Q# W
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
      ^) ~6 X4 {, J( q$ R/ I2 J                                       
    $ X3 B, W8 Y1 |) v! gresult=result.unstack(1).rename_axis(index={'Date':'Month'},
    1 d! m6 B9 W. e4 ?1 H; t                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.. d! S5 D: s- r+ D
    result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
      x; L8 h; }. i+ a& yresult.head() # 索引名有空再改吧# x" ^' z# a$ A1 h
    - h! L# P. O( P( U% F+ ~- C
              Week        0        1        2        3        4        5        6+ E2 O* y9 M7 S0 b% H; ^/ S
    Fruit Month                                                       
    ; d3 b) M& p. x% p2 jApple        1        46        50        50        45        32        42        23" w: [- I1 M6 W9 [, v9 X
    Banana        1        27        29        24        42        36        24        35
    ; {( \* D7 r* `* L' K6 R) DGrape        1        42        75        53        63        36        57        46
    ' Q! \2 l' D  A+ o( L1 z# ]7 YPeach        1        67        78        73        88        59        49        728 G( }. n$ n0 X9 u$ N
    Pear        1        39        69        51        54        48        36        40
    5 ?: V% I) j6 U9 u& R1
    + Y5 ?# r* _9 m1 t" T! {+ i! V8 l4 z2
    $ Q3 [5 a6 y' F3 I; Q( u) [  t37 ^! E" B+ K6 G; ?7 x
    4
    ; n& Z# i, t! M% j- v) h5- c& ~4 L% ^4 A$ G
    6: R; W% I5 K2 B/ v' C! q7 q
    7
    ' Z+ ~2 Y/ _- p1 H8/ \& G5 b* {5 y6 M
    9( g3 a* S8 B# A: r
    10
    3 _3 Q( H' d6 q" L2 a11
    $ g  p  s0 p$ n& o. a9 M' L! A12
    + `5 C) T$ B; h' L& b; e: Y0 g. d13$ E( U4 g1 ?, T- x" Y% [
    14
    3 c3 [8 D! F! H! ?+ X% [155 q# O; M) p% {/ s, n* c: t+ t
    按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。0 {& n- M4 l, J  A6 J, B' `
    # 工作日苹果销量按日期排序. g0 C7 ~  A& J" E8 t+ f9 E9 M
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
    * d7 c8 l6 M  c3 l5 v/ h; w6 b" vselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总# O1 b  O" n$ x
    select_bday.head()
    7 t/ B; R+ ?4 ~! w' Z# ^  F+ Z$ ^, Z3 E* g
    Date
    : s: I2 _( l4 P; A+ }' Z2019-01-01    189
    ) y5 o2 Y. D7 N8 t- j7 M2019-01-02    482# |0 x- B. J- b; O' w9 T  z0 U. \& Y
    2019-01-03    890
    " s) r( F& `3 I2019-01-04    550! u- U- f  G+ ]7 C
    2019-01-07    494
    9 @" l1 ~5 K" G0 Z
    1 v3 |& K7 |. @% d$ W; i# 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。( L  Y* N1 _9 ]& e3 H
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()
    9 s2 `0 M* b/ \( ^& I6 C1 h2 B" ?$ {5 G
    Date
    & x/ ]( J8 f1 O+ N& C0 l2019-01-01    189.000000
    . d' n# }  c/ _! O2019-01-02    335.500000
    % t" m  Y! j$ F8 a; @# `' N# U2019-01-03    520.333333. T# u0 U! r  l9 x+ ~# l0 T
    2019-01-04    527.750000
    9 J6 M. J1 R3 h- D: C8 F8 L9 v, K& S2019-01-05    527.750000: `, s/ o5 z1 `( X. \/ J

    & Q9 h  ^# @& |( @. [! e( f————————————————  K2 l+ k1 @/ C' M. O) z+ C
    版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。/ b; u* `  \1 z: t- W. G+ C" i, w
    原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913  O9 i4 h7 x; s' V4 T5 S
    # U5 s4 U- V* [3 h4 N) G4 d  f1 t7 w
    / m1 K6 [; m( v- ~5 C+ N; ?
    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-10-8 08:19 , Processed in 0.413713 second(s), 50 queries .

    回顶部