QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2800|回复: 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
    3 f* X# M% X# W( f6 o4 v, \) |' t
    + |* A+ e! M" T* c
    & J, _- ]' J, e5 _" }3 g% y1 V
    文章目录
    . ^" ^3 K( D) T3 q4 D* Z5 S5 G# [2 h第八章 文本数据( L8 K8 d1 z9 G1 ?7 w* ]
    8.1 str对象. h! O( w4 f; q: q4 W
    8.1.1 str对象的设计意图; C8 Z2 w' u$ Q- g- U  V& f
    8.1.3 string类型5 n9 }' Y) z; z+ V2 I2 c0 d
    8.2 正则表达式基础7 X# y! `& B9 {" P
    8.2.1 . 一般字符的匹配% F8 E& e6 p& {  s' B/ A# N+ P
    8.2.2 元字符基础
    7 o. P! S- X  e6 N  r3 f" \( f8.2.3 简写字符集* r4 Z, h9 A6 l% W2 r
    8.3 文本处理的五类操作
    4 S/ Z& \* z5 Z/ B* G: p8.3.1 `str.split `拆分# E  h+ G3 I$ f/ F
    8.3.2 `str.join` 或 `str.cat `合并/ c* i: ?, z% Y( R1 B
    8.3.3 匹配
    / ^8 I% y0 q( _8.3.5 提取
    4 b3 w- E$ ]' o! {! a6 R/ y+ |2 c8.4、常用字符串函数/ V* w; S1 w2 f: _3 O' t
    8.4.1 字母型函数
      e1 u! ^. M' r9 w% j8.4.2 数值型函数6 |7 a: I/ b+ v) [
    8.4.3 统计型函数  f: c5 s3 w, C
    8.4.4 格式型函数
    / }9 n9 R. B7 f1 F8.5 练习
    % j7 k3 i! D! c% A* ?; }% U3 ?4 LEx1:房屋信息数据集
    6 C6 {' G/ X$ X# H: ?- gEx2:《权力的游戏》剧本数据集& \  f. `$ y8 G( H4 B
    第九章 分类数据
      F$ b* {0 t, c$ ^+ S0 o2 ?) w9.1 cat对象
    ) ?$ r" ^! @' T9 s+ z9.1.1 cat对象的属性
    5 S7 t. A# v) H9.1.2 类别的增加、删除和修改
    ; u6 T, L8 |; I) Q9.2 有序分类
    % N& h# }8 y( L- o9 m9.2.1 序的建立* Y+ f! r( R1 ]( X# u2 C1 `$ P2 y& x
    9.2.2 排序和比较# i, _% U9 K" W# u3 i) v
    9.3 区间类别
      e; d+ J7 k# |# o) T9.3.1 利用cut和qcut进行区间构造$ {. r* x" H' k/ ^4 X
    9.3.2 一般区间的构造
    $ d7 C8 A9 U* g8 ^; [& r+ Z/ P9.3.3 区间的属性与方法2 n8 s  i6 _) ~1 O! `: e
    9.4 练习
    * r6 ^% F; @* T( G3 xEx1: 统计未出现的类别
    2 m" N% K5 ?6 rEx2: 钻石数据集
    - ~) }6 Q+ T$ ]第十章 时序数据8 T# l  i; u$ ?! @# Q
    10.1 时序中的基本对象
    4 o5 l  s% L" j# j) _$ v5 Y6 u' [10.2 时间戳/ M& X* c) F8 [1 a5 m# q7 g' g
    10.2.1 Timestamp的构造与属性
    4 o0 G7 {2 F0 ~% f10.2.2 Datetime序列的生成! ^. b; s0 b) F  o- z6 u; i
    10.2.3 dt对象
    . P$ O1 P* L  H# L" ?1 y" R. }. Q10.2.4 时间戳的切片与索引
    0 @/ c! z- G! v. F10.3 时间差2 C& o# w1 L: \& A
    10.3.1 Timedelta的生成; G1 g' Y; T. `! [- Y
    10.2.2 Timedelta的运算
    $ c; Y$ S8 Q0 k8 h7 t; R10.4 日期偏置
    ; k: F% w9 D4 ?10.4.1 Offset对象
    ' D0 c" _5 ?$ \9 T( S; w! ?" X10.4.2 偏置字符串9 }; {& q0 P  P! M8 n1 w% \
    10.5、时序中的滑窗与分组
    7 [0 h) H. k7 q/ ?( b+ s10.5.1 滑动窗口
    * T3 r# `; \! @( `10.5.2 重采样
    8 ?  y; x, a" G: u- Y+ ?) E0 z10.6 练习
    - z" F( i. N! [1 o- F1 E3 |Ex1:太阳辐射数据集9 H( Q$ {1 r$ q/ G
    Ex2:水果销量数据集$ W; L  y, B. _0 R2 n- s
      课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网/ g) V8 \5 h( g, s  _5 f# f) k# l1 L
    传送门:
    , z2 S) {1 i; e, g, |' P2 X" f0 @" N9 V: Z6 ]5 n, c( r  j
    datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组)
    7 }+ A6 D2 [3 ?, k% o" [8 z# E& J& rdatawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)
    8 ?- A7 W( r4 Z% ^/ V0 a+ k第八章 文本数据
    7 n7 I, I8 h3 `  _9 L& j8.1 str对象+ u! t% D- E$ C& q3 t& n# D
    8.1.1 str对象的设计意图0 E9 ]. R4 [9 `) y9 \* E% Z
      str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    5 ]' d% e- C6 z* M, j1 A
    - k/ h) }* h% t4 }4 ~var = 'abcd'& o+ v$ O7 x* w4 d1 P7 b* d% q
    str.upper(var) # Python内置str模块7 m* ~2 R" o% N- A! }
    Out[4]: 'ABCD', d# D: C2 G8 q" N3 G

    % }! v$ O9 S. t) Hs = pd.Series(['abcd', 'efg', 'hi'])
    * d5 \- e: i5 g* D" b, ^' @4 b4 `* |! U
    s.str/ D/ l% I1 t3 U! k
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    / r% @- G; h6 `5 d$ {) B' c( z* b7 g4 z4 U2 ]
    s.str.upper() # pandas中str对象上的upper方法
    $ F! E, H0 Z0 ^Out[7]: $ F7 D: b" \4 m; H" n4 G
    0    ABCD9 }. B7 T% h: W8 g0 [
    1     EFG% L" E" L* ]/ s6 t( K+ }5 _/ r
    2      HI
    1 \3 P8 E4 W' i/ g6 U% i9 @" Bdtype: object0 R9 s# v+ s; e5 r9 U
    1
    : e( \* w. _) H  \" Q- w9 M5 V2
    ( w2 S- g3 A# D  i2 k2 v! p4 I5 F3
    # f, ?- o' Y# y$ W" t4
    % b- j0 \0 ]6 G. t0 Q& I5
    9 {4 m3 H' u% ?2 {6 F' A66 ~" e8 {- I' e1 w6 j
    7, R2 J5 j: I) D0 x$ g' D
    8! G, }2 O5 a, \3 q& I
    9
    3 ~; v, }" k( c7 O/ U: N10' g1 ]% T, h( R% l9 v& y% ^7 _9 ?
    11
    ' j0 \8 A3 S8 X5 z4 {6 S123 v# k) n0 K* ?% X
    13
    9 D0 L# J1 u+ Z- g. v8 a/ @14" E+ e: N8 F3 H0 r
    15
    . f! c8 ?; n1 X- o* O8.1.2 []索引器
    0 H# M* F0 h+ E( e! Z. }& ]  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。
    8 R& D' R) b! [+ R  B+ J% q( P  pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:- H, W0 i" K& p7 K

    . o8 D8 ]9 \" b2 rs.str[0]6 n7 x/ ~" u; j) E( C0 j3 D7 e
    Out[10]:
    ' D, ^3 z4 m( H! y" l$ j0    a
    6 n3 G: D" f, T4 T1    e
    : U& U, j% V& ^0 p0 ~2    h, ~5 x  ]9 @5 V6 J- V
    dtype: object9 H% x3 H( G- d  k

    " s: [6 r+ z2 B% ts.str[-1: 0: -2]
    + n; V! K5 O6 M  M! C) r9 uOut[11]: " i% k; k: k3 q. F0 ]
    0    db* d8 g- L5 O0 O2 j) `& F
    1     g
    ' D5 k: B: G) N7 x: r. ~2     i
    5 f; Y; H/ Q' ?. s5 z+ Zdtype: object" v4 }/ @5 F) [
    & ~- O; A- I$ u1 U4 N0 U' \4 M' s
    s.str[2]& }: E% Z( c% q% h0 Q5 B
    Out[12]:
    ) h6 o+ [& P; |0      c1 t, M. ?( k6 \+ s- v% z1 n' B
    1      g
    / w( {: l4 s6 I$ b0 K6 V2    NaN* D  j; S3 |; h" B& u* C
    dtype: object
    ! A8 h$ Q( i3 W
    3 V3 i5 Z* B9 N- A( {" _19 P5 N7 Q7 M; y1 D
    2
    8 ]' |* P/ ?1 Q3 F9 R37 S/ y* c$ y+ x# s2 w: v
    4
    ! ~# J7 z3 ?* V8 L( K2 \- L5
      o( d! {$ g) `9 l$ I6' C$ @. D! t2 F( r
    76 Z7 \: z2 g: M- z1 M$ k
    8
    6 q8 `# i/ J# y8 Y7 K( [9& d  g5 c$ m( o+ u5 @+ d  i0 D% K
    10
    2 j+ j' Z/ |/ S) S0 O11
    % V) p8 V. k$ l  b, J121 s5 w8 e  h# J8 g% D9 J* `( v
    13
    7 B7 ]: ?' j. \( a3 ]* I+ w" ^14" v8 P1 C- s. X+ H) o" e  `+ b
    15. M5 A# c% \! Q3 ~2 O8 p
    16
    ' ?9 N& s% c" l9 S, B& c/ ]  V: p17
    ' N. h* N. f0 K3 \3 J18
    & g: \/ z8 t: f& W* M193 h+ i9 A; y7 C$ D
    20
    1 ~9 z5 m. I9 D! P5 U) R9 D* kimport numpy as np
    % y* f/ Y  [0 p- H  mimport pandas as pd
    9 p3 V) W  B! Z# n# z0 K+ `% U/ D/ i# @
    s = pd.Series(['abcd', 'efg', 'hi'])
    7 ^8 Q7 x7 A0 d- @% Ds.str[0]
    & J6 z7 N, U8 R* b: ^6 |( I1
    : X1 u0 h& T5 o: {2  Z0 @& w- o% ?4 B
    37 }1 m) e4 P/ W
    46 B  u  f$ c1 }( x
    5
    / h2 Y( |8 C- G3 q/ ^+ D7 r/ L; m0    a; q5 U' E0 d$ S% O$ t" `2 Z4 Y# e
    1    e
    : l! S0 v% k0 H- p2    h
    / I! E5 h; `: y, Q0 h1 Bdtype: object7 q6 C8 I0 R$ ]6 f. m
    1
    # u4 m# n+ Z7 @# V! H2$ {0 X# o" A1 _. b5 ^/ a
    3& i. R- E0 Y% ?
    4" Y# U) f0 Z; Y* G3 P- g
    8.1.3 string类型" M$ |# c# ^/ x# {* O& Z& p
      在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。$ Y  c. g" i* Z7 I# h  P4 o% L0 l* i
      总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:
    ! d( ]- A* C' p* y& k$ M. {8 H8 F
    " _4 i. S) d0 s3 I' Y8 K' x二者对于某些对象的 str 序列化方法不同。. }6 k0 p- a8 X7 y
    可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
    & c4 |" z- o* e# y4 g1 w( `s = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    ) e; y; U2 V# d+ Qs
    6 _& n4 e7 h' M% ]; s' f13 m: A2 e6 U  E( ^0 b% Z
    2
    - Q9 f. s2 T! I0 |+ ?0    {1: 'temp_1', 2: 'temp_2'}
    3 w9 F6 [: b  |  u5 c  ?1                        [a, b]
    # I' z, j5 ?9 q  {2                           0.5
    & J' P# C( b3 M( l3                     my_string. `. |3 G) ?9 J: R/ M- ]  F2 |( `
    dtype: object
    9 M8 ^, t6 t3 G* J9 g1' r; y: T) @) h' a0 m
    2+ Z6 X% O$ E( j! o
    31 R! a7 A: x! X9 a' [4 _3 t+ v
    4& n6 a# W+ c  i; T+ }
    5
    1 A) G" m2 G" p. T) T. N: js.str[1] # 对每个元素取[1]的操作; b& K8 C. ^* n( j
    1
    & x; D- f$ |' V0    temp_1
    8 [5 Q/ g% S* _+ r0 K9 F$ L1         b1 E) y, l& C' K  K
    2       NaN
    - ~5 m0 i! E4 l# v! n3         y4 y& r' D! B. Q% b1 i9 }; @
    dtype: object$ ?5 n" [) P& z; ]
    13 R3 o3 V2 u. u. V( m
    20 b7 S7 b* H/ p0 r- [
    3. |" ]+ ?& s! z# G% K2 Z& o
    4) H0 n1 g* w: E! ?5 W7 Z6 x
    5% ?! t" \9 I: `2 N8 T
    s.astype('string').str[1]. ~9 X  O0 |* r3 t
    1
    / `, T1 {" z6 n9 a. h0    1
    # t# \8 R9 |. l, }# d$ N6 L1    ') S, w1 Q$ w" \$ z
    2    .  f5 u4 e% M% [5 {, [4 n" O
    3    y. r# W$ E7 Z  ^7 A5 v- s
    dtype: string
      Z& ^' \: [, l" Z! y  B7 B' H1
      s. Z! [- r) J: N2
    / |8 I1 ?$ B8 O" A0 m! `3$ f* B+ y7 w$ h- W- Q# g" T; I! r* |
    4; K1 m6 q/ O3 S
    5
    ' x& ^4 h8 j8 ]4 t0 X+ D除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:
    9 ~0 a: y% b# n: Z0 w( H8 I7 d1 T
    + {/ A' U, Y- X当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。1 B3 e* \3 {3 A
    string 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。- |1 }* ^+ B/ A7 H; X! o& _
    string 类型是 Nullable 类型,但 object 不是
    2 f# i1 _7 S' s# w2 ]$ p  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
    3 `3 F' p6 y  U  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。% U. k. h: C5 ^: r- }2 `+ R. f
    s = pd.Series(['a'])8 z( h) g0 J) X' O; t/ i- B+ M0 S

    - c5 P# C8 ]1 Zs.str.len()
    , ^, Y9 m1 D6 u% QOut[17]: 0 U! D. o6 Z% |
    0    16 ?5 Z: K8 R/ v, |. @, D4 z  G
    dtype: int64- d8 e: v3 |- P4 R

    " f, q: y5 _. n. u& [5 H5 g) y  x& Fs.astype('string').str.len()
    + n, _. c1 H: b0 w( y8 Z6 LOut[18]:
    4 s/ f, q: W  q; n+ n0    1) @6 ]4 \; T  ~/ S
    dtype: Int648 a+ N  d9 A5 o. b

    7 ?$ l+ o7 A4 s4 o  ts == 'a'
    + G6 s/ s. v# z* O3 I3 x( [Out[19]: 8 m* \" l* Z2 w: D& o& Z3 X
    0    True
    ) j7 K2 j8 E  s) ?5 g; xdtype: bool* R" V% K2 k4 y) x

    ; W8 P; U* K. |s.astype('string') == 'a'
    ) ~( `6 d6 U5 r# I! `) \3 ]9 HOut[20]: ) t. L& `# B6 @7 ?" j9 Y9 w
    0    True0 q, H4 m& L9 ~$ V% d$ G, n" f
    dtype: boolean' E8 {: D7 A2 M4 d. }& i. k; x
    . [8 B' f& A" `# X0 e* N
    s = pd.Series(['a', np.nan]) # 带有缺失值
    ) C; ?6 a1 t+ h/ X
    # y; D. w" x# H: Y2 s4 f; l- Ys.str.len()
    0 _+ a! n$ R  Y3 fOut[22]:
    7 p* n* |% F4 U0 A& s: a0    1.0" M/ J- W$ Z' V& N' P
    1    NaN- S, m) ~6 i9 p/ i; N% K2 t& V
    dtype: float64
    * A! U9 a' M6 S  C$ I- N# C% c1 C- y
    s.astype('string').str.len()4 m& G# t3 b* G( Z$ A/ v3 ~
    Out[23]:
    ( q: n5 p  V9 T5 S3 c0       1
    - H4 p6 E, o# n/ |6 o, o) C1    <NA>0 T! V1 Q3 r' j1 F. e
    dtype: Int64! w$ {- j! c% u: R' K' V* u  @% A

    " m7 w1 u6 I* D' T+ l- n5 |7 ^; ]3 m8 Is == 'a'8 A( f! I) c7 ]0 D
    Out[24]:
    3 X, n4 [, l$ }0     True* B. g9 W8 C  H( B1 Z3 h$ S4 `
    1    False
    / V0 V# L# W- M$ u8 a" I7 j! U: vdtype: bool+ ^2 d9 m/ P- Z+ E2 U

    3 i" O9 X6 c6 Es.astype('string') == 'a'- [  ]6 b$ E' M0 m$ z7 Q4 p
    Out[25]:
    6 g4 V, }% {6 z  }: W( A; R' ^0    True3 U; ~& ?8 C" T2 S; p
    1    <NA>1 f; f7 u  Q) _9 A( g. a
    dtype: boolean
    ' M( u  l8 F; d0 V# `, E& N+ E, }
    & `5 J3 O0 @2 r) e1 {& i0 l' c1
    ! L/ l, P1 ]: f# r6 l* v! x3 t* E2
    ! }! y& I' w7 e2 r* q( T- T2 e3
    4 v. C6 a6 i+ J, O: T: Q2 _$ L$ {4# K) l$ V+ i5 k% t
    5
    9 Z0 z# v* {4 S7 \5 I$ E+ m6
    ( ]/ y. m) A3 U# ^, i& B9 J: b9 f; q7+ V3 c& X, ]/ {8 O& A: P- Z! ?7 ?
    8. P4 l% J0 k3 @* l1 T
    9' K; y+ R  N$ N6 @6 i
    109 x1 o& O$ U1 i1 b& t  ^
    11$ O1 g( j$ l+ b7 L0 ?+ H+ f
    12
    . @2 Z$ v1 _4 U, J: @136 M  \3 R( {; P
    14
    , f; r' [" M) `- `: z& O4 r! J: l7 K156 {/ ?6 e7 d+ C/ D
    16/ T0 D. }* g7 q( W
    17& a; ^, {! R5 ?) L, @
    18
    & M1 j+ J7 `, I0 k$ ~7 s; c19
    " @7 c6 P: N, K* e' R- ]  r: I* F20! B. E" g; F$ {
    21! Z% K* p3 `- T: C. }
    22" p' `/ X( h1 Q7 T- S
    23' L& e; p3 w- d1 G' `7 C
    24
    2 X& W5 @/ h/ Z. G! y  E255 n! Y6 u& F/ H5 a. {* f$ T
    26) _/ Y2 h. i( D7 i7 t$ [
    278 t7 ]& D) |& D! G  F8 ?
    28
    7 k" x6 F) a$ [! z5 d293 H( f1 L9 F6 F  t0 I7 P
    30
    + S0 S: o7 u, G1 D, z1 F- Q# t31
    5 [: c: ?! @1 k5 a32
    - s  E+ k$ x# ?1 L/ c3 F- |/ Z33' [& f& v! |4 K# o2 y. c# X
    34
    . t3 W6 @: `; \# K* \. `" R# R0 m  b35
    4 f- U0 l. e* u. O367 R. Z- b- j3 k: z; w( h# p
    379 n4 z  I+ @  M$ C0 j4 Y- J
    38# P9 S5 g8 y! L7 A8 ]
    39
    , t/ n' u: F7 @! g# p+ ?# W* P, g400 Z* v  ]4 I1 W1 f
    41" |; E1 Y6 k2 n: z8 C8 ~* M
    42
    8 A$ a* |/ @. J, P434 T# ?* J" o* C) `; k
    44
    * S+ i5 M7 x4 n% B! p  N45
    " n1 \7 v% ]! j46
    ' O: Z. }, N, Y8 ~, e) f8 X47
    ' H( R3 E" e$ _2 l0 w' ^! P! N  对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :
      \8 p( J: J( z- D6 q0 [
      {+ [# _: T. U, l5 }s = pd.Series([12, 345, 6789])
    , @: l; n; {- x9 P7 f2 `
    , N( R! A/ d0 \* u  d- js.astype('string').str[1]5 `, b, u) u* T% k5 w+ g+ p% z0 r
    Out[27]: ) D3 W4 R. R( q8 z+ e+ Q, U7 m
    0    2
    * T, r. ?' P9 S; `! E) s1    49 M9 d% [5 V0 n3 W
    2    7' s, s$ p5 {! b
    dtype: string1 Q1 b- I  n( a& c
    1/ Z5 w7 @+ U; c
    2- Q& J7 E, F: u
    3( q" e" M/ N- x( n, \: F; S; d4 q
    4& s9 F, A( G5 h4 I# A
    5' o/ Z' f* p; Q4 N! p
    67 c5 u6 D9 \) P6 ~: {
    7
    " _$ b8 B1 N7 k$ w, g$ ~87 ]6 D( E# l  K
    8.2 正则表达式基础+ c( a6 P- z2 K, N3 o1 s+ i0 y( H
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
    1 H! F, N& G* D- J8 f
    - [9 i0 [7 {2 Z. c8.2.1 . 一般字符的匹配
    ( z1 i  d6 S+ k. s2 O0 [  K: i. n* M正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    ) w7 c3 R/ q% f/ h% u* {+ M; N' F) v7 ^7 R. N
    import re/ R! p5 z( g+ B) s/ N4 d. B1 ?

      y1 X& q. W; c& c6 Qre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配
    " z, D# z9 i$ J- A' j$ }Out[29]: ['Apple', 'Apple']" s7 t8 `% [# T  A( e  l: p' q+ n
    1
    + K. S+ Q+ [# z; _. l# }1 c; ~23 G) q0 ?, w( r! Z& q4 p% t
    3
    ; ?! x0 ~/ W. Q! j1 b% ?  e4
    % N3 f" b# M& ?/ L' p; @) a1 N8.2.2 元字符基础/ q( b# h3 c2 a+ g6 m
    元字符        描述
    6 U' b3 w1 J) E& |: q# t$ c$ p/ o.        匹配除换行符以外的任意字符
    # j( Z0 o1 `8 G4 @% V. h# u& _[ ]        字符类,匹配方括号中包含的任意字符4 k1 R! I6 ], [- B+ e4 g6 F6 K
    [^ ]        否定字符类,匹配方括号中不包含的任意字符
    $ }- _- ~" s# ^$ e*        匹配前面的子表达式零次或多次- s7 a8 k+ ~+ y2 G
    +        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字7 F4 S9 ~3 ~; k$ W
    ?        匹配前面的子表达式零次或一次,非贪婪方式! u7 W* U! K/ J+ ]) d) ]% m$ m
    {n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式3 F& k% V7 e* m) F
    (xyz)        字符组,按照确切的顺序匹配字符xyz
    1 a* ]4 z' }, K+ Q3 y- u" m|        分支结构,匹配符号之前的字符或后面的字符: p$ A& a0 x& U: u: z% C
    \        转义符,它可以还原元字符原来的含义
    - t$ c" O" O7 V^        匹配行的开始
    % G2 e) J' C( l3 Z8 I9 q) H$        匹配行的结束4 B! x4 `  h* S0 M
    import re& A7 N% S6 e, u3 c5 f: W: i% d
    re.findall(r'.', 'abc')2 w9 Q! v! ^# h; F$ V. h
    Out[30]: ['a', 'b', 'c']
    : z! j6 y9 z1 v! Z" ?3 A4 s
    6 }! W4 W1 ]$ f$ B4 W- d0 s" o( Tre.findall(r'[ac]', 'abc') # []中有的子串都匹配/ M5 N! a' B1 Q+ Y" B# Z
    Out[31]: ['a', 'c']+ s% T" F2 r- n2 c8 ]- }5 h7 O

    ) {7 W4 P1 Z- x" j% |$ wre.findall(r'[^ac]', 'abc') + r+ _& Q$ J# z/ Z3 r  ~
    Out[32]: ['b']- h; D/ d5 j% R0 @
    8 w0 m. n6 ~; g0 f( a% u
    re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    * Q) m, |9 N2 BOut[33]: ['aa', 'aa', 'bb', 'bb']$ C7 P! v& `9 Y/ a( }2 o# W

    * ?. N5 }7 j  Bre.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
    " x" W1 \; Q* A* T( H9 hOut[34]: ['ca', 'bbc', 'bbc']
    ( d$ v! n/ C+ U  G( g$ c. p/ z2 E- B# P& w
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
    5 Q' N4 W! f2 o5 A5 U"""
    & o4 f' w4 B  }* T/ v* [, x3 o3 ?! G1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。8 A3 K% K9 S* y  s7 {
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边8 ?" g+ Q7 \5 K! y/ W1 u1 V$ `
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,
    ( w4 i; y0 U0 @! T' x4 `0 @# F0 O但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a
    ( G- n7 a3 f' l3 K: h4 v; O"""4 E& H% w3 n3 s9 U

    ) t6 |) q* I+ ^9 b/ {* ^0 M* cre.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。7 T1 ]$ @, j: a/ m) h5 _$ R5 L
    Out[35]: ['a', 'a', 'a', 'a']  O& N. j2 i, a+ x/ T2 _
    / E' h0 X2 t; W/ Q1 b2 l+ }
    # 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。) q' ?  o3 b6 ?  k8 ~
    # 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。
    & z' R, t3 O3 R' u! @1 y1 Xre.findall(r'\\', 'aa\a*a') 0 K  B" z0 ]; `! `9 _1 Z2 K$ v
    []5 d  x. ^9 c$ p2 T4 E6 Y) S
    / m  @; D. `6 F/ y+ `$ `5 p
    re.findall(r'a?.', 'abaacadaae')
    . e# p! K+ w9 V7 TOut[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']
    $ d/ K! c. l3 u% u- G& |1 P
    - Y% b& _( `4 a% Ere.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表
    & z) K. S+ o2 n/ |( r[('width', '20'), ('height', '10')]
    # H6 C( A" W. m
    1 d( w. x: F+ A: @4 K# v17 N, P7 g0 H5 t* c9 l
    2
    3 ]- t/ @* A' o; N3: W0 h& A) m* Y2 k  q5 j  e6 t
    4
    8 y1 X, a5 l' r. H5
    + `) c4 i. m: [! c  o3 l( f2 t. K0 M6+ t( ~2 t# T8 L% W
    77 g  O% n% y. o; E
    88 U) Z7 d. U  {( o% C- T1 o9 Q2 L
    9
    6 {# n7 ^4 ~/ t, v8 h* `, Z108 V7 z) `! P5 \! r% K: {! g
    11
    2 n; s0 z* A* Q: P& G1 z124 x! d9 o) V+ s* O, i: C
    13
    % R# ]" \! ^+ k  v- F. A14; x% @) g7 f  b& V0 X) T5 i/ E
    151 B! M2 E  n) \! D) _  w
    16
    0 h  f9 o7 k% a. I) f17
    0 r& r. l1 _* K18. t3 e7 K3 T1 B' i# m
    19
    6 C% z1 T7 ^% q8 c% v. R: o20. C0 Z! m5 U! h
    21
    " I  L" p. }4 K22
    ) w0 E4 z; o( ]/ }23; X) R# r5 q3 ^% x/ G$ |
    24
    6 R4 y" M3 }" m. r2 D25
    * V$ V, A7 @6 R6 a. i2 M) |; U% k26/ M( a3 x' ]" Y9 ~! z
    27
    , N! G* G* v* K0 k& H4 \28
    5 O, T7 F6 M' F& [: ]295 k+ J) V3 ^! q0 N; B( J( @
    306 U+ `* O: n& \! _
    31+ `/ p& |& P" R! y
    32
    4 N$ h% B% ~8 k% D9 b33
    % m; T3 ~& i: _3 q5 [1 ]34
    ' _3 ^, b% S: z3 l1 r& P) D- F358 P% v4 S3 x' V. K; {
    36
    . {* s' I4 x' L9 V% m* {37
    . z) T6 l2 N* q0 f  K8.2.3 简写字符集
    1 a9 R8 Y$ N* r. s% f% m则表达式中还有一类简写字符集,其等价于一组字符的集合:
    6 n! z% ]' g( d& Y5 {; `: C! J# J- d. s  {4 W8 X
    简写        描述* m+ h3 E% b: W% S$ w
    \w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]
      ^) E9 `3 J7 N\W        匹配非字母和数字的字符: [^\w]/ [+ J9 B4 ^/ ~$ O) V) P1 ^
    \d        匹配数字: [0-9]' h# e- M! J/ ~& T8 E7 n8 G
    \D        匹配非数字: [^\d]
    / f: U0 b6 K3 ~1 D/ j\s        匹配空格符: [\t\n\f\r\p{Z}]
    3 y3 h- e- O( O$ C\S        匹配非空格符: [^\s]* }& J5 W  x3 i0 r  L( X4 ~
    \B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。1 v: ^/ w, G6 m5 X
    re.findall(r'.s', 'Apple! This Is an Apple!')7 M6 k  U! \. B0 V5 m" O, X$ N( b
    Out[37]: ['is', 'Is']
    - C, n( T; B4 z; _5 V
    : @( {* G* E. {/ u+ w: `re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次
    # r. y) a" M7 y4 p' OOut[38]: ['09', '7w', 'c_', '9q']" z) q- }* W& y# |# ~6 q: J
    % D+ l# i: C$ i8 I7 i! S
    re.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)& A. W( p! S1 u* Z+ C
    Out[39]: ['8?', 'p@']% l5 M3 D4 }% d* H! F1 v" j

    3 ?. [5 \4 J- W# d  z' Kre.findall(r'.\s.', 'Constant dropping wears the stone.')" y/ P* x3 _1 e% j& |+ t2 {5 d: L
    Out[40]: ['t d', 'g w', 's t', 'e s']4 d, T  H, V* G" U) `0 P
      P3 T- Z2 t" r; m, w; Y
    re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',( k/ \; O, x6 k2 k6 `; i
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')& W: h* U2 h5 J
    6 W  }0 a1 r1 C% z& E+ Z. [9 M5 N
    Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]
    ( `+ X& k6 x* h, z" |
    ) @+ K* Z$ w/ C4 L3 i! v; s  I' Q11 |  x( L9 w- @. g0 ?2 D9 m! M
    2$ t8 L% e+ d" t& `! t+ Q
    3
    0 g8 c: w* y% h+ h3 F: V4
    / C5 P- X$ ?% k5
    , ^# P0 d$ x5 A+ j5 [0 T, {: r! U" ?6
    . K2 u! D# e" ^4 ]" p: y7( e; W% F8 ?3 l$ B3 N# q
    8
    7 c! J% S* ^' w5 f2 t9
    6 C) S: D" d; F5 C( h10
    $ w+ B8 }0 S6 t% l/ X11: U( T: \1 o/ k' T& z* g
    12
    ; x' B/ t" O5 g' u/ Z' V) ~13
    % I& ^6 i$ N. I' {& F* |( a' K14
    ; b& b1 S+ n) v! o158 P9 k$ k% G; f9 |
    16. v9 }# h. G$ l* l3 Z6 {) R0 A
    8.3 文本处理的五类操作
    9 U# a4 j4 }9 b$ ?7 g8.3.1 str.split 拆分
    3 o' D1 J" b8 J0 K/ q' M+ ~# f  str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。
    - F' L0 u) z& w4 e
    0 H! x* }! H. H8 `+ Bs = pd.Series(['上海市黄浦区方浜中路249号',
    * L7 w' W' q7 I            '上海市宝山区密山路5号']): }- B+ T) t3 m& t+ ^! ?3 p, W: K
    $ P( I4 w1 s* X9 M% @0 _) t2 F
    5 K5 g% V/ N3 L4 f# s& Q
    s.str.split('[市区路]') # 每条结果为一行,相当于Series
    # o' l% s% q$ D" yOut[43]: # ^1 W% p, q. `: {: O) y
    0    [上海, 黄浦, 方浜中, 249号]  S. w  D6 h/ c% T9 P6 v, Q% E8 ~
    1       [上海, 宝山, 密山, 5号]
    + X7 I6 X: C4 S" m1 ?2 I2 A5 u7 }' tdtype: object0 M; i. u, |7 T$ o
    9 w& p9 c. p0 J8 t$ w3 r4 [
    s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame
    4 b; w1 O. c8 c+ eOut[44]:
    ! ]/ z" Z! b, W8 L8 F/ \    0   1         2
    7 ~' i) b" [* l0 ~* h) Z0  上海  黄浦  方浜中路249号
    9 J- d) t: u7 s0 e1  上海  宝山     密山路5号) w5 {5 k2 }* Z  j# a
    1
    9 I; }% N1 M7 N, P1 K5 C" s2 l5 s. c2% b# A- N7 U" \2 |3 p4 W
    3
    4 S; `4 o1 g& J0 k0 N2 [: D: S41 q' ~' I$ ?  k$ H) o  I2 Q7 R# m, \
    5
    . o" ^% P1 |; w- a% }6* i# o5 l3 ?+ o5 \  C
    7. ^8 b/ T$ E5 U& Y5 ^* d" R0 t' W
    8
    8 \. c; n/ J$ o( v. V! r98 z5 O. q5 n1 y  \3 v  W
    10
    ! j8 [. u( C' f6 j! s3 p' U! x11
    . Q8 O' ?) Z8 ^; ~! F+ d$ U124 I+ A. `( U6 O9 v' W! \/ G
    13
    - s; U/ A5 U6 R  f: I% R% [14
    2 |, ~$ K7 P. Y152 D# [) W* i" z0 H1 ]9 Y
      类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:( ?$ S  r% s% t6 n8 x6 `" D3 G- F

    + @1 s9 b; z3 c# N, R* D1 C# N4 Ks.str.rsplit('[市区路]', n=2, expand=True)
    6 j) a5 i" @* C$ m: _5 y8 bOut[45]:
    * C1 L' g/ e5 E) \" u! g& S                0
      X+ V. L% H5 N0  上海市黄浦区方浜中路249号
    6 g  ^' T& V& w) a8 x4 S1     上海市宝山区密山路5号/ E% B0 H/ i) i: f5 k# a& i; D$ @
    1$ H  D/ T/ r, K9 Z
    2
    2 B0 o+ }+ q8 n( K+ k3( X/ o1 r+ m1 i& t/ R
    4  {; q: P" G; R) y/ K; E, Q' z# n
    5
    8 X8 _$ T9 r% ?8 |/ x3 P' n- `& _8.3.2 str.join 或 str.cat 合并
    $ R6 p& I5 T1 B1 E) Z9 t) h" Sstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    ( N" ^' [( V; R3 \, `! ^4 Dstr.cat 用于合并两个序列,主要参数为:
    ) M9 b* x: O) Q8 W6 O7 ?6 ]; E' bsep:连接符、. a  u$ t& v( ^; T' E
    join:连接形式默认为以索引为键的左连接
    . B  [3 e" G$ r0 n0 H8 L' `) Z% sna_rep:缺失值替代符号
    ) Y- _2 m. ~5 Os = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']])
    * N; E# X' _& X+ Vs.str.join('-')5 D3 j% `% b/ z; w: t3 g; i: u0 C
    Out[47]:
    ) E6 K/ z* M) ~) z+ u8 b: O( i) V0    a-b
    8 ]+ l! |6 ~* Z. C1 g4 D1    NaN: z# k$ n9 t3 O( L' n$ C# v
    2    NaN
    , h3 o5 C8 ~0 [7 |! g1 zdtype: object
    , i" j5 h( a3 N# `6 c1
    - L7 Z7 O3 m  T2. O7 p! M$ q0 E$ u" t
    3! ^* e9 Y2 q) @1 t* n
    4
    0 J- Q# R0 ?/ v2 Q) L, z5: O& p% b$ a2 U4 P* u" _
    6
    & F9 o( T# t7 e. a7
    + L+ e& W) m" q% u: |  A. Fs1 = pd.Series(['a','b'])2 V7 t2 F* I7 r5 e1 T& U
    s2 = pd.Series(['cat','dog'])6 k; X' K' p( l( K8 Z: q
    s1.str.cat(s2,sep='-')" r8 |7 k5 O  F4 V) G' r9 \
    Out[50]:
    3 @: U3 E6 o2 \0 U6 x0    a-cat
    ) L5 t$ @& g- M0 @1    b-dog/ f1 t2 W6 ~1 |, _% N
    dtype: object
    ) W# P1 \- K  h) r# N0 C" P6 ~) s) h2 A. W
    s2.index = [1, 2]  v: F" x) ^/ P3 G$ c! ]  S
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')
    8 c+ ^7 w4 e3 p* s4 J8 qOut[52]:
    ( ]3 {+ p# b7 {! T& B* ]3 i. {; U  {0      a-?
    & \' V7 G) b* \; Z1    b-cat
    ( P8 Y( W* `# X: `( \0 [2    ?-dog6 F9 n; u" P+ e# _) b" u* P
    dtype: object' J1 [+ x. b4 a/ R9 l. e
    1
    ) i! [2 @# a; y. i$ X# D6 l2
    9 e3 H' X9 l7 @4 q! H1 v3
    : x' y& t* X' p( }. y4% C% U9 F4 d! @* i, i9 |
    5
    8 r7 E  G. z- }( ^2 ]6% `! T, [. M' w
    70 c; K# d6 P+ W
    8
    * f$ a$ x" H6 B6 e& Z99 t% ?+ z2 c$ W* I$ o1 p" g$ ]3 B
    10
    2 Y  m* J& |1 D5 `! _/ \$ q11# P, _. W9 J+ Y
    12
    : ?1 l! k5 C% o6 g. x2 r" N' S( V5 w* o13
    1 p0 x5 l# p1 a* x; c! w142 @; o/ P( i/ w, e" I9 r3 m
    15
    0 m! k+ z2 f; {3 ~& f3 z8 [' n8.3.3 匹配  B2 P% d2 u5 A3 J
    str.contains返回了每个字符串是否包含正则模式的布尔序列:: M5 K% I/ A  {# f9 d" z. D7 X5 H" l
    s = pd.Series(['my cat', 'he is fat', 'railway station'])
    ( P. h1 s" Q! Is.str.contains('\s\wat')* g8 V9 }3 Z% U' `! k
    : m) e& O& S) t6 }9 r
    0     True
    ) [6 _4 A8 j4 E; Z/ e* x+ ?; v1     True
    4 D6 k: @6 P) s2 P+ J2    False' x$ B) N3 _3 a* l, V
    dtype: bool
    7 I3 G; c( |. H! ]: Q1 Y* N/ V1
      h8 J% |; ]0 X6 |# W23 W" D7 m( s$ K0 `/ G- D6 Q
    3! X* v1 Q$ T+ `& N1 m
    4
    # f! G. l5 a; e55 o1 E5 a% x; Q) F
    6# J: m3 E9 ~2 j
    7
    " W4 G& T' b4 w. vstr.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:% a$ B1 V# q; c
    s.str.startswith('my')" M; k5 \. g+ Y& V# E, [5 E
    3 P, g) @: @' R# e2 q" E
    0     True
    ) m( U- Y5 D0 [  C2 E) }0 T1    False1 O: o0 L) Z% }9 c
    2    False
    / w! z6 H" W0 z1 X+ J0 Jdtype: bool
    2 x+ j+ N! s' X, O2 F$ D' i1! v# M& L% I9 m1 w( X  P  u
    2
    4 }2 W& k" p6 L# N. l3
    & S, l# a! m: i0 F/ b4
    / c2 C5 r  E' x  g5- u) L6 I# e% U( C
    6# h7 \- c- R+ K8 y/ ^1 M
    s.str.endswith('t')
    ! n8 R. v. q. x4 N: t0 Q
    . f/ M! C6 |9 A0     True
    & K- z# c2 P, z" J6 f8 u4 J1     True' E5 q/ K1 y) M
    2    False
    5 Q" `; v8 b0 m* \. C6 vdtype: bool4 j/ G( ]( L* L7 \. a: b
    1$ D+ g% G' ^" W( a4 [" `7 D
    2& _. C& T$ h' @/ S9 j( M
    3( n1 H" |  ~$ a) v% ^
    4' w0 F; ]2 {  a7 k. a
    5
    ( b4 U" A  {% e! x7 o6
    3 ~% s7 U+ ~9 L" G3 zstr.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)
    4 N7 S2 ]2 ^; M) C' Qs.str.match('m|h')8 l6 b" ^2 F% G3 G* h$ J0 ~
    s.str.contains('^[m|h]') # 二者等价* [; h/ A* V8 R0 N5 i  K' Q

    * }* y8 T8 l; G; C' R; G0     True
    : p- g4 i+ i9 |; a7 p7 Z1     True
    4 c7 T( L3 V- Z8 E. ]6 M$ e2    False/ C- S  {( u/ f) h1 o* @6 O
    dtype: bool- E+ z: l8 |5 T
    1+ D" R0 G1 \* g7 Q$ R$ H. q+ Z
    2" H9 m5 |& b/ }9 C& p
    3
    3 ^) J; c7 h7 W0 n  @1 N' Y- `% ?9 I4
    + T' r! e$ a" q  E& }9 J/ \$ _56 x9 a2 K. D5 k! s8 v! v- L
    6
    - C/ I+ H% E! `' Z' I1 l71 c- @+ Z* x- B; H- o% C
    s.str[::-1].str.match('ta[f|g]|n') # 反转后匹配* d6 Q9 c' j  s8 V
    s.str.contains('[f|g]at|n$')       # 二者等价0 B8 |; J% c2 R
    % Y* |- b. T8 R; D+ T/ i8 K2 C
    0    False3 O1 G9 G% m( Z# V) `8 T# x
    1     True( S6 g! S9 h: D8 c5 i* m. d- q
    2     True
    3 E1 a' N* Q6 f. _! ], tdtype: bool8 u! z" r+ }9 B
    16 k% J* f8 x8 l8 ^5 E
    2; u* `/ d' S! K" A
    3% O+ X* U) r. E& o: R5 K9 }/ T
    4( C9 t5 [& B  u9 M1 F) \
    5/ d6 d* v0 C, i; D# i  g6 p
    6* o1 I% a1 V6 A9 X7 |
    7  z2 r2 V; f8 H
    str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:; O. Q9 d6 L! a# w/ e
    s = pd.Series(['This is an apple. That is not an apple.'])
    # k) J( x" m. t% x) X) ?& I
    * [# F) v! _+ ]7 a5 }. R& O' v( Bs.str.find('apple')
      H1 \1 O! f6 c4 w% wOut[62]: 8 r5 U6 M" ]" O$ b
    0    11' C8 n# @, x' ^8 ^6 t* v! |
    dtype: int64
    : b/ e* |5 r6 {, c& d# Q$ C' h; M+ {$ p! n
    s.str.rfind('apple')
    4 y  R) r0 U) y4 ^Out[63]:
    4 a4 i3 ?# z5 W' ?+ ?0    33
    . G$ Z0 ]& I$ l7 {' Jdtype: int644 A# r, y$ P! O9 j
    1  ]  s. N; v; _! y- Q( d; S* L/ Y" U, {7 ~
    2
    & o  n$ A& ]; K, n3  U) p7 s: H, n1 b
    4
    ) T& d( X) Z: u/ |" N$ a: i5
    ) t% {: `  _, F2 @: l5 k6
    6 C- d; P* @7 D6 k1 g9 z" r3 Q78 v- |' U+ `& H+ s5 Q" F+ k: Z
    8
    , k) S5 n. ^1 @8 Y4 g  k. _9( E. ]2 g# i# K: Q4 T0 }% ]( |
    10
    " p1 C; t7 W+ K4 N% x. f11' s) g) j# R/ C% K$ K4 y
    替换5 ^4 k6 x, p, }
    str.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    5 v2 Y8 t1 p. V0 i* rs = pd.Series(['a_1_b','c_?'])
      l4 }2 }' e# U) F; m3 m2 H# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?5 i3 d8 g1 z* z  ?* e1 {* k# m1 a
    s.str.replace('\d|\?', 'new', regex=True)
      u/ L& u8 E" S) [# Q0 P
    9 q7 _7 X0 {; K4 ~1 \0    a_new_b
    - T% x6 G. N9 Y2 w1      c_new
    / \- G0 P' E* Q  g7 xdtype: object/ _" h3 E# v+ `* \
    1" y+ Z  U( y! [0 v
    2
    0 y' \' N9 W" ^9 v9 p4 f' N3
      G4 z( Q. l: h4
    3 K, H' W: @( X$ @; U5
    ! V8 p) q) U# W' H64 z/ l# k  B4 Y6 J; E
    79 [1 m! h* \; `3 v, x$ X# `
      当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):
    1 _; k4 J2 T$ W& s6 q* @- U& x" O& T& A
    s = pd.Series(['上海市黄浦区方浜中路249号',
    7 f  r* j, N, D/ x4 }1 p7 b                '上海市宝山区密山路5号',& w# Y6 r) x  Z3 _& r
                    '北京市昌平区北农路2号'])& M% L# j$ S4 D) z# H8 _
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'  m( e$ Z& w, ]% P6 @
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}
    % N: u+ l6 ?8 U$ j  B7 {. y8 ?6 \district = {'昌平区': 'CP District',3 O* I, m/ T8 B. {9 q
                '黄浦区': 'HP District',- t# q7 v( o: e4 {5 Y% e9 a
                '宝山区': 'BS District'}6 u& j- \6 q, E6 D+ `
    road = {'方浜中路': 'Mid Fangbin Road',
    , _8 [) g) [4 S; q' C' k        '密山路': 'Mishan Road',1 Q4 c. T8 N# _% Y1 d4 O
            '北农路': 'Beinong Road'}  m. Q5 Q3 }$ k" i; c* @5 k
    def my_func(m):& j& l# N& c7 @& |
        str_city = city[m.group(1)]8 A. y$ S2 @) M1 p
        str_district = district[m.group(2)]% X. G+ B& `& f% y% @
        str_road = road[m.group(3)]
    0 S3 U2 r; F& M, w" U$ o; ~% A5 t    str_no = 'No. ' + m.group(4)[:-1]) M8 X0 Z2 P5 G' p! [
        return ' '.join([str_city,
    7 S' H9 v) J- O8 `; x' W: x                     str_district,
    6 f. D2 F& P1 L; o# L: o& q                     str_road,+ w" m4 Y: ~/ [& i: u
                         str_no]); P2 i' a; u' w
    s.str.replace(pat, my_func, regex=True)( S8 q! N. u/ |- ^( r0 \
    ( |& n4 c6 [, L
    10 a! p* g# R& n1 H
    2
    0 V/ R8 i: f$ c3
    & f/ f9 Z1 `/ ?8 H% c; S7 |, Q4
    : m. H, Z1 _- `& v2 h56 V% U/ d9 y& ^6 N- U8 S
    6
      l$ t" L0 W9 L, M1 T" g2 p7% U7 a2 K4 d. V$ ~* m% W; y
    8
    : L" Z0 u% Q$ @9 V) E2 H9
    " d, g' p2 J! I10
    0 j( y4 d7 e3 O11
    & e2 f# c) w" G+ W  C+ E12' `  L0 D+ d( `2 Y
    13
    ! H/ p; k" G6 H3 ?0 w14: i+ w8 D2 ]$ f% k, f7 }7 ?
    15# P2 H$ c5 L/ W3 w/ s$ _6 m) K
    16
    8 N( L. M* p1 Z7 Y. a; I. x178 @+ J: i- c, P2 [5 k7 ?
    18& e3 {* [# {- f. a3 X
    19) w& O( O5 @& ~( M
    20
    ) [) h" @2 ]6 n21
    ; G' a5 J1 Z- e$ j- w0    Shanghai HP District Mid Fangbin Road No. 2498 K; v% d5 y+ _$ r' _
    1           Shanghai BS District Mishan Road No. 5
    6 _3 P* W" E0 `2 z2           Beijing CP District Beinong Road No. 2
    / b: v/ \7 ^3 n# Ldtype: object
    ) z2 K$ B; d# G' q# v2 O$ t1! D0 {' }( V& g6 [- z
    23 M7 i/ L7 \& O
    3! f: G- p7 q2 s
    4) e) Z+ p! Z! e
    这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:4 O! X1 p8 I) K" n

    ( R9 U  [6 s4 C5 m# 将各个子组进行命名; L0 U3 D$ Y! w* m0 f4 x$ |
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'4 [( q% ~$ u9 @) L
    def my_func(m):7 ?/ E4 a# Q6 H5 A# O+ ^
        str_city = city[m.group('市名')]
    ! f' [4 p0 ^1 F. g! u3 K    str_district = district[m.group('区名')]8 z+ M9 L. F+ o8 ]9 G* a' I
        str_road = road[m.group('路名')]+ G2 Y% e. C, ?8 ^
        str_no = 'No. ' + m.group('编号')[:-1]
    6 ~4 n' W4 s! l/ H0 N. N% [4 |    return ' '.join([str_city,
    6 B5 T3 J  V: ^$ W                     str_district,
    ' M4 S$ m% r. B4 P8 [                     str_road,
    ' I, ^. y( l$ n2 @1 g; A                     str_no])
    . ?, [: Y/ e( p1 b- q1 ~s.str.replace(pat, my_func, regex=True)
    : ^$ n/ |) I( U8 ~, D1
    / t; Y- [: N7 S7 c" _, v! t3 Z- t2
    2 G( N2 `, ^) S: K7 M9 @% ?) F$ w3  g8 C- w0 w- ^- i( }2 v; J
    43 K3 S6 f# e* p
    5# U5 U7 @5 a2 R- X
    6" H, J; x0 n' F4 C+ a
    7
    3 B" R  {0 W( n8 u# E% e88 K" C% t( A; ^: m
    9
    4 A( h0 Q1 p5 j" B10
      w2 E7 I9 T* L( D3 N% t11& d7 C2 f9 `0 A7 a3 {+ c/ O
    12+ U/ h6 W/ R% L& n/ r
    0    Shanghai HP District Mid Fangbin Road No. 249
    ; S3 H: z5 S3 ~; B2 x1           Shanghai BS District Mishan Road No. 5% N. t3 d# n  \7 C( L) z
    2           Beijing CP District Beinong Road No. 2
    # p7 a! p. i3 F: W, jdtype: object
    0 h$ j2 x  F% Y4 j) @11 ~! U+ E$ }. E' N
    2' N8 A9 b  q, j" E9 \
    34 |' v* H  Z. o
    4
    : x* z! k2 p0 a  这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
    % U" |1 N& R) k5 @3 P
    % {; m4 q0 c' y; ~, c8.3.5 提取
    * F9 I, \( a6 d2 u( X" i  L/ zstr.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
    ) {/ [  ^, x+ h" E' T& F2 rs.str.split('[市区路]')
    ) M% Z2 ]) R, B% Q  rOut[43]: $ s: b/ N! U& ~; l
    0    [上海, 黄浦, 方浜中, 249号]
    8 L/ P+ a9 `" N+ A1       [上海, 宝山, 密山, 5号]* ~* I$ Q( _  P' g9 G* U  K# @
    dtype: object! u8 y3 Q: m$ L( S% v# P
    / _! S4 i7 I4 ^+ W4 U& N
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'
    % X0 l# \, m, \5 a7 Ts.str.extract(pat)
    0 \; ]+ u! |. S; J4 a0 b; `# dOut[78]:
    0 n3 ]! q7 W* @5 a6 r- J( J7 ]    0    1     2     3
    , c7 ~5 I( A; u0 u8 f) }- f% X: j0  上海市  黄浦区  方浜中路  249号
    ' Z$ Q6 t" S" J9 [1  上海市  宝山区   密山路    5号
    ; o; r3 d! ^$ j0 ]0 W, d$ E) K2  北京市  昌平区   北农路    2号
    9 A! y& o+ J2 {7 Z# ?& e/ Z8 v1
    ; x4 P9 o& J* j9 }3 |" M# w! b28 j9 [, }& |9 z1 @
    3) L) i7 _- {) V' F0 o9 S# {2 k7 X. t
    4( }# B6 ^, [  r  ^  f1 z3 s! i
    5
    8 j5 {* @9 I. G1 [( j6 k6) N/ W) e7 l$ K6 w
    71 t* d* M4 e- j: G
    8
    2 u3 r2 m" v9 ?" L$ N9
    + M! B: F+ B3 X/ j10
    0 m# m& v# |( O- G3 K4 a6 \11, ~+ u1 l" c) p
    12
    , N  c' Z( G, e4 A' L0 T% d" x1 s13) O1 }: L+ X# Y* e8 @! V; Z
    通过子组的命名,可以直接对新生成DataFrame的列命名:3 e1 h/ L. a; _
    ! d9 q8 M" a& o  p$ S
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    1 I: n7 ]" [7 e$ p( Zs.str.extract(pat)
    $ p! n9 D+ h( f% A. t  F) g% [Out[79]:
    & {7 L2 N' G; L. k    市名   区名    路名    编号
    8 h5 I* D# T8 v/ i# }0  上海市  黄浦区  方浜中路  249号" m4 B( h; p* L1 h. ^
    1  上海市  宝山区   密山路    5号* c3 x+ F9 Y0 {* c6 a: A0 O
    2  北京市  昌平区   北农路    2号9 I! ?( d8 d2 h
    1
    & k0 I2 P3 b6 Y/ ]1 T( ~2( l( C5 \- M; e) x" N
    3: v: c# ^# T' O+ E
    49 ~! N. q7 |" w
    5
    5 ]& K  Y' z! o+ z6 G6
    ; f# B) b8 ^9 C9 H4 Z0 Q# x2 _7, z8 K* B, q* [- T5 M+ P
    str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:% C& s2 J* d2 _$ M! ]
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])
    . t0 f1 E$ D* P% i5 Y# Fpat = '[A|B](\d+)[T|S](\d+)'
    - V* G/ r6 s+ N: d' _s.str.extractall(pat)3 B- }$ G9 a- m# I0 D
    Out[83]:; k! I' Q$ ?3 w# C& L; ^6 D
           0   1  @7 @' m$ F2 R& {5 d/ r
         match         
    4 ]: L: E$ K  a+ q- c' Umy_A 0      135  15) n' i6 g3 `$ d8 t3 r1 p7 e
         1       26   5
    0 K8 Z& j6 l4 |3 f- V) `my_B 0      674   2* L6 r/ u) s# `
         1       25   6& i( c$ _& g" Y* I& e
    18 ]$ Y( }; P; k( N0 n: L$ ?
    2
    0 c5 I8 m$ W$ i6 M) h0 D2 l7 S# u3/ Y8 ^3 F  _- J7 I
    4
    : K9 s; X! E4 ~% y# W- x* [5
    - e$ a6 @8 C2 {7 G/ M& s6/ J! L! z& N- K/ g2 Y' Q6 G
    7
    $ o' j$ r9 r4 l3 z. I4 y0 s, O2 x8
    * D) {/ A6 a8 }6 |9
    " D/ K- _! {: D$ h# H10& n- ~1 ^* q3 n1 U: e/ J
    pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'$ R/ b: R+ _3 `5 w( {! C
    s.str.extractall(pat_with_name). \, e  e; L9 S/ f/ M5 t
    Out[84]: $ _1 V9 F- R$ n& ^- K& T
               name1 name2
    ! A* L" [" D+ G! y     match            5 v. {  K' C4 z6 l- s5 w# r4 z
    my_A 0       135    15
    1 m, J2 b- D* a& M0 [     1        26     5
    0 E6 G( T) ]* i1 tmy_B 0       674     2
    + M/ o+ v& }/ g4 V' ]     1        25     6
      G% }7 @5 T! t* C% X& Q7 h9 N4 Q1
    * Z7 c. B# G( l$ ]" F( X  ^2
    1 z. y3 z7 ^9 l/ }3
    9 H" V; M- W" W! s- _% V5 E' j4
    $ M$ P  L- y3 i1 d5( y% |# Y% S' H& C+ e# O# _
    67 p& I5 V& c, P7 X8 h! Z
    7
    ! l% @; D! W0 c2 E) w8
    6 B6 a" T! A2 ~$ o- Q9
    9 ~$ h7 U; J5 ]1 Y, qstr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。- ^; G, E1 D/ M* N
    s.str.findall(pat)) \, }$ |/ q+ G- O2 C, |
    1
    9 }" m5 x+ C; ^my_A    [(135, 15), (26, 5)]
    ' R9 O- R& Y$ }5 r* gmy_B     [(674, 2), (25, 6)]7 E2 _, Y( e( o9 [' p
    dtype: object
    5 ?' |+ c; k9 }; |: Z' x1
    * A# C4 g7 Y/ S  ]" J2
    6 L" e5 Q2 M% M+ \+ \8 o% q( `8 b3
    ; a7 z3 C4 v" z0 U# u8.4、常用字符串函数
    , |1 }7 C( E, N( P$ H( ^2 G5 D  除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。7 M, D9 G; T# V& H- [

    9 n" `. r1 P: }& N" O8.4.1 字母型函数
    6 L7 C0 A1 A) P  upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
    7 w9 P: E8 X8 {; h8 {* n2 D+ t3 H
    1 u. n: b. L- v  ~0 T5 rs = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])% A# t! X* H6 \4 y+ Y0 Z
    % h9 X4 ~3 m0 a; ^
    s.str.upper()
    3 N1 Q1 R9 V. j3 [7 f6 m6 w- xOut[87]:
    - z* i' Y' u, |0                 LOWER
    $ f: T. ~. |: s/ e1              CAPITALS
    % Q" {' [! d8 M; |8 B2    THIS IS A SENTENCE+ S; W# {6 ]5 B
    3              SWAPCASE
    6 B, I6 g0 e5 q1 \- E( Hdtype: object& u1 g, o  G+ Z( M; E" U

    $ R3 z1 N' E3 [' ?, s5 h/ o9 Vs.str.lower()/ C7 H  G6 ^2 t$ R# R) V6 D
    Out[88]:
    $ X9 P" S1 J- G( q0                 lower* a! M4 b' v, m  w; x
    1              capitals
    / p7 l& ~: ~& l. e2 X2    this is a sentence
    4 Y/ e! r5 I- n1 H+ R* G0 _# B3              swapcase+ y& G" X  Y8 o1 ?
    dtype: object" \3 N2 Y: W( M. C- `
    ; B0 D" U0 Z$ b: I3 c
    s.str.title()  # 首字母大写: l9 m+ d- s% G7 V1 O! N
    Out[89]: 7 g$ w5 q9 n* Z7 X+ J1 S
    0                 Lower
    : d1 G) I' g1 }' g( W1 R1              Capitals
    ) e5 \. o# [7 c2 d. H" a; g2    This Is A Sentence
    ; G8 R7 t! Z0 |3 l3              Swapcase
    3 ]2 V/ \  _3 j( }9 ^$ w* Vdtype: object) E! G# c! R5 z& ^/ I# [

    ) |4 {8 V5 m4 b' P. M8 B# Is.str.capitalize()  # 句首大写- E  a0 O; q$ Q  q+ n
    Out[90]: ; w) O! _& m, I& s# Q, _
    0                 Lower+ d8 c# L" i$ \* v1 _8 Z* m  O2 _  b+ E, d
    1              Capitals: F7 K! \% J% c) L/ O
    2    This is a sentence
    6 F; o& J" z* i3              Swapcase2 _$ J% h0 K" k! ~7 J0 m
    dtype: object' g4 H% N! H6 w% T; ]- g

    0 B' b, k3 r- G( F3 ]8 s, ds.str.swapcase() # 将大写转换为小写,将小写转换为大写。
    " c/ K8 K. S, r) v3 POut[91]: 3 J$ C  r8 R3 m: S7 \3 r
    0                 LOWER
    ! u  l+ n6 G( ^8 B1              capitals
    3 o1 X8 }' z0 `7 T6 ~2    THIS IS A SENTENCE
    3 h* w; K0 u/ c  b3              sWaPcAsE; b% K# R4 g  n" o+ j  a& R4 w/ Y' m
    dtype: object
    4 O# l6 t/ e- m. j3 |# t6 Z/ C3 i1 ^, P4 ~; i
    s.str.casefold()  # 去除字符串中所有大小写区别- `( h- P4 ~, p7 k2 s# j
    & z. Z% Z5 a9 q$ `
    0                 lower
    ; m& R# c2 S$ R2 ]1              capitals7 m: W5 N5 a# X
    2    this is a sentence9 Y# v0 j% c2 W5 o
    3              swapcase
    . H* m# n* k8 X( f& J5 G0 z3 I2 S& B# ~* ]6 d. g9 T0 ]
    1$ s+ N# A6 \  n5 B1 S* M! @
    2
    - _" K/ |2 O# f/ n. ]3- a( u( u( P. `& A$ e" D. e, r
    4: F, L7 C6 J" i& F. r
    5% S, U$ H0 i6 W9 t' t/ M
    6
    + E! h, W; ^6 {1 D77 y/ w" D$ M. b; V6 h8 O6 G2 |
    83 h" r1 G2 ~! \$ }2 q
    9
    9 w1 x. _& D$ s2 G7 ?- E10" w" v" l+ x6 D3 M: x6 |
    11: k& K: O$ \& _* f3 q" L8 t
    12
    2 s$ m. u. X& B13
    6 P" _% k. c( ?/ a$ X$ r" v14& y$ k2 p5 R# i* C! V5 g* `
    15+ F8 ]7 ?2 m# s% H  f
    16
    : R# h2 T1 W( e! u' {- b7 b5 m17. `8 a$ i1 m& j6 d, u4 s1 \6 c( A
    185 k! A) C' `& o4 d5 R
    19
    / N0 I4 b( h. j, h; b8 o202 i# _5 \$ N2 ?' P! r1 X- L: g( u
    21" p  ^, U: e) l3 G$ I$ ?, ]; Q
    223 l5 Y& s) n* N6 e, F5 X" E# t/ v
    230 Z7 N: C5 c! V
    24
    ( ^( @% W4 m" q( g: X7 g25
    . n# k* i+ P7 p; f- J$ C. G, H0 T/ o26
    " Y& g7 K4 c" k27  u$ g6 O# ]! v
    282 o& G3 Y7 H; R. {& j% o+ l
    29
    / s- N. |3 H+ S2 _5 T5 Y8 g30& e) k5 J3 M' N. W# R
    31
    ( \" v: E3 N0 r' ^# M- M32# h! F$ d! v3 p0 {' J- {
    33- D- N: \2 U  J
    341 X- c' ^! Y0 @& E8 t5 z
    35
    - Y1 }# [  d. d/ D36- `" Q* E) C$ w7 j3 k0 o
    37% H! t6 X+ U1 l3 v
    38
    9 j# ^/ `. c* J% V4 @39
    ' f5 a# t2 m: Q- v5 i- B1 W: ^: b3 @40( C* v: o0 L, @! |8 D- K
    41' {7 @/ z/ F7 G' `6 x) q4 C
    42
    ( D: }' N0 d9 ]; N43* B' p+ E) N1 o' O# q7 m3 Z
    44
    3 b! p1 k3 `% K5 `3 _. U45
    , X3 n+ j. D9 ]* h# D46
    7 n) s' E0 b" @% ]3 [9 ^! n474 v( N4 ?9 L% H7 j% H  e
    48
    0 a* @/ a$ k( g1 e. c' ?8.4.2 数值型函数
    2 {: g! i$ v0 i# I& h  这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:. [3 W  }# P" w/ q
    * q6 d$ O' p$ E7 Q2 s' }
    errors:非数值的处理模式。对于不能转换为数值的有三种errors选项:7 `! u/ G8 J# F' c7 [" }. ]
    raise:直接报错,默认选项8 z) n& \3 ]( X/ y
    coerce:设为缺失值
    ! o" l# J* {. a1 ?' vignore:保持原来的字符串。3 E6 C. f; q  A) D, C. H: Z+ [9 k1 y
    downcast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。0 N. a# c& C0 |* Q
    s = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    6 O) B1 z' o) I9 I( ~/ V" W: }' ?
    ; ~0 [( C& x: _5 _2 x- `( c! kpd.to_numeric(s, errors='ignore')
    / p, x$ ?+ x! yOut[93]:
    1 b+ A5 A" q. b. t& t5 o0       1
    4 w; s; G4 y8 J6 W, N! ~1     2.2
    & c# d+ w4 o3 J) M2      2e2 q6 o" r5 e0 d4 L
    3      ??' P* q) e( J& d. f% w# o" L
    4    -2.1+ ?1 Y# E$ C' J
    5       0
    ) D$ A6 ]( v; qdtype: object
    - }8 U& ]" _# E# Z
    . ^0 `+ y/ ~  V6 r% f2 N6 hpd.to_numeric(s, errors='coerce')- o1 }5 G4 \- n5 Z' b
    Out[94]:
    ( p3 K2 @1 ?8 y9 K, x/ D0    1.0& \* y+ g9 N: i+ Z
    1    2.2# Y5 C. M; F' S/ Z( [- Z" B( `( D. h
    2    NaN
    ; S+ w4 D+ y) r7 u  C3    NaN9 l$ E$ t9 {7 q; \( [2 l; `8 G
    4   -2.1
      {2 G- m) b) k" B( Y0 p. r% ^5    0.0
    2 a1 @0 G9 ?0 y0 {1 G6 Rdtype: float64( d* H, y/ s# @( d0 T- b
    2 d$ Y% \0 d0 D+ s- b6 C
    1; n1 [% b( r9 d9 m' y, V- Q
    2( Y  R' E2 [2 g, F( D+ d
    3
    ; ^. a% S+ d: Q3 [: h# \4
    4 o4 x- X& u7 ?  E4 q50 M6 C1 }/ }) \. O$ s1 X
    61 q' S* l% Q6 E: t; G, |
    7
    : |! X$ e. l0 ^# c( a% r8
    7 B; \, a8 C0 _' y# h% q9
    % U7 e4 Q8 @3 l/ J/ ]: S* y10  |" x; ?" U7 e. q5 `2 Z  D+ e
    115 A, w# H5 X$ V5 \/ B* G
    12
    8 ^  s" L  T6 p5 E2 L13
    , h1 O1 |% F" u& A2 y* V14: P8 w; C0 ^) S* r
    15
    5 x2 p3 G9 G4 @% g  N; M; P3 n6 A160 Q/ i3 S9 {  W  B, v* F. C
    17
    1 t! B  @3 I5 c18
    ) d" ~# w. e% V$ i3 F192 _" o7 i7 t9 F' M
    20
    & i% m; |, ]) k2 Q. g21
    ) J# I5 m+ o1 ]& J- h. c) x  在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:% j/ F) d4 b. o! W2 ~2 x1 F

    5 Q7 h8 X! H! u* o# E1 i7 i" ds[pd.to_numeric(s, errors='coerce').isna()]' R2 L* y$ w  T! @: J/ Q' S
    Out[95]: , e! ~# ?: q$ ]1 T) E: o0 Z
    2    2e# I/ |7 ]4 l+ l* i8 l0 T! ~
    3    ??
    " x& {2 y% p: F" O) F2 `4 Fdtype: object1 E$ p( |) P. Z% i, b( }- u+ j1 A
    1# E  F8 S" S# h% M
    2$ J( |8 ]; x; k  P' k4 ]
    30 \  T+ J0 i: t7 x+ T$ q9 a
    4  {9 B# K! p/ u1 R3 t9 e6 S
    5
    ' W3 X. A8 P) f6 _" S  L1 `/ @8.4.3 统计型函数; v9 @' J2 h" g0 @5 v
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:0 k2 [# R; U8 M4 I# O, N, U

    0 n. K. h4 P4 U8 `s = pd.Series(['cat rat fat at', 'get feed sheet heat'])5 H/ S8 L" b: H  n- J

    3 o0 U* C. F) N& Fs.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次2 `  @, @. c5 ?$ t
    Out[97]: $ A- o' Y# w; V- l; E2 E5 o, e
    0    2
    7 g4 ?6 r  p6 O$ ?1    22 T/ h* c. U- U4 ?6 L4 g
    dtype: int648 p  \' J3 [2 b% j5 W
    % k4 g* h0 F6 H, H$ r
    s.str.len()
    2 D$ I+ U: e& _Out[98]: * T0 i% K8 X$ [* ^5 q4 `, J
    0    147 U/ n, f+ Z* L4 f/ @4 e' V
    1    19
    . ~. ]# C& V: \# tdtype: int64
    3 P' B, H) i" C- H/ a8 C/ v" s+ N1, H5 U, o- E+ _6 E
    2
    $ `2 Y" v' q& p; a3
    % L+ H# ^8 p' j( [3 T9 g: u# N2 @4
    9 e/ H" H- f% p5
    . ~  k4 h" Z: a; U6
    6 a( g; U& E- V- t7
    " ?' u, u1 c9 f8
    / Z# q( R, ]: e  U, a6 n" @3 x6 m9
    ! s  G6 r+ x) O10
    + {8 j( p8 B* |7 D8 ~- P) x11
    & J# }9 Y  |5 L; f4 V12/ U. A; f: ?! W
    13
    , l0 K% u- E7 |( _" ~( D8.4.4 格式型函数
    ' v$ Y7 P, [$ F  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。
    ( j+ S( X  ^$ ^
    # @' j% u' J- p- V5 Q; b: T& dmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
    - U; p! i9 t7 j5 |. f3 T1 W/ v/ z7 @
    my_index.str.strip().str.len()5 `6 V$ A- U( }  d
    Out[100]: Int64Index([4, 4, 4], dtype='int64')
    - c5 `5 m/ g: g0 @3 G7 r# B' u6 y# K) M. _" [2 r9 Z. K
    my_index.str.rstrip().str.len()" J' j! h  V2 Y9 w$ o
    Out[101]: Int64Index([5, 4, 5], dtype='int64')
    2 T" i* P$ z1 }$ b4 Y# F& o
    8 m& q: w# ~( B. n& R' d- z- Nmy_index.str.lstrip().str.len()
    ' z" D9 t" \! O: w% POut[102]: Int64Index([4, 5, 5], dtype='int64')
    + V/ D9 b. g( s/ [1 y: _1& {4 u/ u! ~: s) z8 |
    2
      J, \5 e2 k) h5 b32 e. }  Y- x# f9 {
    4( v$ a. ]8 w: z) d3 I4 b* z
    5
    + e' ~- y3 b- d6 c  \6
    ) c- c7 z- ^8 A7
    " v# T/ K8 O. x' [8. o6 d& b: _* w9 n% D
    9) ]4 ]+ P" x# X) x
    10
    6 [5 D2 x0 |. k- f  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:
    / o2 ~/ M& P" s2 l
    . }3 g- x- w- ?4 J; ^s = pd.Series(['a','b','c'])1 z2 R/ T% b8 m, r7 W3 Z, D
    # d& L+ d" w2 o* M' F$ x0 q- m
    s.str.pad(5,'left','*')
    & o. o: J! T) QOut[104]: 9 u+ _* x1 U. z4 _8 \5 k  d
    0    ****a/ H  t' C4 r$ E* P/ f3 T
    1    ****b3 S2 w3 Q. u* C9 O5 X0 t" W
    2    ****c
    ' @4 c% s4 e4 @9 G8 [! `dtype: object9 X6 E0 d0 W, O' d) s" V: G5 f! ]4 y
    ( c) q1 R( s& C9 f$ s
    s.str.pad(5,'right','*')
    8 ~' @- k6 o/ X- {& [Out[105]:
    7 `3 N0 @4 T& \) h# U0    a****7 x- h* U) C! }
    1    b****
    ) m7 N: D+ c, G2    c****' W7 D; Y/ O+ A  O
    dtype: object( i: N! u8 J0 F3 o

    " p( S( g4 S& l" }  Es.str.pad(5,'both','*'); Y7 v2 G9 F9 ?/ d# r, o( E* \" g( n6 B
    Out[106]: 9 h4 N( s3 E7 e- \" ]5 I
    0    **a**1 V4 m- T6 S$ M. W7 N* @
    1    **b**1 ^) N' e9 r+ y, i
    2    **c**& p9 O1 f7 w0 f' a4 i
    dtype: object
    5 m5 B' }& X. L8 p' T" l2 K
    / H- r, {/ n1 y  Y7 M1( u5 l5 [- p8 a
    27 I1 I5 x" o4 q1 h; Q+ R
    3
    4 ?# @: r. c6 q$ s9 C- {4
    " |: E  Y: P4 C5 D5 R: ~! e4 R55 @# t& o- c  e. H4 A0 m8 G- x3 l
    6; S$ u" D' ^: O3 `+ F
    78 `8 c3 ]3 g" v6 m- J7 |
    8
    ' z% K7 j% A& J9- t( K/ c4 P$ U  M& ~  Y; h3 R
    10
    : [7 Q+ ]& z7 [& O0 ]11; R: f: x' }: d3 Q9 d
    12- H. ]+ A' u  G2 Z% O
    13
    / D! J1 O0 C, M  O4 Q5 N141 x, n* _! \7 p
    15% J3 r2 E; a1 G! ]4 y7 ~2 b0 ?
    16* b5 m( p9 |  ~. M' R
    170 R4 r4 R3 Q1 H; J+ n2 U+ |
    18
    4 g* C( P8 s* _8 p4 J19# `: k8 R1 O* H7 f1 N8 x6 Y
    20" m6 L  v$ j& j. j7 q0 k& T, ]
    21# J/ A& V- r; h( m- M& Y0 B
    22
    ; y7 @" ?  s6 c# m& p; `  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:% f5 |0 |' }, p" C$ d

    1 u2 z" ]4 W1 m  ]; ?: Y7 l0 Ns.str.rjust(5, '*')% H1 f1 u' W; O' @
    Out[107]:
    . H. u# K1 I! \- B- S. \0    ****a# k3 y: F: N8 l# U! V+ {: F
    1    ****b7 h$ o; R1 K6 \# R% C2 t+ Z  D7 S
    2    ****c' g: m; H* g) B; S& t
    dtype: object; _; ~% O1 L2 u0 A4 f$ Y; d9 W* K
    , B6 ?  B. F) Y6 E1 B+ i
    s.str.ljust(5, '*'): t8 K  x9 `" l- e; v+ B
    Out[108]:
    " ?* O: _- @/ h4 q0    a****
      b: }  T. f( O2 R1    b****, v$ ]# D1 {3 y( G) K& v1 t- [
    2    c****% |/ M( ~7 ?# W4 W5 [4 g
    dtype: object7 e: ?7 c! ~- R' f0 `; y* M
    . e: l! i& ]1 l$ v4 O
    s.str.center(5, '*')
    ' K5 |, t2 A* y0 b( A! `( EOut[109]:
    - \+ ~9 M: b5 n8 `0    **a**/ A- E3 G6 |" i) Q* G( [9 t1 X
    1    **b**
    3 e6 c2 T4 H' D2 }8 o, w2    **c**
    9 y- H1 i7 n, O" i" adtype: object. c+ w6 Q! |% }7 l
    $ ]" `- n1 `) ^" t* K& }
    1
    1 N& b' D; ~8 @! c+ U6 D/ s. l2) o  q* p+ G" @9 D6 I: C
    3, J+ ?, ~0 q! j. Z
    4( f) o$ e8 M8 _. g! A$ ]
    5: H( G; r: }  p6 N' J) V
    6
    & J9 k5 f; F, n7 A7% o5 e7 g9 `  I) W+ n
    8! Y2 W  O/ W" x: J# h, L! ?
    96 W; T; r. o5 B. }  U
    10- K# o! V$ y2 N1 x5 l% d
    115 h) k# |# u8 l2 n1 |2 W
    12
    5 i& ]$ o1 |" J$ W13
    / _+ q; q+ i0 p4 b4 ^8 L/ i: w14" r+ [, y4 M& B/ f: ?" e& n
    15, r5 _% Z" K# o$ q7 |0 i
    16
    & v# g* Z  O! G3 J& K' u" b; M17
    5 d6 `' d2 G  J1 X' K0 c: N18
    0 O& ]0 [% n1 O6 }2 S% r# Y19
    % j8 D7 o& g4 Y5 ?5 s4 H20
    ' T0 E+ G  v8 ?3 m8 [  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。8 r8 l0 I. ^6 o9 H
    0 n3 G( N2 r# t7 o6 s4 a
    s = pd.Series([7, 155, 303000]).astype('string')
    * r% k, ?+ o# N
    " E. t$ y" a8 L$ \% Ts.str.pad(6,'left','0')& S6 O6 X% q- N' {6 J
    Out[111]: # g+ a1 g. k, p  j, U" y
    0    000007( G. d9 |; `# @5 S
    1    0001556 g% c* \' K' \( E7 e2 R
    2    303000* V. }5 }$ P+ \1 Y3 @
    dtype: string
    % u: V3 _) E3 ^- T  j9 p, b' d. Y  _% j& c0 l% Z4 F
    s.str.rjust(6,'0')
    / v" k: W1 ~' X4 U5 L  S4 bOut[112]:
    # E& |* ~2 ^* u8 f0    000007
    ( W# Z- g: m  m' y( d1    000155
    + d6 b$ J6 z9 @% R2 k: F3 J0 j# P2    303000
    ( b4 z4 |1 a! K  T8 p/ Ndtype: string# H8 Z, y# h8 Q  P; w. k

    " ?/ M& h+ S( a( ]9 Bs.str.zfill(6)
      c. V" j  z: T) sOut[113]: 2 ?, K6 M: t) D1 C$ o/ e' e) w
    0    000007
    ; J6 J3 L' R! |1    0001551 K4 J5 A( N' N) w0 W
    2    303000
    & Q  h- K( X* sdtype: string
    0 \# f$ R) c/ Q7 {) h7 s! Q. O9 r1 Z/ S) I, s# A8 f' x
    1' W1 u$ ~! t0 b; q
    2
    ; @- q% r3 |& L( X. r4 Z( m0 x, n3
    8 g$ c8 k0 E: O8 ~; ?2 Q3 [4
      A6 b2 u, e6 C0 D& L5 P5- t+ S( E/ G1 Y7 ]2 T
    6$ V4 ?  C1 Q! k8 q
    72 R0 Y5 x( m. k; T! p
    8
    ; a1 H1 F! G0 n9 Y, T( M: D9; ?0 _7 D: C3 v5 P: y: ^
    10/ ~: f* @& e/ r
    11
    ( G7 d7 _2 U  c% p$ q12! X# u; ^3 b2 J, c
    13
    - s% L; [- V9 I5 A14
    : x* t: ?' [" S15' a7 J; ?2 Q( i, g9 u
    16
    6 s9 X, \; @8 ]3 Q17' A  _1 r+ ?. m
    18/ A$ V: u- c8 G: j- l
    199 t6 o  }/ a$ Q9 J9 H7 V
    20, X. Z) L6 i* w) y, r+ x0 T7 ]
    21  N  f0 }; t" h8 ]
    22
    ( h5 U  l# R) z0 H/ w8 W  F( W8 [5 k8.5 练习
    % g- ^5 v3 [# U) w  K# o! X" REx1:房屋信息数据集8 \3 e5 I: s! g1 l/ g: e( U
    现有一份房屋信息数据集如下:1 W2 H& \* U  P+ y  k

    : `  r% Z. D  H* Q5 r/ ydf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])* J: T6 K) s6 u  p7 p. I. w5 n, X
    df.head(3)
    0 C- c8 [1 j  H- D: TOut[115]: 4 e# D' F7 ]. m( a; x
          floor    year    area price) A) i6 M. d$ h6 i1 P, ?4 ?
    0   高层(共6层)  1986年建  58.23㎡  155万% u8 o% q/ B% b
    1  中层(共20层)  2020年建     88㎡  155万  a# K: p5 `4 S7 R0 U( p
    2  低层(共28层)  2010年建  89.33㎡  365万5 t. h9 {9 l- w% @# v
    1# M3 F3 c2 B% z8 j
    2. F# `; v( d( x1 C+ ]) v) M' K
    3: Q# u  r: B+ x+ x* f
    4
    " W7 q# [6 G: J) j# e2 L5
    ' E' n. S2 I% z- F6
    % c5 q  u6 [2 G; r7
    / ?! h; a; w4 C' P" C! A) V. \将year列改为整数年份存储。& _! \, z' ~) e; t1 O  D
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。6 }' f2 Y( y9 W# H. ]" c) B8 E3 R: N
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数* _1 M2 [+ K+ M; _! _- E8 |) X8 ~$ N
    将year列改为整数年份存储。  X6 r7 Q" w9 Y  p& ?
    """7 c% N! {; x3 Y
    整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。2 {. n$ d! ^: o/ ?- b" R5 e
    注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,* E% V# t: r) m/ L$ ?# v
    转成int后,序列还有缺失值所以,还是变成了object。
    9 U5 J; \: b! r' R% z  s. |而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。! o! @0 b/ x9 [* Y. k
    """
    8 v# l8 D3 k* h! k$ E& Bdf = df.convert_dtypes()4 a/ T4 C; m4 h2 O) n' H* f
    df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
    3 p7 C  u/ _8 u0 \" ^1 Y6 }) Mdf.loc[df.year.notna()]['year'].head(), W0 R+ K3 p5 o# F3 s2 D$ `% Y' j
    : L, k3 R+ M2 K1 V" r% [9 a) ?
    0        1986$ N- m" @! s/ E' h5 s
    1        2020
    ! L- |& _# ?/ d# R2        2010: P1 X7 ~4 b' Z( u2 i$ \$ J
    3        2014. R8 ?+ v6 K* `" u- m5 Y% V; y! W
    4        2015
    + ]5 g& ~& k3 Z" a4 {" J* ?Name: year, Length: 12850, dtype: Int64
    , ?! [7 \# _( N& j: A. a' Q) l- Y& z3 m# W2 H
    11 \, l1 \# v- K( b, w- Q+ G# v
    2
    5 h& }  Q+ `5 d! T* A( k32 O- |! X' W: Q! f0 t
    4
    / `. z- I& |2 |! z6 R58 E. T- U2 j* I( b
    62 n" |% `! c! V2 b5 \6 P5 f7 O& C) b
    7
    % X5 l0 o8 C* y4 A8/ [6 E; W: q/ [& M  G& p0 d
    9
    & M9 e" D: u* w, H! I$ }& R7 w$ l101 E5 f: N4 O+ N' @. J
    11' i4 \8 N+ ^, }6 s; ]2 \
    128 S5 l  [# L& R1 u
    13( w; U* M7 N* H
    141 a+ z; g% |4 L9 r
    150 u* n, I% C9 ]4 I9 Y
    16( s! s+ t* d: d. l; e  |  g
    参考答案:; E4 b( T: o4 v, w
    $ z8 b4 s9 ]3 w* I3 B' O1 r
    不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么/ ?8 Z$ b& c9 x9 p# L

      s( n9 k) |' _4 b' C. udf.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
    6 R& A- z. \  `/ w9 ydf.loc[df.year.notna()]['year']
    , C, {7 w: K0 P' e/ r. R% Q# n1+ p; Q" z% C0 D; L. A
    2
    ( _/ u, E& U8 Z& s将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
    ; Q, g2 l# Z0 K4 Q  Ypat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)': N2 N( N- e- L$ u+ U+ j
    df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次
    7 }9 X: E9 z9 Kdf=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型
    , V( a7 N2 Q- h$ Adf['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
    0 G" Z+ o1 U4 [- ddf=df[['Level','Highest','year','area','price']]
    9 R0 {- r5 ?( ?* p* V5 Vdf.head()
      o1 D; R- ?$ H2 n6 Y9 }4 j+ X$ L* ~& O- z3 ]9 B
       Level  Highest        year        area        price
      h" s% h2 f2 q3 y7 z: |0        高层                6                1986        58.23㎡        155万8 c) _0 e0 Q4 M+ O0 d1 Q7 o
    1        中层                20                2020        88㎡        155万: l$ b% U2 F3 `. [
    2        低层                28                2010        89.33㎡        365万! Y2 Y4 v* i/ {  N
    3        低层                20                2014        82㎡        308万7 W& L& _: D. t# A% w# o" A! A2 v
    4        高层                1                2015        98㎡        117万
    % R' }2 t# p, V) S3 X1
    0 o+ U: d" u; H& E; [! o: j2- r) P) g3 K2 ]1 q3 }. n0 v) [" j8 I
    34 E) r9 F9 [& [" S3 i  e3 z
    4
    2 v$ K' L) y8 f4 X/ [0 m0 |55 t( ]. V, a, T& Z1 W
    6
    : `& q* |0 \& E7
    * J0 B3 v8 Z. e% H8- S8 S* e( G% d1 B# l; D
    9
    ) |7 I( M9 E  c- o  O+ E10. I0 z, \8 l$ t  @# ^5 U
    115 K, u6 l* Y% b  h/ [8 v$ u6 V
    12
    - G  n  j  u" F" f13
    / ]4 r/ [, R; j" ?! S# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
    % i% @- y! i( N- Jpat = '(\w层)(共(\d+)层)'
    $ {4 \4 b$ c7 z3 C* u7 w' Xnew_cols = df.floor.str.extract(pat).rename(# g3 }8 M; C* O+ W/ L% F1 F  P
                        columns={0:'Level', 1:'Highest'})9 g% L, N  R0 {  m+ Q# B5 T
    ' `& A  A3 ]- n: ?- p
    df = pd.concat([df.drop(columns=['floor']), new_cols], 1)* N( B4 |. W' X* Z, U
    df.head(3)5 @# y( C+ _  a" t4 G5 V  W5 a
    1 c: J2 T6 U2 f/ d7 u, P/ D4 H
    Out[163]: 8 @2 `# p/ d4 B4 M8 Y1 b
       year    area price    Level Highest
      l( b8 j+ c0 M2 [' U& ?0  1986  58.23㎡  155万    高层       6
    ! J5 R( A+ S; a1  2020     88㎡  155万    中层      204 y9 z: f+ w: ~: L
    2  2010  89.33㎡  365万    低层      282 B0 ~1 k) D% G: L
    1
    % n7 v" o# Y- Z+ P4 m2% @! d* n# c& N6 x
    39 n, l& ]9 r" z9 p. i1 C
    4
      m6 _" B& A( M. E0 S57 |3 \* Q! @! w3 d/ J$ T
    68 g! o+ f' g; [
    7; j1 Z& N8 w# }7 J8 q% k, C+ ^
    8
    ) F( t8 ]# w5 Z9$ e, h- K7 G+ d* Q7 K$ p8 `' i
    10) \8 \2 X2 N# ]8 p
    11
    + K1 X4 T1 T2 D1 N2 n9 @' p1 \' j12) l5 \! C! c& T$ H- }2 i4 F& z& @* Z
    13. B# L( c6 |/ w2 o) K
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    : r( \" z- z' X! T"""
    0 S2 D8 z1 h* _0 istr.findall返回的结果都是列表,只能用apply取值去掉列表形式
    . b& v1 z/ _; H4 Y, h* u) F$ T7 G参考答案用pd.to_numeric(df.area.str[:-1])更简洁
    4 V( j2 X2 P. j8 {/ u! h由于area和price都没有缺失值,所以可以直接转类型0 E8 X5 {  M0 ^1 g* \1 D
    """: L  L6 ?6 i$ u; _- }; y% S
    df['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
    ; d2 ^* _. `) E) jdf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')
    " u3 p; A$ a$ A! i8 }6 ldf.eval('avg_price=10000*new_price/new_area',inplace=True)
    ) G  ~; a7 |. H' r# 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0)))
    ( V& x  r) S7 w7 R+ `5 t2 ~# 最后数字+元/平米写法更简单  U7 _; n3 b" T3 L" F  R9 v# R/ _5 J
    df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
    % E- X3 C  t9 i% ?del df['new_area'],df['new_price']0 _; f( U/ S. s  x9 s& m: b% m
    df.head()
    1 U1 v$ f% F6 E( H* w2 G( f3 \1 V) f2 S. W8 v- h: y* K
       Level        Highest        year        area        price        avg_price5 p  {5 v  k2 Q; P2 S
    0        高层                        6        1986        58.23㎡        155万        26618元/平米% N. C; k! y5 C1 B3 C- n
    1        中层                        20        2020        88㎡        155万        17613元/平米
    ) t0 u9 ]1 L1 B9 g4 r2        低层                        28        2010        89.33㎡        365万        40859元/平米. s& A2 U4 l; K
    3        低层                        20        2014        82㎡        308万        37560元/平米/ {9 p2 Z6 S7 t4 Y; y
    4        高层                        1        2015        98㎡        117万        11938元/平米  e/ i$ h2 G' m. L6 n2 {  W
    ) ?2 b; i. I7 ?7 g) S, T
    19 `, h* u' R5 u8 V0 c, b
    2
    ( J0 ?2 b* Z/ m% y8 `0 z) C+ U35 @; \6 A* T) k5 z' p
    4
    ( ]9 }' E, k3 Q5 b+ t5* c5 ~) V$ a: l1 T0 b! u: s2 H! U
    6! }3 g/ s+ H1 Z/ O7 i
    7
    . z, A, q& A7 ]8; R0 l' m% ?3 O2 ~
    9
    3 ~3 `% L8 J8 K" T) E* j! }3 Z0 C5 G3 [  k10
    ; C$ z7 ]# F9 x) Q4 ^117 u0 u; K5 U- `6 f8 k9 k0 a
    12: V5 u! F9 F! M5 C! o# }0 O
    13
    6 E4 _+ T6 l) G, p6 e14
    # @5 q3 N( a( \2 w" f" S15
    ( y$ W; k& A9 e) b2 ~166 S0 [$ n2 Y) m8 v
    17" ^) Z% D" O2 m8 }% a
    18
    0 s% Q* m* U- S1 @; Z19
    # @5 E/ ~" \3 e1 S# U8 E! ?3 e20
    : ^% E9 H3 I; X& N4 h; i$ b# 参考答案
    " E6 {0 V* U, J( O1 Ds_area = pd.to_numeric(df.area.str[:-1])2 y( o; R1 N7 m) t
    s_price = pd.to_numeric(df.price.str[:-1])
    3 h0 p! `  W8 I5 ^  @8 v9 bdf['avg_price'] = ((s_price/s_area)*10000).astype(
    0 q) O6 y$ t/ X3 `* i* f                    'int').astype('string') + '元/平米'
    4 @8 B9 j  K+ d* `6 w% y7 [* x) k$ V- j5 s4 S
    df.head(3)
    " h' E3 ?* ?" ^: \% POut[167]: ! H$ O; e9 o9 H* B! L7 x
       year    area   price   Level Highest  avg_price
    ' _( k+ G- V. U1 u0  1986  58.23㎡  155万    高层     6          26618元/平米
    6 M) l" o4 l9 e1 H1  2020     88㎡  155万    中层     20          17613元/平米0 u. o6 J% J' m8 u
    2  2010  89.33㎡  365万    低层     28          40859元/平米. a' ?2 b, a& [1 y2 M! a
    1, r& ^; e) k# w( T2 Z6 P+ n
    2
    $ P  B& ?7 h# j3
    0 h5 Y$ n3 L3 q9 d. l& G2 p! `8 B9 B. l4- ?* n% F  p3 a: _: [
    5
    6 G5 D6 I, ^! H: L- z6+ ?" \* I/ g4 U
    7# N5 [4 e7 c$ E# @7 T7 v
    8
    / \3 m" L4 x; f! n9
    - S5 Y7 o+ \/ ]' g3 J10, f' [. m+ D' i/ _4 B: V
    11
    6 I. v. Y$ t5 v4 b121 \0 L0 u0 Z* [0 t0 g4 {7 P
    Ex2:《权力的游戏》剧本数据集8 V- C  F. X6 F, }) k/ Z
    现有一份权力的游戏剧本数据集如下:! J9 t) J5 V/ Y; H2 k' t+ {- o7 C
    0 {. M3 K) L! ?/ K3 `5 f5 @% L/ P! t
    df = pd.read_csv('../data/script.csv')
    - G6 t$ O( ]! u% D  p# W$ Xdf.head(3)
    5 G( F9 y) L% R- Q
    / {% c# R2 \4 E$ w1 F4 S0 iOut[115]: & F% W- }# V/ e1 I
    Out[117]: 4 r" q5 t  t+ |% T
      Release Date    Season   Episode      Episode Title          Name                                           Sentence/ M" X$ ?- S; _# Y2 M0 I( B/ x
    0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    / v) C7 n- n0 O1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...
    % F9 V, b8 c4 O" w7 o% S2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
    , J' P  O9 X' ?. x1" ~  Z. G! l0 u+ C( F
    2
    - [. b# w8 p& ^& H3 [" d3
    - P, b/ N: r3 p- o" c4
    + C# t0 d- i) p& \5; b5 I  J* s# m% U: x! X7 ?
    6
    : I2 V& G5 S2 t  [( S71 j8 V% S$ s5 k* j
    8
    / P* ~# w" g- b+ c9
    ( ~, w; ?( N6 O- v1 z# J计算每一个Episode的台词条数。
    2 }5 s8 G1 H1 N2 m8 c( A+ s以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。" u0 X8 X  D4 [6 t+ J* \
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。
    ) v/ n& [3 ]6 V* c" i$ v, U4 b计算每一个Episode的台词条数。9 f/ U5 B( l0 _2 p7 C) Z
    df.columns =df.columns.str.strip() #  列名中有空格: C9 D6 \. N# J# B4 ]! @
    df.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()8 B$ W1 e6 _; w. `% C3 D4 ?7 {

    ) G* l4 ^( ]. Y3 y# Yseason    Episode  
    / `+ C( J! c7 `  y, S$ p4 [/ \3 M7 ?8 }Season 7  Episode 5    505
    ' a; ?& N; I2 @) T: f2 wSeason 3  Episode 2    480. N6 c$ ]9 u3 s/ ^/ |% X! `
    Season 4  Episode 1    4751 N; Y) H. s, E
    Season 3  Episode 5    440% h- ~  s* f: T. \7 o4 T! R
    Season 2  Episode 2    432
    ; O4 k! B* {0 b6 k1 E; {/ v: |1: `/ t0 H4 n2 n/ ~" D( q5 p0 I
    2
    , a$ o) h3 W+ N4 j! F4 c3
    8 f' q8 F' J4 L* I4 H) [4$ }. u7 J: }; y! F
    5
    * t$ M$ [; ?. u( P" Y6
    7 d) U9 o# V9 Y! d6 _& ^7 D7 I0 G& V7. ~  ?5 K+ U% |& Q" M
    8
    & ^9 v+ F' I% c$ Q9
    2 `+ q9 B* D% a; r! p) _. ^4 l以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。+ t3 r& `- ]1 }/ \8 z7 h4 _9 {6 j
    # str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数  H0 O+ v* t* j$ P& O
    df['len_words']=df['Sentence'].str.count(r' ')+17 B) W% v. N+ E3 O+ ~: [
    df.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()4 C" k" g& L3 c  h8 R

    & N# X6 N# \/ \+ Z8 k3 v/ wName! K- N5 C  Y) I5 h0 C% X. s* f
    male singer          109.000000
    / n# d# |, F  a; k% |1 Dslave owner           77.000000
    0 k% r# P% ^. Q0 t7 C! fmanderly              62.000000
    " z( K/ b4 V, R; i3 ^6 llollys stokeworth     62.000000/ H& N3 |5 [! F. ~! y5 E8 _
    dothraki matron       56.666667; O% s! h$ ~/ T  T# ^
    Name: len_words, dtype: float64
    5 p- Q1 P- U; |0 |2 d3 ?( O) X1& w' \  h. o8 G$ L/ P: E
    2- V: G2 S1 a% S$ K
    3
    $ a9 y6 c/ x! E5 V4. D, f2 ?' E1 _0 Y+ o3 y
    5
    + [! K. p9 A4 D: @. R2 c7 o. H+ g6
    1 W3 e6 p! x- ]; o# N7
    - Y; n6 ?. b! g3 W" G! L% W8* h4 u& }0 Z6 x- i' f" {/ R
    9
    % {4 B" g0 s" O* Z3 K% a$ }2 X' R7 B10
    5 y0 P# R- t9 d3 g% e7 {+ `  o114 D6 y! ^6 {  [, ]$ U8 i4 I
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。
    - d8 d0 y4 Y/ G& p8 i5 E* f1 Fdf['Sentence'].str.count(r'\?') #  计算每人提问数& F. o' v1 j0 B5 \# }
    ls=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0, V9 Z: N$ t, [
    del ls[23911] # 末行删去) `1 F  N5 l+ n  b/ i4 U
    df['len_questions']=ls
    . z- J! [: K' c' w, x3 Q; ydf.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
    ; q* Q7 K' O- _( H$ s9 B3 v; d
    + @- p- ?0 G2 pName
    3 Q- n# ?3 ]) l* r( D. Ytyrion lannister    527
    1 k- I: _! u6 m  mjon snow            374) y# p& D) s2 l+ [% _  x$ C
    jaime lannister     283
    1 G. L9 ~' O$ j2 Jarya stark          265
    8 H" x- ]1 s& [6 E+ C( n) bcersei lannister    246; |" l0 @! Y4 v
    Name: len_questions, dtype: int64' Z* a" N" M) b! ?0 L
    ; U' V& y, ~# X9 o- R3 T
    # 参考答案% t# e. V; K0 m2 i+ a0 w6 t
    s = pd.Series(df.Sentence.values, index=df.Name.shift(-1))# I  E" a) F2 w& I1 U. W3 n
    s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()
    / q6 _( i0 r& `  c0 s1 T4 L4 A  ?: e1 S: Q) J- u; A6 p9 d& ~% e# s
    1- Q% V' ?# c# t5 P4 q( A8 p- F
    2
    * _! T- n! A" B& |5 r. [! m3
    7 q0 L  ~% J, I2 {7 E- j5 ~4. Z8 g7 ]2 t% r" s( k
    5( I! @) h  P3 B3 b1 n  S
    6
    0 n7 s; w+ B) N; h" k. G& e  ]' T; g7
    ' ~: A# _+ }1 x8
    % c8 l5 N& ^5 a. w) t9
    ' `: R! I( k  O% Y10( b* u5 M! x9 K5 Y
    11; E" X& v! q" F+ {0 a: R
    12
    ; T% e8 C% y& w13
    ' V5 ?  l7 q6 b9 ~: ^" q2 h14& \) O$ x4 @. F6 @  }
    15
    9 ?. r$ U! u2 m8 h8 M$ `16
    4 {" c3 Q$ K" H. W17
    - r& m$ g  l) L  @, ]第九章 分类数据
    : ]8 i; Y  u  r# ~. y  @6 Zimport numpy as np
    , u0 e1 U% ~! J/ K( ximport pandas as pd
    ' d( V2 `  O3 |  O: t1
    * a. g2 c, ?6 j2
    . q7 S; w3 k' m  X, V+ |' m, b9.1 cat对象
    8 ]( k; ^$ r$ q; N1 ~9.1.1 cat对象的属性# \8 K6 [3 C& i- p( F9 B7 ^
      在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。
    ' A. F9 y  N7 M# r6 x, X: S
    8 v6 Y9 b2 g4 rdf = pd.read_csv('data/learn_pandas.csv',
    / n' \$ C! I% T6 {# K3 i     usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])
    5 F: |, F" i$ `! U) ?s = df.Grade.astype('category')
    ) f9 I2 y! F# {/ R
    , a. u5 Q" w: C9 U$ O2 b& K" @4 `6 zs.head()
    7 o+ }8 c/ E' y/ Q& ^Out[5]:
    3 V/ e1 X3 a) }2 ^7 G- f# U- A0     Freshman
    8 u: V. f8 o  x* w1     Freshman
    * K# D$ H$ S5 g6 k% U9 G; j1 C% _  F, Q# L2       Senior
    ! r0 T' f8 g7 R! N3    Sophomore# f, a$ k( E) L
    4    Sophomore
    7 _# v2 f2 A- KName: Grade, dtype: category
    ! D7 E% x# G7 o# [Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']) O0 c0 E! {. x# m2 g: u' _
    1
    5 @; C7 X" ]. F9 Q* @2) r" P/ o# \& s$ E/ s. G* ^0 E
    3
    : M* Q3 q+ K5 x( _" B7 Z# Q! K8 k3 e4
    9 i, j' F" p0 b' q5+ |# V3 S) S  |1 V3 l( F. J6 |3 e
    6
    . o* A0 F. r, y- T" Y5 w% x1 w7" I9 X: t( Q4 O. g+ R
    8! k/ D  [5 R9 j( y6 u% a# p7 @
    9
    5 z( ^! ~! f2 C8 R3 p8 Z+ }8 z, [10
    ) g( _+ l- P" K: c11, Z3 a9 q- F- j, C2 Q4 `9 ?9 ?
    12
    , F1 S% i% x& }( g7 j, V13$ A+ o" J! b; @! M
      在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。+ }& d/ I; C! M4 X

    ; K4 F& k# U& R6 C- T6 cs.cat
    : v+ B1 T  f: I& SOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>
      \2 s# ?4 C* ~) o3 w5 |$ l5 U1$ ?/ H! ^9 ?: ?. ?+ m
    2% J6 D) y, O- o5 v( d- V  G3 I0 X
    cat的属性:
    5 j0 J) a7 {0 M# j( r/ p
      O" w4 R& h; x2 t5 Mcat.categories:查看类别的本身,它以Index类型存储
    7 i- w$ t8 }7 u2 B  }cat.ordered:类别是否有序* O8 f: m& c) _" F( D( z5 N
    cat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序
    % w+ O- x$ f# o$ `- ?7 `+ {+ Os.cat.categories
    * T* e* ~9 W2 s+ a" R9 kOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')9 }% X5 H* H$ n* _; P
      W' @2 Q7 @+ {1 T( r
    s.cat.ordered
    " b* ]5 `6 }) M& POut[8]: False
    ! l% ]# Q4 j0 b' K& h; ?* _. C( [9 k0 n4 r1 m# W0 x. t! l
    s.cat.codes.head()
    7 q( P" m4 d, s2 d, ]% LOut[9]:
    , \! d0 `& a) i7 r0    0
    * r1 C' V0 [7 [5 B+ N9 c1 Z% R1    0. A; l. k: W) c2 e3 k4 @, n7 @
    2    2
    1 w6 h/ T: k) F3 C- o3    3
    ( r; V7 H  V4 N; x4    3/ \3 r9 D4 v+ P' Z8 {
    dtype: int83 K/ g- A# p8 A, \" @
    1
    9 C3 Q% z* I3 X8 z4 j2: x8 L) `& E$ E  f
    3
    : m  X6 W' @) j2 q5 M! L+ y4: D- ?4 d$ p$ T: k
    5
    ! a- {  p/ A0 R$ w% ?) ~6
    4 w- x9 L6 |! [0 n8 _7  g  f, s: K1 c1 }
    8
    ) S; J7 W6 i" d7 u, X% p% {9
    ; U& r0 r8 i- c0 ^: s10
    8 O/ Q$ p0 Z: J& V+ e% ~- q11! r) ?; z( E9 Z, c
    12
    & W* x) P8 y, I+ ^% @+ L$ a138 t9 f0 l+ B; W( g1 J/ \
    14
    . N5 y+ j2 Q' f. o9.1.2 类别的增加、删除和修改. w; B2 E, y/ ?: m
      通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?* u7 E" R# A7 I' b' M( C

    / u! H$ O( W$ Q【NOTE】类别不得直接修改
    9 K& S! e8 z! C7 S在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
    2 i& F1 g) s* K2 o" l2 h) S2 D6 Q# ?& O$ c! P$ K: x( I0 P; Z
    add_categories:增加类别/ i! M! s2 ]- V# |! U* O  \' C5 V
    s = s.cat.add_categories('Graduate') # 增加一个毕业生类别. z" x% L" q( C
    s.cat.categories+ h# K" ^+ ~6 O- n

    * _  m# C. ~* r0 [3 w5 y4 I5 lIndex(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')/ K  m; G& H' e0 o. G5 U
    19 E) ~0 j( q) v4 e% S) I/ c
    2
    ( u7 n! W, H# ^6 U; z* x3
    6 i8 j& z/ x: Y( ^2 M$ ?% D% H/ y44 O+ u6 H# a0 _9 e- X& |4 y
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。( k. m, q) q4 t2 F
    s = s.cat.remove_categories('Freshman')
    1 i0 o. c8 J$ L+ C5 {1 Y# |& o* h, ~5 w2 o
    s.cat.categories, `+ e* R8 S9 C8 c4 x$ R4 h
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')% I9 q3 d* |! ~7 I. J+ G4 O; j7 F/ {

    * x5 t4 K3 L0 I  ss.head()! Y9 h5 }; S- k) V6 E3 Y4 v
    Out[14]:
    2 E" V' T9 ]' ^& R+ B$ U0          NaN
    . ~. w  P" s2 o; G! Z- d& h1          NaN
    9 X0 R( u8 J5 |  _) N6 s- x7 B2       Senior
    0 ^% j4 Z4 ~3 I0 A' r3    Sophomore
    % E0 n) Q) }4 \, j7 m8 I7 W" g+ i2 S4    Sophomore
    ) a: Y8 B2 U+ q. w' `Name: Grade, dtype: category
    # t' C% S. e& aCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']
    . u2 E3 ^. x* M, Q0 k2 S1& X& D% ~4 ?; \# k
    22 b6 t+ @; ?' R( g) N% z2 ~3 s5 t
    3
    1 n! {8 m. F. K2 V4
    ( b( s5 Q% l2 t- l5- \& j+ R1 A3 _7 y
    61 q/ Z6 Q0 a, Z" ]" ]) j4 F* U
    70 d. b( l) f- J2 J* Z+ L
    8
    4 C+ h6 P2 @( e* l+ L6 J, B9
    3 A/ J' I7 ~4 Q8 U0 X# H3 h/ l0 L10$ |# z$ h. ]; B* E- @- S
    11
    8 f6 C- P$ q3 x# a* O" p12
    ' X7 D  J8 q' b' D8 u: n5 A13
    9 @: E. |" m7 c! j/ y) o2 l6 J& d144 j" p' s: z' d  ^  n4 e, \
    set_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
      x7 Z+ B* m8 h7 }s = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士, {, `3 }& I7 A" E# B! [
    s.cat.categories
    . `# H: K1 {6 o+ L0 K0 d8 qOut[16]: Index(['Sophomore', 'PhD'], dtype='object')
    5 h% {4 P( j8 e( \7 n. w
    + }" p8 T6 u; K2 y9 i+ |/ S5 js.head()8 U& n( S8 N) R' ~4 `) M7 S3 v) A
    Out[17]:
    0 C. I; p( @- o) k" c. [) i3 `0          NaN
    / E4 [- g3 u" ^1          NaN
    * ~: }; A% D) x6 ?9 \2          NaN6 S- \6 V5 B# Y/ |4 F. l; R
    3    Sophomore  C! U: _0 J3 O$ B+ `5 G: N, E
    4    Sophomore
    - Z6 I* F% o" \6 H: x, MName: Grade, dtype: category
    4 L: t; L) a0 pCategories (2, object): ['Sophomore', 'PhD']
    $ m3 J- N4 z! E1
    0 {% k8 c& s' [) @' l, e2
    . R" k4 y; |8 Z( |, E. k8 V/ m3
    / N* n' Z! T- @& |4
    . h8 e* h$ S2 k3 l; w2 E5
    ( U- a% P7 J* E' i3 F, u  w# B6- |& N) D/ N0 k! m( P
    7
    + q& z* K. k# q6 V/ E8
    8 v: W1 s# [* {$ ^( \( q) s9% k. s& |. U9 t3 y0 V+ `3 Y6 T
    10& O7 j9 V' j! k! U9 e, L
    119 U- q  E- W$ G
    121 z: b  j4 I2 V- `
    138 H# h! Y0 Z& z6 i8 h' f4 @. M
    remove_unused_categories:删除未出现在序列中的类别- Q! L+ A4 T* ]- c, [) z
    s = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    . M  L# O: C, R8 Ls.cat.categories8 o; M$ z/ p2 l1 B4 n
      O* |# R, P( o% j: L2 |6 T4 l
    Index(['Sophomore'], dtype='object')
    $ [) ]. T  I; m- N1
    0 I1 W, s2 `. Y! l7 t0 f2/ [8 |6 I# F1 z( }$ ^5 m4 a
    30 w5 U$ O5 L. W2 W5 h
    4
    1 ?' b2 P7 n& `. w5 a- grename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:: Z; c9 a* y6 F
    s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
    2 G/ }3 s! F: J; R' {3 ls.head()
    / f1 i1 U9 s. i6 m" c/ f1 R
    5 R: _0 l( W  f" l0        NaN, v) D! F" `7 `1 z  M1 U1 v
    1        NaN
    8 y' I* R' i# I/ g' s1 _6 k2        NaN( X, Y( O: i6 x% `; f1 O
    3    本科二年级学生/ q+ C5 b* q1 v5 p
    4    本科二年级学生
    ' o5 G8 |7 @3 c) u; YName: Grade, dtype: category& @! X( l* T% P
    Categories (1, object): ['本科二年级学生']
    $ E/ D, C2 z) q5 g: @; ~15 ^& p6 H+ ]- D
    28 Q& W, v* S  ^8 y( j7 ]* k7 c
    3
    ( E1 {. q/ b. Y7 j8 }' F40 i/ m; f6 r) K$ ~2 v/ A+ |  `
    57 |" k8 k  X% n2 w& f, n! b1 ~+ g
    6/ b1 M/ T3 d! U" D" V: q8 J% H2 |
    79 h- }9 I) _" d* l3 u! A, R& l
    8# V0 _8 z) h8 H: C' r, j
    98 U3 Y( ?, f9 g8 h% P7 D: a
    10
    * w6 t8 M" o5 H9 G7 P9 {9.2 有序分类/ a* |3 G7 F! V' [% f
    9.2.1 序的建立
    1 i+ F/ z( z4 U9 j1 Q8 a  有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:6 D& P+ L+ q" D5 k  |, D

    9 Z% p# C3 f* e8 n3 H/ F- Ws = df.Grade.astype('category')
      X: h4 D+ [5 b2 S& ~s = s.cat.reorder_categories(['Freshman', 'Sophomore',# s8 g- c! }$ V: k
                                  'Junior', 'Senior'],ordered=True)
    * o$ v+ \' o4 W9 Ys.head()
    2 a" z2 u3 w9 \8 ^Out[24]:
    # @6 e* o2 i  _) w0     Freshman% @( P4 E3 h, D0 Y" Z' Q7 g
    1     Freshman
    ) {9 W# v$ Y8 l3 `1 t! J2       Senior
    % @* V3 E: k7 |2 J! d# S3    Sophomore$ X, }+ H; s. _9 M* P* }0 g  P8 C
    4    Sophomore
    ) e) J& c1 R* `6 G3 G  `Name: Grade, dtype: category
    : Q5 N* N8 x# @6 U' BCategories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']4 F( }  B0 P) L9 r# ]3 Z9 C# X

    1 x: W6 b" V* J' |s.cat.as_unordered().head()2 |# [8 w7 I) Y# X# I/ V4 w- B
    Out[25]:
    5 d; b9 W; R& r+ z5 m0     Freshman/ \, t7 k; e" \: m' x' u
    1     Freshman
    8 R) `' g0 \6 z' F# @/ x. P9 n2       Senior
    4 {: X* E  E  `" p" B3    Sophomore
    % n5 c. }7 O" Q/ `9 b0 k4    Sophomore. w9 x' C1 o# i7 m# ~
    Name: Grade, dtype: category
    ' Q6 W) a( r! ^7 T* kCategories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']8 y6 u0 S! d2 K# E# z8 C. K) {, }
    $ f6 j4 ]% _- x( V" o( H' f  m
    1
    ( f- O3 v% Y$ C! g, ^29 N6 o3 E) S0 w5 X6 r& }! B- a' o
    3. d& N2 l: P  d1 w8 H, v
    4! s/ N: n7 J+ I; s9 C
    58 ?; j' \, j" _7 y* _! o
    65 h% W% u) ?7 u" G, }& e  E( t
    7
    1 u# M4 [& P5 ?; f8
    " ]% s: \5 t1 n) W. h9$ M' W) j& {# x( m) a( R
    10
    7 }5 `* U; c) ]  B11
    & U/ E2 r- x5 C. @! ^12$ T4 n; i. U" }* @9 x# k2 d
    13
    + F7 U, d# X$ R14
    / s5 M, p, N! V1 J# f  Q9 @6 i15) b. F% q* t4 I* }
    162 ]# ^0 J* N( r  d8 L: e
    17' F3 ]" T$ g$ x9 u6 K% J  ]( h7 @% o
    18# }, m, ^. ]4 s  i
    19
    : V1 R- K5 Q4 a3 g* u* A20
    0 u! Q5 ~& ~- ?0 O9 n( a/ t21
    8 _+ k( L* a2 g4 Z22
    ) \8 W+ q  t; l/ }  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。6 _  G" ~6 ^6 x/ c' a( B, ^: h

    * B9 L( N+ u7 _4 X" T4 J9.2.2 排序和比较
    + b  ?# @3 S6 {( v+ B! e在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。4 Y  I. L' @+ E1 K0 i& e
    + z7 T* b' m( ~& K# l
      分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:! N# ?2 F4 Y) ?6 V6 h( {
    2 |0 K3 q( U7 n7 `- Y0 r# p
    df.Grade = df.Grade.astype('category')
    - c4 S' \* L5 u& _; G3 u: T+ [df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)( I8 O9 d- h3 D; `* j
    df.sort_values('Grade').head() # 值排序+ i8 E4 Q6 V* `, l
    Out[28]: 9 T' `4 p# n- e, B+ T) `& a5 `
            Grade           Name  Gender  Height  Weight* A& b9 O4 t9 `9 ~) n
    0    Freshman   Gaopeng Yang  Female   158.9    46.0
    $ I8 m4 c6 x8 A# @4 L105  Freshman      Qiang Shi  Female   164.5    52.0
    + K+ K; F3 i' R% v) H96   Freshman  Changmei Feng  Female   163.8    56.0
    $ j0 E5 Z2 q: N88   Freshman   Xiaopeng Han  Female   164.1    53.0, ?7 Z6 g/ Z$ B6 L* T2 n- J. N% S  O
    81   Freshman    Yanli Zhang  Female   165.1    52.0
    , N/ j! Q( J- m" ?/ T' t& M+ x  r6 k% w
    df.set_index('Grade').sort_index().head() # 索引排序7 Z2 n! U# q' K' }2 a
    Out[29]: ' @( i% \! a" Z6 \9 Q2 P5 l! R
                       Name  Gender  Height  Weight
    5 Y+ W( b: Z; d* J  f8 UGrade                                          0 s3 _" O) H) ~
    Freshman   Gaopeng Yang  Female   158.9    46.0
    - f8 E# ?8 o) H  \3 Q, LFreshman      Qiang Shi  Female   164.5    52.02 e* G8 ]8 I; n- r2 K# D% j
    Freshman  Changmei Feng  Female   163.8    56.0
    ! ]* s: [( @! Z3 ^* cFreshman   Xiaopeng Han  Female   164.1    53.0* `1 U% c0 t* |& {1 E0 z
    Freshman    Yanli Zhang  Female   165.1    52.0
    9 {7 ^8 ~- i( \% r- u3 I$ M# G, V1 U, {6 D: X+ h& W# l! Q: t
    1" M3 @7 G3 d# ~3 n" U6 O+ o
    2' V- {! W, z( C5 v' t, r
    39 `$ A* v4 T" c* h7 [
    4% F, s: i" X4 a
    5, N" U( R9 p! c0 O4 X
    6
    5 a: D6 s3 ^) G1 A; t" V/ E7+ s5 J9 t' B* e9 Y1 B$ z% f! p6 E
    8
    8 a' z% A8 |' V8 R  i9
    3 O& Q: K: d+ x( K, H; m+ g; i3 o4 M10$ G0 m) G: k, l* {* ]4 X1 `
    11
    % V- T# h) p+ F: n128 Q# Q: j, D8 i# i4 M& z
    13  b8 J+ P9 \3 n; S0 E/ _5 Z
    14
    ! @" u: {, v2 q/ d. H7 b15
    . r8 n& a! Z6 j7 Q' U16
    6 O. Q; H9 M: T+ u/ X; O( V3 o% a: ~17
    " ^# m% w3 n4 c) K! r8 ^+ |6 Z/ f  v18& n* a% X! p& V) a) `! Y
    19" k3 @% E/ E& ?
    20
    5 B, u# o$ \( G) ]1 Q- k% m3 O% N0 l  由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:# ~  R6 G8 N, @. Z# F+ {
    - Z5 O/ B6 ]5 f) X- u" \
    ==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)
    8 c. s$ c% D* I% ?& K>,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。
    ' Z# x! \  `8 u6 L% I/ bres1 = df.Grade == 'Sophomore': E6 _# R' V7 _2 V' j: ^% c* I+ p" q
    / ?3 |" h4 V* }, |8 x
    res1.head()$ X5 q0 o6 p: z2 t
    Out[31]: - I  I2 p7 C6 z* M$ n
    0    False
    # N$ K2 Q) P1 [' t1    False& \% T6 M- S1 _: l' J+ I* h
    2    False' Z8 X5 i4 u+ M. l  y) Q
    3     True
    . ~- m; v8 @& z4     True  H- r5 n+ ^& `; B% v; a
    Name: Grade, dtype: bool  _/ C% h2 C. M( m; j5 e

      ^8 i2 _; {/ B$ i! N0 pres2 = df.Grade == ['PhD']*df.shape[0]2 {& }) u% Z4 |0 m+ z* k7 n& ^! e

    : l8 f4 c1 k- I0 N) Pres2.head()
    1 `' c% H# R# x5 }2 iOut[33]:
    8 [- {9 s1 g) K1 c8 |9 A) @0    False
    * w9 U- Z8 Q7 A  {. H1    False4 s# E% {/ v2 ^3 a% T; |5 J
    2    False. v& \5 {! Z, d
    3    False
    * u: X' W; W) x4    False8 J  x. U8 Z- G, f1 V3 a% z
    Name: Grade, dtype: bool
    9 J1 q: G1 v) H
    * e1 r, e" U  d- n: Xres3 = df.Grade <= 'Sophomore', y/ W) p1 k9 y5 j6 a; V

    ' n- K: S/ o) s7 R* d4 I/ I# m( ~res3.head()
    8 d, Z. N6 s' m4 ^/ v$ b0 N" iOut[35]:
    ' L* p' Z. i9 y3 e0 ^* t2 Q4 {0     True# y7 R* J& L) J8 @3 J
    1     True( Z0 [& |& W+ `1 r7 G$ B
    2    False! P7 F  u( o% J% {2 X) p) `
    3     True
    $ K5 [6 x( \$ i" S% o4     True
    + p  q& G/ t) {4 F0 z) xName: Grade, dtype: bool
    ' a  V3 A0 c, t& k( @8 S6 O* v( z& q  Z" p" F( {
    # sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。& V4 y' X+ Y! W9 [( {! Y2 e- S
    res4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) 9 ]* V% E1 @) @( q1 x; c
    5 G# k$ ]) }' U: S
    res4.head()
    , i, f' S: R7 O; t% JOut[37]:
    % S* E3 v, ~3 T0 E7 {5 Y9 q6 {0     True- Y9 r. [1 T- _$ J, V$ ~7 Q3 d
    1     True1 R7 `: ?5 v7 K& F, }
    2    False
    6 D* c% X5 `" |! q2 W3     True
    ) F; I  O$ f6 `' A% b) C3 a4     True/ X, g# E. V' j2 a; G, j' v
    Name: Grade, dtype: bool
    # k4 `/ W8 s; Q: g' j! q7 g$ [0 o/ @/ K9 L" Z6 M: q- |0 [# }
    1# w, }* d; t/ U- G) y
    2
    0 N3 ?5 P9 ^- G3 n33 [6 o; d4 c9 b8 H$ ^0 i. ?8 D
    4
    7 X  G  U* J& X# d8 F$ M5( F+ y+ d( N3 ]
    6
    / N' x( t, X+ {- z) y8 F1 e) x7+ h) }/ G, N- r5 Y, d
    8
    4 L; N7 f2 o  n' I2 |, I92 d' C- ^3 L$ e- u
    10+ n, v3 w; l' |& F4 J% L- H* L9 x
    11
    0 n" X" U4 h) S9 n12
    # C0 i7 f1 G. X5 {13
    9 h6 s" k. L7 U7 O  I14  F5 W" g) w( w1 F& @3 V( [
    15
    " @/ o% v+ d$ i16
    3 f2 Z* e; f0 r1 J2 e17% b2 Q% c: J( b8 T3 a) ]+ X5 f/ n
    18
    7 p3 y6 [4 H$ f( q8 ?+ [1 G19
    6 x' A+ ?' m* s20* E" \0 y1 e1 l! L0 V
    21
    6 G+ U3 w$ i! E5 n* |9 W22* a) N1 h& I0 \
    23
    / T  j5 v4 N- u9 f( y, p241 G/ c; l7 R, H6 |% h( {! E% O9 u
    256 e" w1 d& o$ f% ^
    26
    1 d# s4 n: R1 K) _5 c0 F27
    / @3 Q6 W2 c' r$ u28
    / k1 g0 v( {" z6 U29' V+ Y+ Y( s1 X9 c# O
    30
    , j. ~% e* P9 H3 A, F316 r1 ]1 w1 x6 T5 O! c7 e% i/ y# R& U! k
    32
    , I7 s2 \( V% t) q330 I9 L: v, s4 I- ?3 X4 |3 f, R
    34% @$ M0 Q+ Y8 D8 B0 e1 R2 j% x' T
    35
    ' s( s2 P0 u( M$ X9 }6 }6 K/ o36
    $ R7 J1 Q9 s4 P4 }/ v- Q; Y5 U37
    / u9 P: S0 r+ N# k6 ~' ]9 u; T38
    0 j; I9 O3 p- ?- W# }39
      H2 O" j: T' l. s9 e' {40
    5 w: q# s6 o. w* E6 P, k9 {+ t413 V7 C% d9 C9 l. P! l! R
    42& U& q6 p$ s1 ]3 l( {3 p+ H' f
    432 ]1 q$ N" B. A) B/ n4 w
    44& b; Z' C/ ]4 |* t) F% H
    9.3 区间类别
    + `6 ]( i+ E7 O. F/ Y0 m+ g9.3.1 利用cut和qcut进行区间构造$ m# q; ]3 v1 R# q: [* Y
      区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。
    9 v. q% n4 m9 B# v% G7 X2 N5 \  o% R, P+ {
    cut函数常用参数有:
    . m9 e- ^' H* l# [7 ~" N$ Jbins:最重要的参数。8 E: H( R$ D' N# W! G, {/ S5 b
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)2 k2 \8 }: J+ ~( J  Q  R' g* W
    也可以传入列表,表示按指定区间分割点分割。
    : T( G4 T; M6 k4 C* W: c. v* Q8 X6 Y* u  如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。) G( S2 Y  Y) Y' y' B8 Q0 d4 H+ Q
      如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
    * `/ L8 z- b+ h$ F& P
    6 [5 u5 T: P% S" S  Is = pd.Series([1,2])6 f. `3 Y, `! L
    # bin传入整数& d1 f9 b5 f# ?. r; C" a( ]4 ~

    7 e/ f8 {5 d- \1 Gpd.cut(s, bins=2)
    6 _5 R$ m! W2 K$ C, p1 d( u/ |, jOut[39]: - t0 ?! k/ r7 t8 O; @$ J! T# R; w
    0    (0.999, 1.5]
    - g: {( A, d6 e6 i" g! `" c6 W1      (1.5, 2.0]
    4 y$ F+ M" ]7 Ndtype: category
    1 n. ^. N) Q& A3 Y1 k( XCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]7 w3 H" e6 h7 p
    5 U% e% @# @, q5 e+ q5 x; _* N
    pd.cut(s, bins=2, right=False)
    + x4 x$ P$ _0 i/ E0 a! eOut[40]:
    : O1 Z' z! l9 Z0 x0 A! {2 f0      [1.0, 1.5): U- q+ V9 F' S. v9 X* W
    1    [1.5, 2.001)
    & |: q% O% i+ j7 I9 ?) A5 z0 pdtype: category- e* k; r3 y. W7 N% o
    Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]9 Z& a2 R% A8 T. l# v

    ) m1 M0 `: C1 H9 K' p! W% @$ N% N- w4 J1 i, Q* U
    # bin传入分割点列表(使用`np.infty`可以表示无穷大):
    6 h/ ]( B& E/ X5 Hpd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty])$ h6 `2 ?* O8 k
    Out[41]: - ?0 Q. J8 |# f0 M
    0    (-inf, 1.2]  F2 U( e+ C& V" Y% x1 I8 i
    1     (1.8, 2.2]4 U: G  J  }7 f7 O! Q
    dtype: category
    . @" p/ B% Q: F8 A( LCategories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]4 B. ]+ T+ _, T
    * k. ]8 Z9 J! _, O  W. d  F
    17 q( n8 D2 o& ]. d" K! C
    2$ g% @% o% ]2 f. o
    30 L' y" m+ Y( g/ `6 b2 x
    4% a! ~! R' B1 ^1 g+ O: N
    5
    9 {% w7 w* T8 s. e! z3 ~& O2 I68 ^% T: R$ n- M- g7 x7 R8 k
    70 C% X7 p, F. j# K3 R+ d
    8
    5 ^  f$ Y7 ~4 l$ s" r9* M4 g4 J% i) Y! G) K2 z- N
    107 s7 t* k- H' _0 u6 l; p
    11
    # A+ Y( K$ Z5 O4 A! H, Q( g7 ~2 l12
    9 f3 s! j) r0 }3 w# n13
    6 K0 V3 q+ M, M8 X# ]( _14
    ! {4 l5 D+ _# j$ I0 _15
    : f5 {7 R6 t9 A4 @$ W4 g16
    - n0 h# o3 b) n! j+ u+ H4 _17
    - d: U$ B; G9 Y6 D18
    9 S. I1 |7 @0 j) G% H# E* X19
    # e) B2 v9 I  x20
    1 {- o5 A. `* N: n21  Y* N8 z5 O& J
    22/ ?- L# K1 s; t) O& K1 r" D
    23
    3 }/ j' r' r  e# K24, t' Y4 x3 l- u  ]* Z0 l
    250 U" v* T7 O! a. m
    labels:区间的名字7 k+ ~! }  M- P* o; H
    retbins:是否返回分割点(默认不返回)
    - G/ F; G7 m7 \. K! b+ g默认retbins=Flase时,返回每个元素所属区间的列表3 o; o  q1 t) {
    retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值4 ^! u# ]; p" H

    * e+ C* D/ t: P+ X5 }6 Ns = df.Weight
    # b& o1 E9 P  u+ pres = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
    9 H' y( m1 M, Rres[0][:2]" }% x/ J( h$ B: Y; b1 j

    7 b  s+ Q/ ~6 Q5 `3 ?Out[44]:
    ( U/ b. B7 x, I1 H: K- T/ |0    small1 Y% d3 l, n) z" I3 L. r( N
    1      big+ I$ N9 |9 `9 R( R8 J
    dtype: category
    % }3 t, [# m9 d' Q  }& j& \Categories (2, object): ['small' < 'big']. T* [5 w$ v7 A5 D1 l. N
    0 v# d* z. Q$ P( Q( [
    res[1] # 该元素为返回的分割点3 f- N" V9 n# c5 H7 S$ r2 m# [
    Out[45]: array([0.999, 1.5  , 2.   ])
    ! z) ~' L1 m5 N3 t  y9 z5 J10 ]0 A3 q" u3 W! F
    2
    2 I0 {5 @% x0 T# g3! V- a* p  I. s
    4
    ! @5 y! ]. ~5 t+ K0 U- e# R5. z( f, p' _6 r" g2 ~  B% D+ {
    6
    " E2 D: c& A9 M! X7/ D3 a4 i! d' w/ Z& g  S7 w
    8
    ' \7 K  B; ^# e; L6 H9
    1 R- z! F% G) t, t108 q+ F- ~& L7 c" k# Z
    11, ~4 X% E2 y% i; I2 Z
    12; M5 ?# i: {! F& u
    qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
    5 f7 ^( J6 L, H) c8 qq为整数n时,指按照n等分位数把数据分箱
    8 V( ~6 ?* ?( k$ j1 f/ Zq为浮点列表时,表示相应的分位数分割点。
    . W; d: D* e. Q+ D$ E: c1 ^s = df.Weight
      b% p- m0 F5 l( }. M; f- s: G& ]5 b6 m/ J, z: J' r
    pd.qcut(s, q=3).head()
    6 r& ?: P. H. U* eOut[47]: & E' l3 [- R. s- m+ v- ?
    0    (33.999, 48.0]7 ^* B2 l3 A5 p+ b! k7 K- R
    1      (55.0, 89.0]
    * O' a3 |! M  p' L2 w/ W: y) o& J2      (55.0, 89.0]
      m2 R& o& c6 e5 m8 T3    (33.999, 48.0]- Q( o& N( W& L: s4 b
    4      (55.0, 89.0]
    * e3 @8 Z6 W7 t0 r! V. ?; e& s" cName: Weight, dtype: category+ Q# k3 S; K. O+ z9 C
    Categories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]6 \0 ?$ t# M8 E2 T4 y

    7 W, b4 w, D7 q. Wpd.qcut(s, q=[0,0.2,0.8,1]).head()8 t0 v4 ]/ ?6 I8 o- y- \' S: s
    Out[48]:
    " A% `3 E: N0 e: P4 j- Z5 }0      (44.0, 69.4]
      Z! V% w# |; {% X1      (69.4, 89.0]
    , J2 m) H' c; ~* Q2      (69.4, 89.0], p# s; }: V" x9 I
    3    (33.999, 44.0]
    4 |- d/ n( M1 F/ l+ |. x4      (69.4, 89.0]
    ; J! c% f1 ?3 d0 jName: Weight, dtype: category: X; N3 w% P% B1 r
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]; r1 `$ ?+ }8 W( L

    9 Q* O3 p0 ^- W8 O1
    : ?0 i5 v, s" `$ M2
    ! X0 O1 O4 |$ O) B3
    9 W+ E. }$ p. O4
    . `. f4 q+ U4 X' i9 J1 E( t5 d5; J9 ]9 v$ w! G: U- x- t2 ~- b* l1 I# z
    6
    # N% v7 V, f3 o! Y  Z7
    * |, s2 J2 J4 V" E$ [9 u8
    ' E& t$ m& D9 o$ V9( x- f0 t5 K* n- }$ b2 t
    103 O5 x9 {7 h3 U6 H& n9 q" F
    11
    0 L  f1 k' i3 f% y: x0 R12
    4 @/ B% c5 X" [$ U9 F8 h+ B13$ f- e0 ]% `/ h3 K/ t7 @- F
    14- h# _* {5 {/ x3 c. ~. v& p$ S
    15
    / a) u+ N$ l5 ?+ l: B! g0 k* j16* G1 d) g0 \, _* K  ^
    17
    2 o. ?6 P+ q' I180 [" m: Y: M: q# Z1 Y
    19
    0 k' Y7 c- b2 i  ]20
    " N* H7 M7 {- U5 f. p7 N21( ~3 w  @5 d# Q: u9 {/ S% ~
    9.3.2 一般区间的构造) f; G1 }+ \. ^; R$ M5 H
      pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。- M4 Y7 u. @! X! L' o

    # @, U- k, N, I1 z开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
    # i" s+ V% y. I1 L% S: E+ Smy_interval = pd.Interval(0, 1, 'right')
    * Z) @2 J5 ?2 i! V- H4 A; T# {
    * L, y( {' O+ n2 |$ N1 u/ ?my_interval2 U5 B9 A( X" W( K4 O/ A5 U7 s
    Out[50]: Interval(0, 1, closed='right')% x* h; Y: ~; j$ Y; v( S$ P
    1  i% Z7 \' C" F6 I) x- x$ V1 g
    2) N: v4 |' ]5 ^- @$ r/ G& S: Q8 V
    35 _: h+ K+ v# i: \$ ~" ?: i- g
    4* A/ a4 P4 p/ n
    区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
    9 s8 a* y6 T, C) v& W4 f1 q5 W, S使用in可以判断元素是否属于区间
    ! v7 Y; p( a/ \' Q用overlaps可以判断两个区间是否有交集:
    : ^# q6 j2 n- ~/ R* `% M  a! c0.5 in my_interval/ d6 N5 J* b8 T: t

    $ L0 G( r( j0 R' p1 ITrue, A) r( r3 |, Y  S6 Q" X; D4 [4 d
    1
    1 _  P1 e$ \, e4 @2
    0 s" M7 A" Y/ Z2 v$ Y, y( n5 O- d3) |1 K: w6 H; D- x& m9 E) I+ }
    my_interval_2 = pd.Interval(0.5, 1.5, 'left')
    9 X" m7 |1 N! x* x8 I4 C. gmy_interval.overlaps(my_interval_2)
    * v: r# P8 Q: E! m! C
    $ `3 j" M% b) QTrue
    6 A1 n$ V% S% {  Y8 v4 y  r1
    ' z. T6 u' q& |" j/ Y, E# [2
    ' X" E9 S3 D  r/ F4 l3 x3' [6 r' |' K2 D( G( W5 y  x
    4% w+ x$ d& b8 T' Z5 x
      pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:2 I' y; Y/ v# Q9 R0 s. w& |! a/ [
    4 _! U/ b$ I& a* ^- O
    from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:3 }: U3 C' K8 o; a7 `# O
    pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
    7 u) n8 O# p8 C3 }6 n) Q- c% B) A# |8 ~# s8 }$ g
    IntervalIndex([[1, 3], [3, 6], [6, 10]],
    5 O3 ?! d7 _5 Q5 o               closed='both',
    ' R9 E6 o7 s* T& D; E" T               dtype='interval[int64]')
    + S0 p/ O1 z9 }' s1 j$ U( y9 |1  M# n2 B0 c. V+ L+ @, e; x  u
    2; A4 n! c1 b% N
    3
    ( T5 u# p' X( y5 \- m6 n4
    : D! m& \% m4 b, h- R( j0 t58 b& r+ p- f) w! d0 B; _  ^. V
    from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:: f, Y8 ]& v+ A& R
    pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
    4 K( `$ S( f1 k, l! k& S2 e# {' S; \2 a) w3 t; g+ Z5 D
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],% R. x3 g2 a( ~7 U. R" J
                      closed='neither',
    4 m4 z  v$ V8 {  U. r$ C  Z; I' ]                  dtype='interval[int64]')
    . Z$ F' l; b" I" c1
    - F5 S( Q' \" c" d; r$ E2
    , U* f6 q$ W$ R: M9 S0 X* V3
    8 ~' I; y+ f& |, _- ?# N41 V4 f; a6 |9 R8 `, Z8 F; R
    5
    % V  P, r" A5 u6 s7 K% j' ~from_tuples:传入起点和终点元组构成的列表:. \: T! a) ]/ U5 P8 M; u
    pd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither')
    2 @3 n5 V* j( O6 H
    # @1 N. K5 S  c1 v2 Q# N" AIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],1 w8 m7 ^6 m( s, O  o9 w5 }
                  closed='neither',  w+ A% o; L, e, l  W3 I2 V
                  dtype='interval[int64]')" _: h$ E7 y' S! b
    1
    , k' J3 q' |- Q! |6 ?4 g2 k9 B2  }7 [5 x4 H# Z/ X
    3
    : b/ A* T) L+ ^+ }+ V" ~4, k. d0 }/ e1 ~9 `( j! U
    5+ t. q2 A5 r; g: ~6 `
    interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:, M& y3 `" M; g9 c
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数4 m& f, }# ?& H+ A' R
    Out[57]: - t, k5 T* j5 V+ \+ s: {; B8 a; l* }
    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]],; T( _" p8 O) P" r$ P
                  closed='right',
    # _0 j! u4 z% H) O  V              dtype='interval[float64]')
    5 c2 j0 C& v4 o9 c# C
    9 P* A, f/ O7 G) b: {& X' Z! dpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度9 I0 G7 l* o  N0 y& u( N5 u: `# o/ K
    Out[58]:
    - y" N) T, G; l/ yIntervalIndex([(1.0, 1.5], (1.5, 2.0], (2.0, 2.5], (2.5, 3.0], (3.0, 3.5], (3.5, 4.0], (4.0, 4.5], (4.5, 5.0]],
    / ~0 h  X7 `3 a! M' \- n$ g              closed='right',
    . d" \4 K2 j1 B& U# y4 W# X              dtype='interval[float64]')
    4 `) ?5 r! Q2 R+ {1! j, v, p6 m9 o3 i, v
    2
    ! e  h1 ~$ s" D& h3# L' j( x6 R( j
    4$ K$ p' y9 O3 {. L
    55 q, L! z! Z  n; Z+ J+ f/ e$ l9 N" U
    6$ X$ r  F: f! P8 S2 p6 w
    78 p4 u7 a0 g5 P! X5 ~, H
    87 ]& O  m- t, a# a" Y& y
    9
    ; r6 N2 t- q* r2 `+ t& e0 m' l* o. f10
    - D* D( d5 l) |11- v" F2 o- C2 @
    【练一练】
    2 h* O7 [* I' l  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。5 g3 \  k# c6 \' U) V! {! M4 Z

    & a# G; |! z' F- P  d! k4 G8 N  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。. Q  M4 ~. R) u

    7 g; r& J6 T4 Z! x, _/ X' t0 |- Mmy_interval
    0 f) T; K4 z6 ]) qOut[59]: Interval(0, 1, closed='right')
    - ]5 B  l) Z4 C- K
    2 M; {, M# U9 H& S! U9 j# Vmy_interval_2
    + \  A, O9 Q+ [/ u5 e# R) R0 }Out[60]: Interval(0.5, 1.5, closed='left')
      W7 j% X, A8 W9 K4 I, V+ q* _4 G
    $ B# S' a; J. ^7 Fpd.IntervalIndex([my_interval, my_interval_2], closed='left'), L6 E" U, c1 q. @+ B6 W  h
    Out[61]:
    ! m7 B% y! n, D; H7 g' o; VIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    7 j; B& o* V, \5 s' v              closed='left',
    + U% R, h! Z4 u- ]0 m/ D9 w              dtype='interval[float64]')
    + T' {' ]* |) S1 n! R9 T+ y+ V( c1
    - C  i2 l3 l+ e" _9 k% |$ [# V8 W2
    $ J. U! O# B" L& Q' l7 X3
    * C0 W# J* L3 `( i) |; @4
    3 l8 ?0 D# j) H4 r* H  B7 T5
    " [. O# N8 z" O: D" I6. h- y: g" [) X- T" p
    7' S! Q0 u) I1 ]$ o3 |( a
    8
    " a+ n/ R. J* P) }/ B. v9
    ( ~: ~* K9 ^+ x" H3 M: W104 X: ~; O8 @0 P) ?# k. N
    114 L5 Y5 x% H5 n3 u: R$ ^
    9.3.3 区间的属性与方法4 ]' d0 r+ Y# L9 C+ o0 B1 \/ a& i
      IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:2 I# n, J( O8 c2 a5 ^; ]9 \

    ( e( V: J7 W6 [( Q( {  u8 Zs=df.Weight; O# E& ~3 C4 A5 w7 z) w
    id_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
    $ c& V/ U5 e+ eid_interval[:3]
    1 e* P/ T4 o. r% ]7 I* \7 Z2 a0 d
    ( i/ v) P4 M8 Q% m& xIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],
    3 x. o6 T3 W8 l; j" u                 closed='right',- x) _9 o7 b( _# n7 ]  [
                     name='Weight',' A, E7 E+ v0 o; T/ @3 P0 m# G
                     dtype='interval[float64]')
    7 G/ L7 ]  I( i# D1 w# E1
    9 f  {& s/ D/ E1 G2
    , C/ i  {& M! M* l3
    3 c0 k1 s+ B8 S  l$ s43 \( R" g# D9 |( T
    5
    4 [6 _* l$ z& x  B6% q/ v; y# X5 @; i; ~
    7
    : w5 [; D. b* a& [+ V87 [3 y; C, T" ]4 a( i
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。( p5 x8 H4 P/ O6 X
    id_demo = id_interval[:5] # 选出前5个展示- P- a' B& g1 S, U( P
    + q) W" ~. j+ v; E; y3 o! U/ P( N
    id_demo' [5 k$ L- t2 j9 x
    Out[64]:
      O7 R: g8 M# d# u9 w/ tIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    # x! K4 y4 i; |1 E5 s' l/ Y! ~              closed='right',& K" b8 D/ b  x9 g4 D
                  name='Weight',
    * l$ d0 y% x. ~* q              dtype='interval[float64]')' |: p3 z  o9 Y7 b" b

      n1 N* G4 g) Nid_demo.left # 获取这五个区间的左端点; p$ Z& N4 B* p" T) [$ W
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    4 X! U! D3 ?2 N  Y- P
    . f# G0 m1 x4 w2 Mid_demo.right # 获取这五个区间的右端点9 A- Y5 {1 O0 h7 O
    Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
    $ P) y3 D9 n# M9 V0 ~8 E. [  {( q5 P2 I1 R4 e$ X3 V7 z( l% v# x9 I& v
    id_demo.mid
    6 D2 o2 v. q! ^8 J: y  R) |Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64'), s4 G$ \* u( l
    + @+ A4 F6 E; Q. b7 _
    id_demo.length
    1 J, l! I, x: d9 M" Y4 a9 D- \Out[68]:
    ; V' q; ^8 _2 FFloat64Index([18.387999999999998, 18.334000000000003, 18.333,
    8 B9 H: p8 F4 ~0 r7 V+ ]1 ^* i              18.387999999999998, 18.333],
    / N7 w* f; R7 Y             dtype='float64')
    - e1 z% F/ l# Z% R: |  D
      g+ O4 ]! `$ l! Z, U3 M* ~1
      g9 c/ m, g( ~3 D! e29 J* k' ]8 T- c; P; p7 h
    3* G; `. `% P7 K/ w
    4) L) q4 H& V7 a
    5
    8 t+ Z* |/ [! S5 A6
    8 n: n0 J' H4 f. T8 z1 s) ~# S7
    : k; b2 h2 [6 X# S8* M8 w$ C* X# ^" {: }5 t! {; \
    9
    5 _/ e. E: t9 q# r: w) o; v10
    8 z4 h( X  }4 J11
    3 ?. A/ f- H9 x' Z" J& q12
    . ^5 @" R/ B) o& c+ W6 e13
    . [) a0 a+ K( P6 i9 G' q14
    . G4 Q2 Y/ d: z' J15
    * l0 c& n$ Q) T2 N164 ]% n2 c; Q. W7 M) S! _
    178 g* ^. i: ]8 b% C3 L' e/ H( q
    18
    ( W% P1 {. s0 P% z9 V* G, w. ^192 B' z/ e# u- Z5 c
    20
    " r6 ?: J. f! F$ F- ?21
    3 X9 W3 H: p7 i' j224 d& [; ]: q3 |: S1 L
    23, U: y! R3 U  N
    IntervalIndex还有两个常用方法:& y$ r: H, d! f3 E  j' L
    contains:逐个判断每个区间是否包含某元素
    1 k; e) u, I: f& joverlaps:是否和一个pd.Interval对象有交集。  I% e+ I5 P' @
    id_demo.contains(50)
    6 [, r! {! t* P- oOut[69]: array([ True, False, False,  True, False])6 |$ ]1 Q  R# }4 P0 P( O$ u
    " F# g5 |6 F! }- L0 Q- l0 ]
    id_demo.overlaps(pd.Interval(40,60))3 z8 y8 {- X1 a
    Out[70]: array([ True,  True, False,  True, False])
    , S0 @' V  p4 E1
    1 D. O0 `! w0 g& Z! A! _) _2: a# U. Z9 y! i1 _/ D: }* x
    3
    . Z+ b- d5 Q/ m8 V4' ]/ R* i3 P+ l0 a/ M
    5( D/ A7 N1 k3 [
    9.4 练习
    0 U9 {- }! R! E& E. vEx1: 统计未出现的类别
    ) U- E- C- V( n6 X7 j# x  在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
    5 |. ]6 _: Q+ l& t! N8 Y) G$ F
    / |9 G! u/ s, ?" a  T# Qdf = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})
    ' Y  H1 O+ B& K# }4 }pd.crosstab(df.A, df.B)
    4 c% Z: U; y: w2 G9 s8 z( i* c# e2 z% J- K) b
    Out[72]:
    ' i' ~# ]: p$ {. o  wB  cat  dog
    # E. K, X0 r1 I6 cA          8 V6 ^" m. P& J* `# G) ^: p
    a    2    0
    $ h) X' n: i( ?) qb    1    0
    * x- }$ W' U  K7 e$ {c    0    1) T9 }5 U  ~! G) @
    1
    # R8 p* [2 {+ g) P  V: w( l8 n# Q6 o6 P2$ S( e+ x5 ~! {  z* k* x
    32 g9 d1 s) @' V: E
    44 ?' y! @% }/ t4 N
    5' ~7 |$ b. X6 P8 Z0 n9 q, A8 L
    6
    & a5 u+ y9 o) i1 ]( h, d7% r# |8 ]8 q3 D: p8 z: M# F1 ]
    85 q, W& |* P2 ~8 Q; s* n8 m+ O
    9
    " ^7 U9 ]  N5 o* k  e( Q- n- Z  但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:0 m! o3 ~: Z! G5 [/ y" Y% `/ j

    & K4 M4 m* ]: g1 mdf.B = df.B.astype('category').cat.add_categories('sheep')
    1 w: U1 ^; P; K) l3 B& S$ t8 ?3 L0 opd.crosstab(df.A, df.B, dropna=False)
    5 p8 f/ M4 w6 g4 c8 y# J  f
    - F# B% q% Q1 {" YOut[74]:
    ; g" L4 T3 P( |4 RB  cat  dog  sheep) A) [, @* |$ L3 M  d2 b- M% J& u
    A                 
    7 s+ j/ I( x9 z0 T& j5 E8 u; na    2    0      0
    " l: X' M: f% p6 m$ pb    1    0      0
    4 \  _3 s3 @# a: F* D: cc    0    1      0; A& l* r9 ^- K& ?
    1/ d& l: h8 c* p4 j
    27 S. A: y$ y! X( l* [
    3
      k6 V, v. g1 H( `4
    ! k$ t" I" w! B1 G4 a3 b% {' ?5/ B8 P  w% ~$ Q" F1 @
    6' a; _0 |+ H/ E
    7
    8 r7 B; P& n1 k- w9 F- w8# m. M# X6 k) F4 [4 o$ t) A, @  l
    9% A; x& Q  h5 R# L
    请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。
    * e1 f/ a( ~: @: P
    ! K3 g8 @7 q" s8 }8 Y7 lEx2: 钻石数据集
    3 F% K: y, ^9 y2 R4 S  现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:- _; }+ T- _5 `2 ?8 X" F
    ' X( m5 P( K! f1 N
    df = pd.read_csv('../data/diamonds.csv') ' `' S9 n- m' C3 z6 a1 z) O
    df.head(3)
    9 Q; L6 F$ |" Z' u$ @+ m/ I1 i
    ; r9 |$ l0 s/ g. o0 g$ F/ \0 o3 e$ @Out[76]:
    7 A" ^7 x% R$ C4 l+ p# a   carat      cut    clarity  price
    0 s+ w0 Z6 @" y$ V- f# g0   0.23     Ideal     SI2     326, o% @# l" P! k& J" }- m9 Z4 ?
    1   0.21    Premium    SI1     326& A& c" T. h" Z
    2   0.23     Good      VS1     3274 b4 @, Y! F' B! ?; e7 ?7 M" L' A
    1
    - D0 r4 e" K: {# Y2
    2 E2 F6 {6 t$ a7 c$ Y3. R/ j/ U' R! O2 p3 Z. S6 {
    4
    & f7 f! Y5 k2 q5
    0 g5 n/ [3 n# ]! t6
    7 j6 g" O  U/ c73 P( i, b9 M/ E) k1 G/ I  `
    8. N! L) _. b) w! t1 w, z
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。# P% t4 w) L* A% D$ t& Y! T2 D
    钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。. H5 X( \- i3 J
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。/ l/ o) b# g. w, n3 T9 ?! c
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    ) f. h2 u' g: {. L" Q; ~( d第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    + P4 \* w" N3 d" O& f对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。8 j' o* |# ^) K+ {" ^: c
    先看看数据结构:" j  N( g9 D, f
      A4 H0 y, I9 X4 K0 S" p# ^
    df.info()+ a4 G% K0 i7 w0 H1 `6 l
    Data columns (total 4 columns):
    9 M4 O3 E8 r% n1 Z* c' i #   Column   Non-Null Count  Dtype  * P. F6 |- T& p
    ---  ------   --------------  -----  
    7 B) k# {6 G& S# d! h 0   carat    53940 non-null  float648 J+ O/ ^5 n- p+ c
    1   cut      53940 non-null  object : H! Z) @* H+ p3 j( w; ?
    2   clarity  53940 non-null  object
    * E3 v; f' t- Q 3   price    53940 non-null  int64  
      L" u) U$ D. o9 q5 n% n6 Udtypes: float64(1), int64(1), object(2)
    4 i( `5 l7 X8 i* }1
    ( @: W1 U1 b* h2 h! `2
    ' X5 l3 U# Y8 D% Y0 |( c3
    8 U2 ]5 E7 S. ?3 E# {4- z& E- e2 r3 j: u
    54 U9 J' I4 s- ?- a9 M# r
    60 K- M; u$ @/ l
    7
    2 m# q( `- \( w! X8# J7 N% k& v- {
    9
    + S( v, @- C0 W, B' C# A, t比较两种操作的性能3 V& v5 f8 M; t
    %time df.cut.unique()
    5 K0 a0 S2 Q' Z. G. Q# f+ K- D2 F' F4 w/ w- \! P8 X
    Wall time: 5.98 ms
    5 `' M/ t- _( k- U8 Carray(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)' J0 c5 |1 r4 p6 J, E1 {
    1) m  q' K2 T) z+ ?
    2! t8 N% K8 o* K# V4 V7 Z2 r% y/ l
    33 N: y, k# F  ?, @
    4# M. M) G, e0 X4 Z2 s+ a( t
    %time df.cut.astype('category').unique()
    . o. `% o  V$ |2 [4 O' G. `+ ]& i) C" f( @8 M3 a* U/ p) S+ ?
    Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms" S% r  I  \3 R- v
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']4 X) d& X6 g+ E3 l
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']. L/ d% V7 D  u. I; l; u
    1: x  B) }, K. d9 t
    2
    ( {4 ]5 D) `! u/ A8 ^3 s: }3, R) Y+ H5 B( c6 F4 D/ R5 T) a
    4& G/ V- k& B& T# a$ X
    5
    ) [5 g+ z, D4 d- u: {df.cut=df.cut.astype('category')/ J6 ?0 l0 ^9 l/ j* x- E5 y) x
    %time df.cut.unique() # 类别属性统计,2ms2 T* Y- c" c0 v/ a! E! w. Q3 @
      n' ]+ N) }  M# M
    Wall time: 2 ms
    0 w9 m+ o# T- p; l- z( p) `. Z% C: N  p['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']) h4 Z# k& ?2 B: u2 J% d4 |! @
    Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']4 `. h0 O* y6 G8 r/ Q" J8 V
    1
    9 G# u( ?" U- l- p* f7 O: A& I27 }8 r2 E+ W. w3 D6 X1 P6 Q: @
    3
    5 H9 k* d. L/ l4 s5 [+ w4
    + H) Q) B2 x/ U/ D5 @1 R% B% W55 M$ S+ ?/ B& g  U6 |* `
    6& E$ z8 J. @* a3 F, M( J
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。8 j+ u2 t; ~9 j+ _# i
    ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']
    ' w; a% K; U, W" i. lls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']
    ! Y' ?2 F1 k8 W- `  p5 idf.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    6 H2 n2 P7 S: [/ Z7 M) Xdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
    $ l* V2 g# F& r4 e2 \) j6 D! E3 x0 C' o" n# Z; A
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)# H% V0 X5 `, h2 z' F! ~/ |

    1 j' g7 E5 j" S2 g        carat         cut        clarity        price
    " K/ T. j* y, t# C7 n( j2 ^315        0.96        Ideal          I1        28017 n& F" c' X) u6 G7 g  g
    535        0.96        Ideal          I1        2826
    1 f. W! j6 \4 H  c551        0.97        Ideal          I1        2830
    3 j3 V5 w3 r& S1
    # r! j5 K! u$ C; k1 n! _% }0 c2+ \! N+ u) m" g2 `0 n4 Z3 l
    3
    5 A% O# W, B+ O; ?* D5 v4
    ' z$ e8 c9 }1 w( `5
    9 j# u7 c: Z3 |% m  ~69 R& H; c" R% N/ a
    7: x9 x! [& h' ~
    85 x6 n- ?4 r. T4 O: t! ]
    9
    - {" Y: b# D" l' O" A106 W+ y3 B! J6 C7 g8 Q
    11
    . ?; R) w. b. z9 A% n- X1 a- k1 K分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    7 w. U. p" ]4 h4 ~7 J8 f! l# 第一种是将类别重命名为整数) i! @# e5 n2 ^$ W4 g
    dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))
    ' t- S7 L# Z! k2 s% Tdict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))! X/ {3 n' s% F0 H
    6 ?. J8 @" `) u! W
    df.cut=df.cut.cat.rename_categories(dict1), P5 m7 g" A$ @( b8 V' L
    df.clarity=df.clarity.cat.rename_categories(dict2)
    % i4 z" U! C9 o) p* u; qdf.head(3)1 v6 u% }# d; m6 b- G' N8 A- x! R
    * m; d, T2 ?* M; m' H6 B
            carat        cut        clarity        price
    : F6 K) [6 F0 T* x! W1 D- ]0        0.23        0          6                326& h) `! S2 N6 N6 ]" K+ H8 I# I' N
    1        0.21        1          5                326
    . O  \7 r* A. D8 ~. E2        0.23        3          3                327# p7 ]  }% r( F
    1
    8 o- w4 b& p9 R& C1 {( [27 ?+ d) p- L- ^4 L
    3
    ) O3 G& F) h0 a& t" C$ d, ]4
    8 W9 K2 e+ j( s5$ @1 V  ?1 C- q8 V: v5 X
    63 e; V) P4 F/ t! R. n
    7! M; b! m0 Y9 i9 d" O7 I
    8
    + `) ?) G( N$ X% S9
    ' b+ b& c1 I% r10
    : q8 k( r/ Z( P. ]5 `* ?% c/ V8 w113 h' _% i7 j( s
    120 H- {+ {/ X7 J) Z" Y, u% d+ }
    # 第二种应该是报错object属性,然后直接进行替换
    3 U/ A6 R  ]+ bdf = pd.read_csv('data/diamonds.csv')1 H, M8 N2 U% O% [2 e: L7 Q3 B
    for i,j in enumerate(ls_cut[::-1]):' ]2 g6 Y* y' C: n# H- N3 R
        df.loc[df.cut==j,'cut']=i
    " F6 a8 q) S- R% Y( N6 ?. z2 i: A* |% x, [; J% {4 j2 W9 ?
    for k,l in enumerate(ls_clarity[::-1]):; p: J5 c# ~) F5 g+ E7 @. s; ]& Y
        df.loc[df.clarity==l,'clarity']=k2 T: q6 Y5 N- Z9 s! J5 X* t
    df.head(3)1 K3 S+ Z4 J( A  \
    " t  w8 m3 c4 z& ]
            carat        cut        clarity        price
    / ~; f1 C3 l) O; _- [9 y. _0        0.23        0          6                3262 ~1 y5 W' G  Y) b- H9 k+ ~
    1        0.21        1          5                326
    5 {* L& Q8 {5 g& |9 m2        0.23        3          3                327- x, p% N$ I6 k
    1
    ' j; }. y! |6 d/ ?( q8 a5 d- U" f2
    * C! q2 c/ R+ f35 s- w  l8 o4 m1 a
    4( n! q: W5 z( T& ]" e  Q( S% r8 y) d7 M& Z4 S
    5
    9 g- L& V4 x. o" r: x9 D7 K61 p/ o: V" }/ @5 |
    72 l0 j  r8 D! s) q
    84 Z1 v4 O! w: L+ s* A
    9
    3 C) U+ |/ m+ C* Z) c. K: ]103 w5 E7 f7 ]: Z- i+ l; F/ D/ m
    11: l: g- a5 r9 c0 y% V2 [6 `6 D: W
    12
    1 Y1 N6 E4 X) _8 B. x6 D! n13" d& o; y& m* F' u( C) h- Q3 U
    对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。( y8 D9 H! a2 K) @
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点
    - z+ y  c0 \* |. x1 O: Javg=df.price/df.carat
      ]: W1 y/ s2 S
    # D# \1 k+ R& S% H$ }df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],
    2 M  O' Z1 O2 Z3 E  {. ]                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]9 F  o  X8 G/ A3 g

    5 X' I' x: y& c( J! pdf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],$ q+ x, f: L( [# s. p& I9 H
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    2 }) X/ N  C3 ?" mdf.head()
    8 ]8 z0 w+ |5 F1 x$ E9 J
    ' [- d  J' n5 L) x: v        carat        cut         clarity        price        price_quantile        price_list! b) c) C2 ]& Y2 k: h% b! _: T
    0        0.23        0                6                326                        Very Low                Low) f) {! c4 F- T, V* [) C" `
    1        0.21        1                5                326                        Very Low                Low% X  K* ~4 y7 q/ f- _7 o6 I1 S
    2        0.23        3                3                327                        Very Low                Low! n; y! U4 H$ G% O& J
    3        0.29        1                4                334                        Very Low                Low0 O8 Z/ h) y: i* G& w6 o) r$ o
    4        0.31        3                6                335                        Very Low                Low                                       
    3 O  @& v+ x0 \0 Z* V+ d% [/ E
    ( Z9 n  }- g) }$ M! T+ d1
    ! r3 W! Y+ R  ]$ P2) B8 Q# c/ i3 I# r2 ?; F
    31 Z, b% d: c# r& N0 {8 H
    4
    & |  B( k# V* v5
    & n% R  @0 p, ~4 y' M6
    . b8 }5 z: N' g- C. k7
    " m& E6 v( K6 ?, K8 [! n8# Y; u: J% p' C# W: z- W" H
    9) _+ R* Z! o6 \0 y! l* C, d+ `: V+ i
    103 j3 c. |8 Y1 x$ C: u" [& q
    11
    . F0 S" g. x4 t# N7 n  n12; }* _3 P8 R& y+ g8 h1 A  r
    13& E& }* ]$ {7 N5 x& N! O
    14
    % B7 L: \5 L. N# E* w# ?9 R7 Z15
    : I5 k1 Y! d7 n) \7 ^7 S3 l# }16
    : C4 J5 _8 T/ ~! c分割点分别是:) E- D  i' z# R, \, |

    / R) B6 S; ]/ v! K3 F1 g& ]array([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])! I1 t) F& l* [! P2 K9 o6 A
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])- L( s, c# q9 \* ]6 o
    1
    ' w' _$ N" X& u9 a$ G# |2 Z8 J2
    ! @! `0 ?% x, k' P第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    2 p) Y. k( g6 W+ m5 b) F  Idf['price_list'].cat.categories # 原先设定的类别数
    * U3 M, U8 m3 x& N8 [, JIndex(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')
    6 X# }1 O. l2 f8 _8 C- r* M9 G
    / t# v0 x" I6 x7 U  Vdf['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别
    - |9 a; X) l  X0 B1 j& \Index(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现. D( \0 R/ }1 G: O) y
    12 C0 Q% D# o7 O$ S; {
    2
    : d  F" f% E6 v+ T% q1 p3
    ; ~" ^4 t/ P3 c; x48 T  m, L+ g/ h8 ]
    5
    . z( c& h2 X0 h/ d& @avg.sort_values() # 可见首尾区间确实是没有的
    5 |9 q% n8 [& D! I/ R) _2 }31962     1051.162791
    3 R  A+ H/ U8 ^% f! X2 w# }  T1 Q0 |" _15        1078.125000
    , L- [9 C- _- F9 h4 j  \4         1080.645161& l! V3 P4 K( G
    28285     1109.090909+ n) N+ D4 [- S5 _9 [, M( m
    13        1109.6774198 {. c+ H% a0 k8 v, ?& q
                 ...     3 `3 \0 [  Q- {$ f& I9 {. c3 Q
    26998    16764.705882" M  X2 b7 T- p0 q+ E$ H
    27457    16928.971963
    ) f$ f3 t/ T- s* B8 U& ]27226    17077.669903( {- Z$ }# t! s, l
    27530    17083.177570
    9 |9 q. }5 o: S+ A. A7 Z" o27635    17828.846154
    1 z2 \3 _5 D% w0 g  Z$ w0 ?: Y. w1
    3 e3 T/ t; l& G( }0 {2 A2
    ! a3 c' X7 d; I) k8 ?4 J* {3) r  d% l6 M% H4 I; O
    4
    ) u' ?3 f6 M  ^$ d5
    7 w3 s% D+ @, h6) t4 }! I; I5 i0 ]) G+ e: ~
    78 A# ^- j1 K; F( _" J' [
    8( ?- v5 D0 M+ L8 o1 `) z
    9- d/ @) s& d5 y, W4 ]
    10" r: `& Y& f  ~# _9 L
    11
    $ @0 f6 Q1 D, d$ g) ]0 X4 \" I12
    ; i, Y6 }- R" t对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。$ Q5 X: \6 ~5 N" X
    # 分割时区间不能有命名,否则字符串传入错误。7 E5 z" o2 R/ K+ r* P5 h, Q, b
    id_interval=pd.IntervalIndex(0 R$ t+ T% z2 h: _- l/ e3 t' e
        pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]# \# `5 P3 s6 `9 Z
                                )
    " p# E, K9 Q0 C/ ]: y+ M% @% @( A8 D, F7 Eid_interval.left+ u. q2 s) U5 y, K% t
    id_interval.right
    9 E, R/ s1 i% r; f  ]id_interval.length                           
    1 e* l# o9 w# e1
    ; n6 `# }( C! _3 Q2 z9 e, x2
    ) \2 d/ n8 z6 s* @: a( E3; B& t& @+ H* ?8 I& y4 q. _
    45 m* G- g/ F' w9 p  i; z+ u
    5
    $ |( o- U3 ^# u3 \5 m; |* L/ D6; R3 a! k, C0 w3 |1 Z7 G
    7
    1 S3 S( w8 u* Y第十章 时序数据
    & n. j. C& q- S- e8 Fimport numpy as np7 q1 A! x( f* g* c: L
    import pandas as pd2 @! C, B0 y$ |2 J' n  h+ d
    19 ^) w. e7 Y2 k! J  k: }
    2
    6 s2 q8 C8 f; H
    - Y( j' M! K' A/ H, n- A( v2 X8 H
    " v# W, _5 E& _; h: I! `0 t: A5 d/ {10.1 时序中的基本对象" `1 T- I4 J) p: T  v4 s7 v# A+ s
      时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?
    6 M2 ?& I- Z% X! J3 a' _5 J* \1 A8 t) j$ |
    会出现时间戳(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的简写。
    $ t2 M6 A+ B% G( g3 p0 y, [* d" v" D1 s; ?/ _+ D
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。. |4 R. J5 M3 b$ j. R- \
    7 j6 D" w# Y1 C8 [; @
    会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
    , x! W- R! B) \# k: V$ C1 s# c( o! O8 L
    会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。" @" q- u) p4 D( k7 R0 V7 S# n, d! g

    + [. L7 t" E$ e' I  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:
    4 B6 o) n, M, H
    , y9 Q! g% ^) k5 i2 T* j概念        单元素类型        数组类型        pandas数据类型" U% X) X' q4 }6 O/ T  p; U, M
    Date times        Timestamp        DatetimeIndex        datetime64[ns]$ M' T0 N3 i- [2 v
    Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]
    0 O! @, ~' L' Q+ L/ RTime spans        Period        PeriodIndex        period[freq]
    8 Z6 Q/ k& c1 o$ N/ r: w9 WDate offsets        DateOffset        None        None' H& q% `0 K( `! p  `5 M
      由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。* _* g5 U5 \5 P; o

    0 i' J& J( G2 H2 w10.2 时间戳: i' w# r4 _; K" ^9 m) {* t9 Z: T
    10.2.1 Timestamp的构造与属性
    6 R4 |& i! e9 Q. T" h单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:
    + G$ h1 F) j7 ]* V5 \$ {' G
    ' s6 j2 c. v( e5 Y% Fts = pd.Timestamp('2020/1/1')
    4 z- [& P5 i3 j
    8 h# d+ h. I3 |2 ]0 y$ nts6 ^2 a7 Y7 g, }! B2 \' ~* G
    Out[4]: Timestamp('2020-01-01 00:00:00')7 |' N) I/ ?( r

    4 H5 G9 P' N- @ts = pd.Timestamp('2020-1-1 08:10:30')$ S' b6 m7 h8 Q$ Q' n

    & W- h0 R5 v. Sts
    * g; `+ X3 d* f  t7 t' K1 U2 EOut[6]: Timestamp('2020-01-01 08:10:30')
    7 t+ v7 R, V/ ^2 }( B" h2 {6 g1$ O& V; P/ j* X! I4 _* O+ Z/ K
    2
    : K0 q+ C! `. O& O5 x5 W8 k3
    ! {' W3 ~9 h8 a5 s9 ~  m/ V* V  d- \4
    ! K, U, b$ V' u- i* G5% x- |  w# C' A* G+ J1 I, }: x8 E
    6
    9 H3 L$ E) {% ?1 D7
    - E' E* N1 N( f; e( ~; i! S8
    0 C- }) X2 ^6 O7 X3 F, E9. }4 ^: U1 c# f/ W( ]% o6 ?. s# h* `9 L4 d
    通过year, month, day, hour, min, second可以获取具体的数值:# W) e0 a: s; F: j) Q9 @) A& I& O9 i

    - k, F2 N* K2 i/ b& W% Sts.year
    ( K3 |  T/ t/ m# IOut[7]: 2020
    * x& X$ e& A, C! P% i) Q
    $ a$ M2 T4 c. P8 ots.month8 s8 p  S) V6 x6 U: V7 D. C9 r
    Out[8]: 19 j* ?9 o4 x9 N! E3 `

    % z$ }2 w1 b+ z: v$ X/ dts.day
    $ `0 O$ _+ K8 M: k' v$ BOut[9]: 11 X% V$ {9 J; O* g; c; s2 r7 |
    ; N; {1 g( t8 s; X+ k
    ts.hour
    ) E( p3 v2 b/ K, {1 LOut[10]: 8& w- R$ m4 `9 u2 R8 w& X* K1 p6 `

    / |' w6 P3 X8 _5 qts.minute" y; s  {5 J/ h: c% K: P
    Out[11]: 10
    1 [1 l$ t1 F: R7 X9 |% S
    2 C0 C4 u, v% e% z2 k9 Uts.second$ b9 y3 T6 o. g4 X
    Out[12]: 30
    ; j2 `4 Y7 T) |$ C5 U; d$ ~% K9 h- W1 F, x- o3 o5 A- w- c
    1
    9 M! R) L1 g2 X2) q) l" J5 z& y+ l/ ?. m
    3& c$ K( a; C+ k
    4' U: ]3 }1 b, f4 f7 r3 Z! n7 ^
    5/ X  P) ]$ Z2 D' O8 ?: Y& `
    6
    $ w9 A- D7 z) s% T$ ]7) X0 E, g0 V( Y" W- V
    8
    6 Q$ X$ @0 f9 a93 ~1 g2 A& \0 ^3 L$ @1 ]8 ]
    10
    ' \% R2 J8 F) P( q: G2 I, A112 W/ h; b& P* A4 i
    12/ T7 H3 O5 ?7 y; c' d, l8 d$ F
    13" Z" q( v' }  z: e6 l3 L" u
    14
    2 z5 j3 W2 w3 t/ J3 Y0 X7 k15
    4 U6 q5 Q$ [7 k/ ]. @  V163 O4 v0 J& ]) S1 Z
    17
    1 v3 R8 V. u/ u# 获取当前时间
    & q6 q$ T+ M2 U# f  @now=pd.Timestamp.now()
    ! ^. H$ `& L9 p1* j2 E: @" v7 L
    23 s. F/ h; w( I0 f
    在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:1 u3 d& ]5 o9 m& I
    T i m e   R a n g e = 2 64 1 0 9 × 60 × 60 × 24 × 365 ≈ 585 ( Y e a r s ) \rm Time\,Range = \frac{2^{64}}{10^9\times 60\times 60\times 24\times 365} \approx 585 (Years)
    , \4 S( v5 v1 C( o- tTimeRange= ' y' a8 S$ X/ L
    10
    : J' e  c+ u6 T+ ]4 x9  ^- W& ~/ x* B$ V* f
    ×60×60×24×3659 H5 Z" H& y* y0 B! [' ]7 w2 B& j
    2
    6 V% J' a6 X$ X% F( b1 z64# t  [$ |& `( y7 V$ p* z& `

    + M9 X) }5 o7 a
    : e0 R; S" k4 a! s; X1 N ≈585(Years)
    + C9 u5 q; K  I9 M) j2 y- J2 o0 A( v9 j1 L; h+ M
    通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:$ G/ j3 B: v  b

    6 {1 E6 u3 A; x  Z. n8 Ypd.Timestamp.max
    2 B! G, `: ~8 ^) ?Out[13]: Timestamp('2262-04-11 23:47:16.854775807')9 E4 u- Q# K, E' a

    6 Y0 |# f. c2 X; Fpd.Timestamp.min
    2 _( W: E: t( [  M) b/ N5 X& zOut[14]: Timestamp('1677-09-21 00:12:43.145225')# B& A* {+ i8 e0 t8 E
    ; K$ C9 Q/ f/ ~6 D3 s
    pd.Timestamp.max.year - pd.Timestamp.min.year; s/ R5 R0 c$ e# T" f+ t, }, z1 O
    Out[15]: 585
    / w, {* r; \( h/ c, Q) T1
    . P' t3 S* i$ y% |2
    , e2 D( A2 h1 q7 V- `1 U2 |; n% `" K3* N& f4 M/ Z9 x+ [" X
    4+ ~' [, {# {) C: h5 Q) L& _' `9 W
    55 X8 J/ K# g4 ?3 }! P5 ]
    6! v" l6 c4 k* u$ n5 ~8 S9 b
    75 V& Q5 d) ^+ z2 {- ]- p" M% ^3 r
    81 V# e. x! {+ V5 r. ]
    10.2.2 Datetime序列的生成( r3 l+ g9 ?4 g% P7 Y  c; _
    pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,
    " K; s, W4 y# w                                  exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)
    5 P& t4 B- ?3 k1$ e7 I5 r$ Q1 j
    2
    1 ~+ r3 l- R  g6 a$ `pandas.to_datetime将arg转换为日期时间。& r0 z) z1 X9 S

    & ]. u. U, @0 |- @) h2 a, H( {arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。
    , X2 K/ @: h' E7 }# derrors:: q6 \4 j3 Y# _* m
    - ‘raise’:默认值,无效解析将引发异常
    7 d- u; x9 t- g' B- ‘raise’:无效解析将返回输入
    5 N/ E. x* O9 i% n% h- ‘coerce’:无效解析将被设置为NaT/ w: M: F3 G8 _- J& k5 `: Q
    dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。+ X3 p+ V( h  M$ c: i: Z# N% C
    yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
    " n7 D: D, Q; Q, C9 ?- }utcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档' K3 h" Z4 x; g1 p2 n  J
    format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
    4 l( w1 M- o/ P1 g) ^unitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。; N' c; l3 B! X4 I$ q
    to_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:
    6 |9 K2 l4 C' t& X; x- [pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
    7 e6 a: q/ A0 T' Z/ ~. {+ b
    ; |# z6 u; A6 Y2 L: F- wDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)
    * y" ?# N5 D. J* E& l2 C1
    " Z8 h" D; a% q! Z: W( T2
    $ _- o7 y$ z0 _3& P& M  \5 ]  x+ d1 _* e
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    1 T, }3 Q+ @' Y3 l* k5 ?5 u- S7 `
    temp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')- y6 n) p" \2 P5 ^4 }' s6 L3 V6 ^
    temp
    8 |* @6 ?( M: o( `1 a& z3 r! E7 \/ \* R, ?2 P8 v
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None)
    4 }, c8 q, K0 ?$ Q3 H6 X& ]1
    * J; F5 n9 L- ~5 ^/ P3 d) Z7 H2$ Q  p! v( [1 u8 P
    39 R* N. ]$ x. R
    4" D6 A- i( Q: C
      注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:
    / z: O( X+ D+ N) \% j" ~3 C- G. V; f+ i: L* o
    pd.Series(temp).head()4 K. {! }: s$ y
    0 e- B9 U  Y8 X$ v
    0   2020-01-01* M  a8 Z+ A/ M5 H4 a
    1   2020-01-03- l5 H( m! V9 H, [" d: L
    dtype: datetime64[ns]2 A) s7 x9 F; {
    19 f% e  j- `1 X; ~# J3 c, C7 j! s
    21 W+ A6 d0 @* f5 Z: q
    3
    , U) w; j0 ^# H  v9 w4
    4 i/ O4 D3 C7 I1 P5" C6 N* w( h- U. x! X4 i0 I$ p
    下面的序列本身就是Series,所以不需要再转化。! I3 g9 A( b8 r7 ~- Q) U- O+ g; ?
    + R- `& t2 q; A% W# w) w
    df = pd.read_csv('../data/learn_pandas.csv')
    2 n5 W, H: ~; p0 h. v! T6 Us = pd.to_datetime(df.Test_Date)
    % |- f# s1 R1 ^/ h; [" C0 u& Ss.head()" z4 E8 C+ q( [1 ]) U

    ) z4 I0 |6 C! U  p  k* I( j5 a0   2019-10-05' N& p" C& `; Z9 g
    1   2019-09-04
    ! G: p* ]# q9 F# k2   2019-09-12( b% @& \( I4 Z$ u. n0 v
    3   2020-01-03
    - ^1 ?8 S( B' A% ^9 P, Y( e4   2019-11-062 j# a4 n  l4 K
    Name: Test_Date, dtype: datetime64[ns]
    . j% j. a" i" K" K" g0 P1
    ' y+ G6 x+ }" l3 H# W" Q0 k2
    * m9 ?$ L9 c8 ]( o% N- |3
    . S1 h* r) \1 X$ s46 z9 J; h2 T4 T: B9 L
    57 h! Z& O5 T( o' l1 w  R
    66 y  x4 ^. P  A4 o4 A
    76 x0 Q6 g0 }6 L# F: u2 \
    8
    9 P" {3 H0 h) a. |9
    / ^: |9 {' |# n8 w3 D8 T: b4 U1 ^3 U10
    * ?7 [0 d3 i* E+ p; f. u! v' u把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:
    # q; m) X& Z# n$ V  V- vdf_date_cols = pd.DataFrame({'year': [2020, 2020],
    ; H9 k2 G- A- c3 E) b                             'month': [1, 1],
      N$ V9 F) D# v1 _% l' H                             'day': [1, 2],
    9 H7 D2 i8 Z4 y  C8 `; b                             'hour': [10, 20],
    " I  g, j8 F$ n/ t7 ~& `                             'minute': [30, 50],
    0 X9 _, i# t- F# F7 g4 U                             'second': [20, 40]})5 z$ M& h7 \7 M5 f; l  v
    pd.to_datetime(df_date_cols)- y% q; w5 v: T# j
    ' N2 `) }7 i8 F# W' m9 n; l
    0   2020-01-01 10:30:20, V. t8 Y6 \# n1 s* J# D
    1   2020-01-02 20:50:40' B7 Q: b7 E9 ^- N* z
    dtype: datetime64[ns]4 O3 Z% b$ ]- T9 N6 M. O
    1( @5 m$ X9 l- V8 U( g
    27 h' x8 n8 O8 |+ w
    3
    % [2 o$ M: O% R8 K9 v( Y4
    ( B0 e  ?; h  D3 R4 e5
    # o' Y( i6 D1 u' j7 o6
    ) P' Y# f( l! @2 r( U' f7
    & j5 h; u' L  L$ q# p8
    & I& O, l' Q. @) A, R2 ?9$ Q& J3 n* H* b6 T
    10! `3 t8 E2 z+ R! x. s" I7 R
    11# W8 V4 z7 [, S5 w4 g) H3 _; a3 V2 R
    date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    / D0 N3 k& \9 L8 L5 m1 {8 Qpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    ' e' f: e+ z6 o( n7 m) I# nOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    - d; ?' J/ F& J9 A" T& a: b8 w
    pd.date_range('2020-1-1','2020-2-28', freq='10D')" \! }+ \" K" S7 l6 l
    Out[26]: + B' k7 P5 L0 s/ O
    DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',
    / k/ f) O, p% @4 {1 ]% w. P1 X               '2020-02-10', '2020-02-20'],
    & a% Y5 s6 e: `% @, l" z! Q              dtype='datetime64[ns]', freq='10D')
    4 X& m0 Y& T5 c
    , D, l" L. p9 Y, xpd.date_range('2020-1-1',
    1 g: _4 ^8 E: a! Y* S              '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天7 I( ]  U9 m; Y0 M# l* C1 j

    6 j) ], Z5 u" y2 NOut[27]: ' @6 E8 A, ]) i
    DatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',9 u$ \2 Q4 g# R$ s* _1 |& i+ C" h/ A# y
                   '2020-01-24 04:48:00', '2020-02-04 19:12:00',% x7 @$ y, ?; E
                   '2020-02-16 09:36:00', '2020-02-28 00:00:00'],
    2 h: `* n+ B" i' I              dtype='datetime64[ns]', freq=None)
    1 K1 D, \4 I' q7 e# N+ z& M! I# r$ ~& ^0 F
    1" k) k' p( q$ g6 @
    2
    7 D* j6 H) P/ ?# s3
    4 q8 {& G9 l- k/ J* o  u40 O' q0 J. T5 r- b
    5
    % a* {- w6 |1 O" I" }0 c2 C1 k4 s6. E! O/ i! q! b* M' l0 \
    7
    ! Z) r2 x  e4 ~8
    5 g( W; [" }6 H. u6 C2 g9  u; Z7 Q5 U. z+ f: e3 q! D, r
    100 D, p) A# ~/ q% H
    11
    / m( ]; G/ F! ]2 e# D! p+ c% W12
    % @( t0 v- W- t. l1 V! s2 w13
    : [# g2 R; A1 A& Q1 r14: {) c! }/ v: M2 i/ q7 q
    15# @% W* ?- D1 r' g$ ^- k2 L
    165 q0 t4 z" v' B( {( s: |( Z4 g" a, r) a% s
    17
    + i# c' c: s2 Y. _4 f这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
      @, s1 f9 u; r0 y
    & l6 E1 P8 k& ~/ w【练一练】
    ) j+ v  H1 @- B4 `Timestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。) N) [9 _& B0 y8 n2 d3 J$ Q$ }/ l
    ' a- ?! c: x' }6 H
    ls=['2020-01-01','2020-02-20']
      t2 a8 t8 [5 U: \- z+ Adef dates(ls,n):$ r) f  c1 p7 `0 I' c
        min=pd.Timestamp(ls[0]).value/10**9/ r' s2 X: L. D# D. i" U* ?' z
        max=pd.Timestamp(ls[1]).value/10**96 w2 L+ |; o7 V' M
        times=np.random.randint(min,max+1,n)) Q! E1 f( G% o) X$ m
        return  pd.to_datetime(times,unit='s')
    - E* Y  [# D8 a  ~: }/ Zdates(ls,10) : Y' K/ |( Z6 T- H
      B9 Z* l# Y/ }6 d
    DatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',8 C2 w) [6 A' r4 n( \6 j- ]2 G
                   '2020-01-21 12:26:02', '2020-02-08 20:34:08',
    2 x% c& u& M7 ^5 n* P) w               '2020-02-15 00:18:33', '2020-02-11 02:18:07',8 x  [& T, E. c& i
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',
    + i- _1 ^" V: E# {! k' L               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],! T3 W  Y3 ]9 i* S' D1 k' w- E
                  dtype='datetime64[ns]', freq=None)$ T: D: c) e! D4 S
    1( I. V7 M6 G: l* |* R& `) L8 S* |
    25 n9 {5 w/ E, ?; q
    3
      o/ H  Y" w( F5 d0 a4
    8 \1 I, p5 {. U3 V1 I7 j5
      n0 S2 M! m  T6
    * x6 ]8 K$ e& E8 n7
    # z" e+ ]# ?2 n. P4 {8 y8
    9 v% o9 {; G' d, h3 N# |90 [/ \, H. V0 R6 i& ^3 w  p* q5 l6 h7 s
    102 v5 z2 c7 J/ l" n- Z
    11* ?  q7 F6 b% z# m, f2 b- p5 M
    126 [# }, ]7 p7 y. ?
    13! D+ s2 r$ a( m) u8 P6 N
    147 T. M6 T) T  y, ~
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:$ ]6 s$ B% f) [8 H6 G' P3 i
    s = pd.Series(np.random.rand(5),' g" F6 p0 a2 R% T3 Z
                index=pd.to_datetime([
    4 z8 U% d6 w' v4 c                '2020-1-%d'%i for i in range(1,10,2)]))0 b* S  R. K' _/ y& N" ~' }. G* D9 e

    " y2 [0 m1 r; U0 d! V& K
    2 W0 s, L! C, es.head()3 e$ j- A# O/ @" a2 U
    Out[29]:   M5 x6 q( x( w6 z' T7 ~
    2020-01-01    0.836578: U' {( G/ z/ o
    2020-01-03    0.678419
    9 T1 C! ~2 q# f$ M% \' M# D2020-01-05    0.711897
    ' Q2 c! g( I$ K7 A2020-01-07    0.487429" H( {. D. c, Y/ T
    2020-01-09    0.6047055 j* W8 j2 u" i. D
    dtype: float64
    / Q* c8 q) Q0 a$ c$ i' Y$ h7 u3 L" I$ k, X7 v
    s.asfreq('D').head()4 P" C' H) c& x- g* M. j3 y
    Out[30]: # p. L9 t  m% k8 L+ ~2 r
    2020-01-01    0.836578! |6 E3 \! d  K8 V$ N; K
    2020-01-02         NaN, L$ u. d1 @7 L3 b" r% t) ]5 h- i
    2020-01-03    0.678419
    % H& [( W0 N' r1 h1 Q* D& U# n- h2020-01-04         NaN3 \. ^4 b6 c4 M% Q! o
    2020-01-05    0.711897' {8 \# Y- G. `  {2 T3 J1 J! f$ _
    Freq: D, dtype: float64
      E3 A( W8 z0 q4 m, H
    " [9 t/ A, D# e1 p$ p! |s.asfreq('12H').head()
    ( N8 F# `" b) w+ C' n# i7 cOut[31]:
    ( C* t9 M% m0 H: g$ x2020-01-01 00:00:00    0.836578
    / V7 o" L  r' z$ G: @6 E2020-01-01 12:00:00         NaN
    * q/ s/ _# P; x) G2020-01-02 00:00:00         NaN
    . \4 x! z8 Q1 q' ^  s2020-01-02 12:00:00         NaN
    + G4 j, Z2 d3 {/ O/ N! C; x4 L2020-01-03 00:00:00    0.678419
    : t, s9 m# a  _; m. f) H) QFreq: 12H, dtype: float64
    5 u/ @4 {1 u+ T4 g* s1 V- w/ c  S0 A  `- F. r2 V( i
    1+ u# u4 r) S1 K/ f, u% {
    2/ t8 h/ @3 I8 X3 D/ z% f
    3
    # f) j0 N2 m  E2 }4+ k$ w4 \/ u; r- O
    5; Y; E7 Y# m7 f
    6
    4 N( |" P$ n2 S& X7( r  t1 ?& }  a
    8
    # C7 V$ ~. D1 S9+ t; m2 ~$ {! W- P0 I/ Q/ q0 @+ ^
    10
    9 Y6 w0 B" T7 ]11
    $ a/ h1 y& o: `2 v! y; ~  ^% G12( B/ ]$ i2 ~6 W" a+ M% v5 Q
    13$ V, A/ p7 T, r
    14
    6 a: s2 P( {; f- s& E  c" c15
    4 y5 @3 m3 M" e1 a; w6 K- \5 x16
    - Z+ R9 c- _( g$ N( A* K5 E17/ Q! I7 I! N7 w: w
    18
    . {. [  @& F9 p7 d. _19
    ! U/ r5 V9 `' _: {20
    4 V/ l# z$ E5 @4 c* E! {5 c21
    3 {4 D+ P+ n! k6 z, G* _! D" P# T/ k22
    % z: ^8 ]' x8 B237 _: B1 V# q0 Q* S
    24
    ) W# O5 o; i* e9 _, z25
    & n& Y, ?/ m: q8 q+ C& h26
    " I3 r  m! [% B$ _8 e27/ m, a( {- K7 N' s" Z, S
    28
    6 I( c+ U, @" M' N( O& H29( o1 L" U+ K; t. Y+ Y$ z; J
    30
    . C, }; `1 p$ Q% }% Z7 b31! T$ X8 P0 p) Z6 @/ q! }0 o$ J/ h6 m0 j
    【NOTE】datetime64[ns] 序列的极值与均值
    ' ^" C& e  e% H4 u  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
    ! o( T2 a! r- _- S! w9 V- [, m& X+ b. ~* ~, O
    10.2.3 dt对象
    ; Z, k+ a. F* r7 d: Q, P  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。2 t/ ~' J/ ^! ~0 x
    0 L4 _) p5 ^& x
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。
    . f+ i9 `, f4 w; i7 ys = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D'))( m; C  x  S& q  ?

    ( ?4 l2 ~& ^- J7 B2 q, F6 Fs.dt.date
    + |, }. Y6 R9 tOut[33]: : i; \4 o* Y5 w2 b$ j- a
    0    2020-01-01
    7 m& M  r" [. J) O+ g1    2020-01-02
    ! K. [  s0 ~$ }0 ^4 Y2 Q: E. z, p8 J2    2020-01-03
    / q& L  v' C- x" M; l  V; zdtype: object
    + H' x% f! ~* x* |5 ?9 h' l$ y+ f! E2 j& a
    s.dt.time1 \4 h% K- x. q8 c  {3 g
    Out[34]:
    9 n$ t& L: u8 ^! a0    00:00:008 s# T0 u9 Q5 ?$ i' b8 Z
    1    00:00:002 |8 S- L4 g6 v* [" A
    2    00:00:00
    $ L- c$ D, O4 r1 X  Rdtype: object: D0 y; ]% W0 }7 _5 S6 p
    , K6 ^3 I; T! s8 D! t/ H
    s.dt.day
    ' W  U# L' A9 i: r* W" L- uOut[35]: $ {% T3 C3 m5 N$ Z
    0    1
    " m) L% z/ `: [* L; r! u1    2  Y1 ?" [8 y2 ~$ ]* W
    2    3+ O! k+ A% a8 O6 H+ I5 r" Y
    dtype: int64; B$ r" o( i; R/ t- G

    $ S$ j: b' s! [% x; f- ]; q! Hs.dt.daysinmonth$ |% ]2 g4 \: l0 p+ o6 {
    Out[36]:   ^3 N9 E; L, v5 \* S$ U8 h
    0    31
    9 R; \4 N* Q; C6 b4 F# c1    311 J3 |8 b: ~! l2 w" c2 i" ~6 K/ s9 V
    2    310 ~1 M4 f+ c9 y# H8 |( h
    dtype: int64* ^* R! ?, d, g6 _+ l
    0 G5 d7 |; j" N  E
    12 n6 m; V- O' o* t7 A' O- C6 b7 w
    2
    % b! u* Z, h5 D- g. t/ w3: |& t$ G( L; Q! _8 ?
    4
    9 A1 s) Z( j0 \) Y! _5& W1 t$ @; T5 K2 ~2 ?
    64 K$ ?; v0 ~9 n$ d0 E
    7
    : I5 A6 r% C' w3 ?8: R" w" e2 T2 Q( c, A# ]' \
    9
    2 D2 p# E- J# J10
    * u/ I. q; N! {0 |0 b* A11
    0 h, t+ z) A+ g2 F12
    7 a% g' _$ C8 t4 x9 q: s13
    " _3 T2 J% {9 U/ }# v14
    . @/ k8 a8 q# \% F15
    8 Z1 m- t! a2 w1 w167 A- ]! Q; N2 p$ P
    17" q( V  M0 ]' X3 f/ \
    18
    . m3 ?, e$ A9 P1 f6 ~& p& u19
    ' h  @% h+ ~5 _) a/ H0 t20
      U+ f( o, w5 O7 W9 e21' L  l* j0 d; S- h. f
    221 Z! V, D- }( m
    23/ T( H2 X# P/ e) M
    243 ~& W" A1 u& C3 b4 y, A
    251 y- }4 I) A) k; O* d
    26
    , @# V2 [" @4 E8 G, f27
    * [+ o2 [8 U* l, Z& z1 W& i4 N282 s" Z0 c$ ]3 Z4 F4 V7 S
    29
    9 `+ h* N' G8 D8 @7 d1 M  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:9 O; X. R' i, l9 w- \, m! _0 V
    . z+ {3 |9 v7 |0 Q5 ~: X
    s.dt.dayofweek* ^' v4 H2 `; q7 w0 H+ k
    Out[37]:
    ! Y: i" P; L+ F0    2
    / t! I. K: S+ |5 v4 u6 }! l9 J# o1    3
    8 b% I' D& n% m6 L& y2    4
    0 F; w: f* R& h6 Kdtype: int64* t; \1 ^; `4 N+ l7 n
    # p/ b: ?+ _- D/ {& B9 q4 Y5 r5 u* N
    s.dt.month_name()2 U: ~8 J5 e, g2 b, y- _3 _
    Out[38]:
    ' k; ^" V! u4 B( _0    January, p/ y% ]  u" q; I" s+ p2 g! m: b
    1    January/ G  l' o3 Q' U, d3 Q3 B
    2    January
    0 B* H) z1 N0 E# j( xdtype: object1 ~* v4 F0 w4 C6 x' W
    5 l' v1 n1 W2 O5 w) ?1 |3 |- f2 ?5 Z; o) ^
    s.dt.day_name()
    . R. _- y8 u" L$ {! t0 v/ b$ D  }Out[39]: , y$ F" d7 B$ X! O3 Q
    0    Wednesday% M4 H& D* _" b2 F" s
    1     Thursday
    8 x$ Q4 f3 X! n3 A6 v2 I& s1 v2       Friday" V) Z1 N; L9 y" ~( D
    dtype: object" q1 k3 V$ \5 D; Z  g/ r9 ~

    2 \4 y7 Z2 n8 c# w: Y1: S: ?% K$ v' g( r. Z5 a1 U
    2
    + M3 q7 g, }: C1 r, P2 r3& B; s- D6 [; T* Z1 K
    44 u# ^' `, A8 R
    57 r: |, h& Q# v
    64 U1 e7 N6 A: N0 _. _8 n4 [
    7% v; J) ~( ?; a/ o' h
    8: \' `5 J7 _/ O+ g
    9
    5 G- O3 K& b) x* _! ]6 ?10; _; e- ]( x5 G3 d6 @) T: e
    11
    + a* E, j) {: k: j( J, ~12
    ( r5 t4 v! _( G% d13: U7 I7 b# R: Z4 s4 a1 U
    14" |4 M$ K1 E& P, o' ~
    15
    5 T$ ?/ l; M, y7 C1 s16) p& B) f; t  a  g  S+ ^
    17$ K/ C) V& P. p  A$ h- X6 k
    18
    + G5 H8 {5 Y  k  e1 L. S19% S8 Z7 B' ?) x$ W: ]. [
    20
    1 v+ T3 G! T; g. U0 A第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:. F/ A3 ~3 |' y# p
    s.dt.is_year_start # 还可选 is_quarter/month_start
      Q- w3 E- B1 ?: ]0 m+ h+ w' x+ gOut[40]:
    6 A$ {) v3 T# D  d0     True6 N3 ~. b$ B) k- f0 o0 e# m. @1 e% H
    1    False
    ( A- L+ e! B. n, a; z4 a; Y$ b: c; D2    False( M+ _  e( y/ K' e- ]
    dtype: bool
    $ E) ~2 ^# _) J5 p! o- ?
    % }6 E' Y: p: h$ j7 s6 B& ?7 M/ As.dt.is_year_end # 还可选 is_quarter/month_end
    2 ^& K5 z) q6 g+ B9 u; o9 l, r4 k5 fOut[41]: 0 ^9 l& c: c" E( a  D6 u
    0    False
    / ~  p" n/ |/ O) \1    False( ]8 O' B/ E5 m: T, J0 B
    2    False# O' l6 o7 }' ?
    dtype: bool
    5 ?3 V, x7 O( E& M! W; g  p$ E* z1
    8 K" h7 _+ B# B# K2& g$ \: d  J" s
    3# ]  l' s6 m, A8 ^- G3 h9 |# I
    48 a( @( ]! S& u0 g6 R/ j4 l. X
    5
    4 [: v$ w1 K- @8 h0 K" o9 E( c6' j8 e  `; C2 @2 p
    7
    ) k6 o3 t0 z8 q1 s& {8  o' Z0 K2 s8 f8 m/ B
    9
    4 e0 i7 V& `9 R& v10$ a5 N, \/ N6 w' S: p' U
    11! E; b) j( Z- y3 r# I* t! r
    12
    * ^1 a, z. V7 S# M13) e3 h/ _- A5 _/ K+ u
    第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。# ?+ s9 @$ G9 }: J. |
    s = pd.Series(pd.date_range('2020-1-1 20:35:00',* s  a) q" h6 O7 }$ n7 T% N, P2 N6 N
                                '2020-1-1 22:35:00',  |9 V5 y' x4 K& `' b
                                freq='45min')): f; z5 `) Q7 |
    * Z% r6 k2 N. v2 i

    ' l6 I6 f4 n3 U1 L# ts
    6 D* e! j  @% ZOut[43]: ) j7 j1 V3 Y4 n+ D* d- A4 J4 h- N
    0   2020-01-01 20:35:00
    0 C) g8 r, d. S1   2020-01-01 21:20:00
      M2 {: h( M$ m% H2   2020-01-01 22:05:002 X: ?2 i+ d# q- e& q# P# T
    dtype: datetime64[ns]9 b& N! U& B. k! E3 a; F- g
    0 H7 H4 g0 M+ s/ Q" }
    s.dt.round('1H')6 ~/ Q4 J8 D. X  Y! f
    Out[44]: 1 m5 C2 o2 p) l" x4 k& U: d" V
    0   2020-01-01 21:00:001 L" L' H  v  J
    1   2020-01-01 21:00:00
    6 k& }9 B) D  W) f) w$ e% C9 b2   2020-01-01 22:00:00
    7 }  ]& k. U9 C& G7 }5 ldtype: datetime64[ns]
    % G; c) H# C" `* ~+ @( j: ^7 {3 \1 v$ O8 L: C
    s.dt.ceil('1H')% y+ f; b  h& o- }5 n
    Out[45]:
    $ E7 ~2 [7 Q3 c* L1 }# l0   2020-01-01 21:00:00
    , X* Q, |1 S, y* l2 F. D0 q1   2020-01-01 22:00:00
    - h* F' M7 `& O0 T3 \3 a2   2020-01-01 23:00:00! p  S6 l& `  d3 k
    dtype: datetime64[ns]: `$ l& M2 R, [% J5 k* ]
    ! h: a' {3 u6 R) A/ O
    s.dt.floor('1H')
    ) j+ Q  z( m, U2 I# J: O7 O  B5 `3 yOut[46]:
    7 y! F5 t3 i/ ?8 o8 e2 T0   2020-01-01 20:00:00# R& |  w4 \1 A4 G) k; E
    1   2020-01-01 21:00:00/ N+ K( u7 t# k/ Y5 ]  P8 s5 H
    2   2020-01-01 22:00:00. N- [# o  R( O# D
    dtype: datetime64[ns]
    ; W/ B& N$ a! J4 i$ K: m; ~/ d9 h/ ?1 U3 F2 x" R
    1- W9 A; U& U' p' @9 _2 N8 |
    21 j# H9 B9 c" o7 y( z
    3  t1 ~* R( |( q+ [
    4
    ( s  y  {; @5 M! T- N, R2 f$ L1 o' l5
    3 J% M" B6 r1 m# F; X5 v6
    ( l) z( G* @. M) q: c7! A5 o, h! P: i# E3 `$ V1 x
    8
    ! ^+ x, ^) o4 |4 y1 \9
    - x& F5 Z; p5 F! w+ _2 o7 `3 \10& s% h' N" R0 ?" q/ {' y
    11
    0 Z$ c0 ]9 c! H8 |12
    6 o  U) |$ \5 U: [8 M13& s7 l/ Z! s- A3 I( h' N) B9 K
    14- m$ I1 m0 H3 h
    15
    % G' s2 J0 n: ]0 R3 y3 |16/ ]/ B% D5 I8 f( f8 u. G" ]& ?
    17
    % I9 ]/ X; J# e18
      v: U7 U4 x" r6 C19
    : }$ z; z3 U5 Z* C2 l206 ?( e; n* Z( i) c) b2 F
    21# W; V+ d. T2 g
    222 ^0 F! S) n4 M- B$ Q  u
    23( E* }+ }% c; e2 f( C( Q" Z9 i
    24
    # L! ?  U% e( G1 ~1 h25
    6 X9 x! f3 w, F. s. O26. m  k2 j# m+ V8 @, r
    27& V2 V% V+ y+ ]1 M+ y; b" x9 W7 I9 J
    28
    2 Q1 h& a4 Z3 y- `' h  ^5 S29
      z7 }1 V: N. ]4 D3 S30/ }6 S* o$ ?$ I" U
    315 I  a& Z/ [/ Q/ v- L) ^9 M
    32, ^/ e+ Y  [# c0 y  l
    10.2.4 时间戳的切片与索引+ m% B& V" n: d/ Y  w. p
      一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:2 e4 ~. r! `# T2 h

    + C& D. m3 R% p) U! r; `利用dt对象和布尔条件联合使用
    9 D; f  b8 b* X' O, S利用切片,后者常用于连续时间戳。
    4 i/ D: K$ f& w. }  ys = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))
    $ E+ A, [  K! Z4 v/ s$ o: e. zidx = pd.Series(s.index).dt
    - q) a9 F6 i- [! W6 A2 us.head()* i3 {$ w1 [! Y& \1 Q0 o8 z# d8 ~
    " i- y" x: Y6 N7 X! N* J
    2020-01-01    0) N! U5 @$ G/ H5 N3 [% K+ x
    2020-01-02    1
    + R+ z& w5 G6 R$ l" o2020-01-03    1
    - w; I4 @, X# y% h$ e! D* r2020-01-04    04 j8 i7 n0 e2 o
    2020-01-05    0
    / q$ u) P# o) g' R2 M( yFreq: D, dtype: int324 Q0 i4 @  h3 s8 D
    1% @$ n' \2 H  Z+ j7 O5 _  k0 V
    2
    $ G+ p5 y3 ]  x! d" p) a1 s3: u8 Q4 u5 I/ U' @0 h* Z
    4
    3 v3 ~6 {, o1 A% u; ~5
    / r6 L" a$ U# Y' R/ |7 v/ h  |69 x7 }7 s) [# V( ^) v4 @. b
    7
    9 e" F9 W9 |/ s& N) l8
    4 D* @2 U" N$ o% _: M% X" G3 S9
    " C% P% I% G3 q0 h9 c& V3 h10
    4 X% r4 ?8 V& O# _# zExample1:每月的第一天或者最后一天0 E- \" X! F/ H+ V; H

    9 n$ w) @5 l$ Js[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
    ' a3 y- a$ v7 `- Z+ q7 ]# ZOut[50]:
    % ]) j2 I# a2 Z) O& H' [2020-01-01    1; L; K2 L' D+ _
    2020-01-31    09 B( K7 S; j$ \: j# G- N
    2020-02-01    1
    - v* |7 z' ?* w  m, h2020-02-29    1* n* f! C& @9 J7 V# D* V# w
    2020-03-01    0) T3 F7 {. T3 E: p- s( f% s
    dtype: int32
    4 ^( k, H' O/ S! A2 [. z1
    2 E& X& t8 y' I* B2
      o# P5 v  t) [3+ `8 b) X7 H6 E. w# E
    4
    ! s% \  t, v1 y4 F# h' X8 d5
    8 m" j, t5 a4 q6 Z9 }7 T+ k6, g& g4 O9 L( Y4 ]% A
    76 M' e3 ?, M0 H2 j" @! u
    84 F% U: L8 V, |. @" k# O( @
    Example2:双休日
    - w5 G  U1 n* Q9 \" Y
    3 ~4 i- \6 p9 P( f9 ls[idx.dayofweek.isin([5,6]).values].head()
    % E+ t" u/ i/ w: C2 o: BOut[51]: 2 {* L% z6 t2 {; ?  h  C+ A
    2020-01-04    1
    * \. g7 Q- N$ w# N6 h2020-01-05    0
    1 P$ w  @, O- o! C( v2 V$ W8 L! o2020-01-11    0; `2 l+ w( i; p5 d1 ], N
    2020-01-12    1! A: \& `! \$ A" j+ B
    2020-01-18    1
    2 y  d! f9 l6 x3 Xdtype: int32% C1 D2 s  l+ ]7 p' b: c+ ^8 A. ?  B
    1% K/ E0 F% j, Q; S  _
    2. b6 c1 O/ J% r% O! M" b8 W) P
    37 Q' G& n7 h) {- p6 t8 `. y% q3 L
    4- ^- {* @! I  M
    5' l- b, v0 H0 x0 w
    6
    0 I6 G# X/ ^. A  M7* h" m* C  R; d4 h; O- I$ @! f9 g
    8# [# L* F: n; W8 o/ `
    Example3:取出单日值- h/ U* {& r' Z& r0 `/ ~3 k/ X
    " }1 n: i! s& `9 T
    s['2020-01-01']
    7 P. a, w- k+ ]. ~2 FOut[52]: 10 S5 R; Y; @0 ^! L- A
    : ?% p6 o. H- l; N7 ~- I6 x& f- j
    s['20200101'] # 自动转换标准格式5 @: k% |5 K% i* F8 [2 L; E
    Out[53]: 1; t! \& ]6 g9 z/ M
    1( H3 m( i% Z3 `  T% ?0 H3 l0 j
    2
    ; B( C7 [$ J: ^! I* Y' G3
    1 w* }1 |3 N' N; m4 u43 v2 ]$ L, {7 ^% x6 f# ]
    5, F; W' U7 K: I# |: K: _& f
    Example4:取出七月! ]; `( e$ I9 b7 P- x5 B4 |
    8 m8 I( Z) U9 {5 z# I
    s['2020-07'].head()7 y2 @- y5 u* \# b9 K! C) @
    Out[54]: 5 W3 V# V0 P, x, E- m
    2020-07-01    04 u! W1 d/ T+ X7 W/ z' {9 p
    2020-07-02    13 T6 R8 b0 Q; Y$ w
    2020-07-03    0
    7 `" O5 q: |, Y9 r2020-07-04    0
    3 |! A& _* ~2 a4 z) w2020-07-05    07 n. d0 H/ I, _8 K) a  `
    Freq: D, dtype: int32/ z9 J9 q, ^7 R' h- ]) L
    1
    2 L5 W, r6 G8 l% k3 I- U: M) k25 H* @5 p9 T* O8 P9 P$ x* I
    3
    & k+ d+ w* D% g4! H& A/ ^9 X; `. V8 w% D
    5
      {/ c. T/ J# ~  T5 O! H2 z6+ i2 m6 |7 u! i7 R+ C
    7. X; R5 F+ |- w
    8
    ! Z6 G. ?  O4 f$ E! mExample5:取出5月初至7月15日
    5 t6 k1 Z/ i! Q( Y! r. x4 y# |7 y  \' ^9 P* H2 L
    s['2020-05':'2020-7-15'].head()
    1 o5 Y/ _+ t. ~$ t$ l2 eOut[55]:
    7 B7 M. D, k7 r6 {" f9 c! a9 a2020-05-01    0
    # T+ U; n! A. s7 F' O% ]7 Y& ]  k2020-05-02    1& {5 y! n- _3 ]2 U/ l! t; ]
    2020-05-03    0
    ! H# M$ W2 D! P7 V0 t; X$ r" T2020-05-04    1
    / w3 x" a. D. V' f( \9 p2 r2020-05-05    15 i4 x# w! F" b1 Q% q# ?4 [
    Freq: D, dtype: int323 T: P' \/ m  L! w$ u% |, S

    / i/ B4 x: n0 @( u& I& I0 x) os['2020-05':'2020-7-15'].tail(); Y  U2 U2 s9 Z4 D" |) o1 x7 s! ^8 H5 C1 U
    Out[56]: ( {! O, j7 x6 d7 P) i9 R! q& D
    2020-07-11    0) C: I- e$ t7 l4 ^
    2020-07-12    0
    + H, ^1 ~* B' A& c  T2020-07-13    16 \% J; D$ X' e- r' r: F' u
    2020-07-14    0
    # z8 p1 g. e6 {( _* M9 B  S0 V2020-07-15    1
    ( ^: w: @" o' x! S7 I, x0 YFreq: D, dtype: int32# Z9 `6 M9 p' J

    . Z% b1 U" g, g6 i% U7 z1: \# H( W! Q+ Q8 l; [
    2
    , a1 {2 y) Z3 v& w9 ~4 l0 c3- t, g1 z6 g: O9 |
    4
    ; S6 K  T) q/ j7 }0 @9 B5
      L: |' P& h' p( k6. w8 N) z; H( i( I
    7
    2 I, X/ M. L2 s; X9 @8
    9 |: [, F3 I+ |$ ?9
    / }) P% C9 I6 T/ P10
    # e) V: h! x# A# `11: Z, ?1 {  U3 b# o: D  E2 e- T
    12
      Y' d0 C7 h- s0 f- s13' L3 L: W( `( @
    14  K: D  d8 f) o3 W: L+ S3 j+ @
    15
    8 z) g2 z5 A( G16
    / W3 H0 \% ~# o" f1 Y, s- `17
    5 {% e% f8 _3 Y6 M8 q4 R" y$ Z  n10.3 时间差
    4 [* }# h6 x% z+ U2 Q10.3.1 Timedelta的生成8 X; Q. j* ^9 U! K' }
    pandas.Timedelta(value=<object object>, unit=None, **kwargs)
    ( I& y- Q8 }* c$ T# }  unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。1 |* h. |: o2 N) d0 q* t
      可能的值有:  t6 q; J+ C( G! m
    ; f3 }/ e/ _; \
    ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
    4 n' j+ y9 m  e. K) t‘days’ or ‘day’: n0 X( U7 R+ Q1 \
    ‘hours’, ‘hour’, ‘hr’, or ‘h’
    + Q7 |9 z9 z  R0 f& y2 D‘minutes’, ‘minute’, ‘min’, or ‘m’6 v* s5 U" O6 t& P0 B3 x
    ‘seconds’, ‘second’, or ‘sec’
    ; @2 h# |) X* Z) n" n4 f毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’$ ^. n% B5 m0 c6 B% Z
    微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
    5 U$ v  f- c5 ^* X2 c% n纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
    , P; G/ Z9 N1 E- `& p, _时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:, c" Z& U4 ?$ @# B) Z# m+ r5 n
    pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00')$ U8 y. a$ b0 R+ }4 q' S/ h) a
    Out[57]: Timedelta('1 days 00:25:00')
    , k. M/ H& B& B. A+ D
    : J( d' Y1 @( m- O6 G( ppd.Timedelta(days=1, minutes=25) # 需要注意加s
    ' K; D2 [$ k* ~; L+ K  KOut[58]: Timedelta('1 days 00:25:00')% w( M7 _6 y0 o0 ^' @

    * o; c% S  ?' Q" U6 x" {" Bpd.Timedelta('1 days 25 minutes') # 字符串生成8 v2 ^8 K$ _& U/ p) e
    Out[59]: Timedelta('1 days 00:25:00')0 x& g1 t/ x+ A" _5 ]- E' Q

    4 h# g% j7 r3 }pd.Timedelta(1, "d")
    * U- h0 n6 C, }/ {8 i/ TOut[58]: Timedelta('1 days 00:00:00')( N4 _1 j  {: J2 q5 T6 X7 G
    1
    ) |8 b% f. V, N9 o0 F2
    : O- c& Q" m3 x3: v7 y" c2 S: `* R) w& |- v
    4) ]! L0 }. `3 l. l3 X
    5; A) W0 I6 V  q6 a! J
    6, w$ @9 U5 ~- J. q. J
    73 m* X: r4 _; Q7 N0 u
    87 q- A/ q9 @4 y* S" h
    9+ q' ?$ I# W( j6 T
    10$ d- g8 S! M- w2 T9 F! [1 U4 w
    11
    5 o) W7 v' ~+ v$ j9 e生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :$ r# \; [5 v0 I( ~2 I* J; Q
    s = pd.to_timedelta(df.Time_Record)
    # r9 u+ _# d7 `/ g- k) V  j9 u+ }3 @6 P3 l1 r, g
    s.head()
    ) Q& A: b6 {  p' {# H* z( h# JOut[61]: % E0 ~2 D4 x0 H1 _! U; ^
    0   0 days 00:04:34
    # B, s& W, e, O1   0 days 00:04:20
    & P. V/ Y* n4 d2 l; x- @3 C2   0 days 00:05:22* ]# E: b; l9 B/ w. M
    3   0 days 00:04:08: S1 B* ~" d0 q& e0 E
    4   0 days 00:05:22
    , p9 @! B9 ?4 {, Y( L6 `! U$ HName: Time_Record, dtype: timedelta64[ns]
    3 q3 e, ^. \& F: i( z9 |1. j5 N& \/ \: I4 O; s0 `
    2
    8 V- H' y/ D$ N! t( }# `/ C- a3# ~! E  z+ \! a/ O1 w
    4
    ; P) ~- n% u( i- V/ n59 M4 R( d+ _9 X
    6: _% V/ ]6 y1 ~7 F
    7
    6 J( V. {4 c1 W- k0 K1 m8# G9 n% l8 T% |" e/ Y- H: t
    9% N( P9 q% \! K+ J
    10
    2 C8 o0 k, l; M9 ]" _6 v) O- N与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:( \  b; p  {' d2 |" n5 Y
    pd.timedelta_range('0s', '1000s', freq='6min'), R1 T2 J: H" E; m% C, i
    Out[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')# p4 D. I  c4 |1 ?( w

    * y6 {/ l, T& O  p* ^pd.timedelta_range('0s', '1000s', periods=3)9 [+ [9 Y* p+ D3 v: [  S, l; p5 ~
    Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)
    6 g) ]$ W$ c! e$ y% J1
    4 n7 c* v7 V* r& `1 d1 E2
    : a1 D. m9 K1 s" l+ q) ?  o3' p9 \7 w, J0 x% ^* M5 ^6 O( `
    48 e; Y7 w, S5 o& o$ H9 t
    5
    ; g+ V( N: {" X% U对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:1 C1 n& P) H: R2 v  B
    s.dt.seconds.head()
    9 A0 y$ z( e! @5 c7 ~Out[64]: 3 R( L* ^0 z; E* e
    0    2745 J- h. A! G7 b8 I* p
    1    260  p( X- r# S& d- \' ?, ^
    2    322
    ( I7 A3 u% m. u) b$ |3    2486 @8 j: W7 G$ F2 L5 q4 r
    4    322
    7 u3 ]6 L9 J( b9 S9 uName: Time_Record, dtype: int64
    5 r9 m5 S/ u( g3 P1 j7 q" o1
    " M) A8 G; S( W; |! E% G3 }2
    9 F$ z$ L6 s" ?3 D; i3
    / m; J$ k6 ]7 J3 E4 X. }4+ k/ [- X( J/ A. a! k6 m0 V
    51 f9 U* G: D$ W3 E$ F
    6
    ( C0 r8 {- Q% N5 O  L  i# b7
    ' G8 i5 X2 Q- _6 J& ]# I6 I% X8: F7 s7 E) R: O. T; F6 y& {; [4 L
    如果不想对天数取余而直接对应秒数,可以使用total_seconds( Z( C- o; L& b7 }$ A

    1 {7 n; q0 Q; N& ]( As.dt.total_seconds().head()9 d) E( ^; ]! U
    Out[65]:
    2 L4 r" B% m9 w# l0    274.0
    " C- L6 i- `, G7 |1    260.0
    % B5 N- Y" F" l. m2    322.0
    % t* X$ d2 J2 b! ]4 h3    248.0
    7 a$ u# F- Y% g) O4    322.0, z; J9 u( o5 n
    Name: Time_Record, dtype: float64* c/ S; l2 ^  b) X. ]& D% m" K
    1
    . Z$ ]4 f. ]! W2
    4 G- L/ ~$ i, D' h; {+ k* A. U5 \3* q; {4 o" E# @) N
    46 H. ~/ w* A5 S8 d& U
    58 b& L- t1 N1 m$ x
    6
    % A' Q7 W# V: G# |7
    4 g1 l: u, q2 G' c4 ^8
    6 G  }7 h' A3 B: i( S9 a与时间戳序列类似,取整函数也是可以在dt对象上使用的:
    2 v. s$ s! a6 `
    ! o! J+ ~5 c# w$ G: \9 X" rpd.to_timedelta(df.Time_Record).dt.round('min').head()
    1 {# `3 ~% z3 W; {% ?Out[66]:
    4 ?- x4 E: z; k  m3 U0   0 days 00:05:009 ]4 l. }# {7 _
    1   0 days 00:04:00
    : i% q6 p0 ^% n, h2   0 days 00:05:00
    " t3 C; y; [( m3 v' P- ~! v' d3   0 days 00:04:00
    0 g" ]0 X) f  c$ Z: A4   0 days 00:05:00
    % m* S. @1 `; DName: Time_Record, dtype: timedelta64[ns]
    8 Q7 M5 k4 j" ~% u. [% S8 @) q& P1
    / o1 q! \. W0 M" p# Y7 g% K' @* b2
    : n! w, i0 b( ]$ u. t3
    # b7 c7 B9 J- [8 H3 L7 z1 h4' V( P5 X* e) J5 i) Q- ?  {6 B
    5
    $ \" [4 e$ t7 M+ i, E$ Z6
    & N' n" D, ]. N3 p75 }' ^$ c. B( F
    8+ ?5 C, k$ g0 c7 |6 L0 L
    10.2.2 Timedelta的运算
    7 x  I' r  \3 G5 E# D- C0 u* }单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:7 T1 Z& y, t  G4 {9 g, |  q. E
    td1 = pd.Timedelta(days=1)
    5 e+ I. M; q# m2 btd2 = pd.Timedelta(days=3)) n* |! o; Y2 [, k3 a
    ts = pd.Timestamp('20200101')5 N( ^. e1 `! X9 r
    : H4 J$ ?& p1 A3 [! {
    td1 * 2
    : q6 W1 ]0 W, B# f7 f! ]Out[70]: Timedelta('2 days 00:00:00')
    - ~# E2 ]/ @* l3 Q  W
    0 D3 T# i2 \# g# Z) c2 k! f7 ftd2 - td1
    ' |$ j) E1 q3 \: v, xOut[71]: Timedelta('2 days 00:00:00')
    + y6 g1 X9 W3 z1 R# D9 x! |* p5 |) x' `0 a# T$ T
    ts + td1, @  X+ |$ [* H
    Out[72]: Timestamp('2020-01-02 00:00:00')
    , _3 }( M4 X+ p+ w, q$ A
    2 f, X; K5 u( W: F3 [) L9 K6 `# tts - td19 e8 r* ]% i( b) d1 m
    Out[73]: Timestamp('2019-12-31 00:00:00')
    : O, _% f9 ?! P* b0 z  ]! d5 K+ G5 \1( |& w+ |8 s# Z3 i$ u  |
    2
    / A  J% z: |7 D3
    . C& `' W0 o' W3 f8 [45 N3 E, a. W' D  o3 _3 m/ k
    5
    $ D$ b2 M5 k9 B( ?# x6
      B# j. t4 T" X8 q7
    ! f! X% U4 \& c  U8* {) Z% O) i: ^" j$ E+ p
    9' {0 x: B/ s. C$ L
    10
    1 c) f' M9 U' P. V+ E# Z11
    - Z/ b$ T+ c# s, H) S' ?12) p3 E4 E& B) A& {# p: P
    139 A5 x; G& |/ W! K0 C
    14
    6 \/ D3 j5 `1 y6 N: m; j5 y: s158 V7 ]0 P9 s+ B
    时间差的序列的运算,和上面方法相同:% ^. y: V( e0 I* y/ R2 l( p$ t
    td1 = pd.timedelta_range(start='1 days', periods=5)
    ( M/ `# v. F. S5 J# t5 Utd2 = pd.timedelta_range(start='12 hours',# T9 }: p5 c* H* J. K) s
                             freq='2H',1 J; ?2 L8 ], L; V1 F
                             periods=5)# n2 z' ~# T+ \1 ~1 H5 T* B
    ts = pd.date_range('20200101', '20200105')
    4 |/ h5 u/ ?1 o4 X. Std1,td2,ts4 a. _. A: I) i: I. ~. M/ z

    , ]" p: R5 h5 s% y% ZTimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')9 `& M0 D+ {8 C$ w
    TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',
    3 e  x! }- G0 F" o+ r  c! ?; w9 L                '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H'); s. i" h1 o: v" _4 u, c/ H
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',
    6 B* z% O# ~6 g* u               '2020-01-05'],2 E8 ~* {! w8 F4 O- x
                  dtype='datetime64[ns]', freq='D'), ?5 c3 |9 n% R9 I2 H8 ~
    1
    + p! B2 m8 l- \1 I7 H4 j2
    5 i# A$ D" p0 ]3 J3* Y' J% F% ~* F1 }9 i6 \/ h
    4
    & x* @8 ^7 m# S; b, {3 C4 ~55 }# `( C) H- H5 G
    6% s) e' n7 S5 H% a6 s' n8 ]
    7( R' |: y2 L0 g/ |+ z) c7 e& m
    8& x# \: H& t8 H+ D: {2 P
    9
    0 O  l' ~, T* ~+ K9 w) L. g; U! t5 I% j, O100 y2 r7 r3 C* G% @- @7 a
    11
    ; ^/ B- N  @+ L  _0 o' n122 h1 [. l4 u8 d$ e- |
    139 [1 `, h" v) A( D8 b" I! \
    td1 * 5# s( x1 V% `2 o
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
    : r8 T: y2 x$ j  }, F; `7 S  S' X' ]8 r* N6 j. @) o
    td1 * pd.Series(list(range(5))) # 逐个相乘: P/ F! M4 j# h3 {
    Out[78]: . O9 R- _1 y5 j  H/ B
    0    0 days
    5 m& t% g0 I5 y% M0 a' L4 {# H1    2 days+ R& n: L- D3 o5 c
    2    6 days
    . ~- R6 H, s- t& z6 o* }3   12 days5 L7 _  E- F- z0 r6 Q% A8 \7 s) W" y( ]
    4   20 days5 P/ L6 t! t- g# [3 i! G. x3 _
    dtype: timedelta64[ns]2 L/ I' ^# i* d; l

    . V# {3 A+ |8 U4 x" ?" h6 \5 u$ qtd1 - td2- o0 Z; l- s) T
    Out[79]:
    $ H& |0 q  R3 {2 eTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',7 _! H' N/ Z* k2 f
                    '3 days 06:00:00', '4 days 04:00:00'],
    6 s- V, R- q. |9 W# Q8 U+ B               dtype='timedelta64[ns]', freq=None)
    & {+ x0 D7 e; B# p* ?$ H
      {) \$ K" W: b, Y- M/ L. Qtd1 + pd.Timestamp('20200101')
    # V3 \/ _2 T  c4 m. IOut[80]: & h( L& q0 p) Q% T! O5 y
    DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    7 h4 P5 I: e7 R' k, n               '2020-01-06'],dtype='datetime64[ns]', freq='D')
    ! i4 m. Z$ C& u- @
    5 [7 d; F* k2 X3 w8 Htd1 + ts # 逐个相加. }; m; c, F1 D% X$ X8 d5 A
    Out[81]: : n( [! b6 |) p7 R! N
    DatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',
    2 r! A8 g# b% E9 c6 [               '2020-01-10'],/ f1 I% L. J# L
                  dtype='datetime64[ns]', freq=None)
    / v5 _! d3 x+ f& _) r$ W$ L! w1 ]3 V" w: J
    1
    1 K! z" C, |: T- H" J+ |2
    5 y0 a8 p# }; M) g3
    - n; T' N7 L* f2 }, W6 \4
    * @" w" x3 z0 u5
    3 ?7 b& o0 E7 A0 Z6 Q63 t3 b7 F: u- e
    7
    : j, ~# y  d" a: R- P84 s2 G' O6 z. i& z0 r6 r2 g; q
    9; g6 z6 d0 {% m1 r  Y, }
    10$ D4 C$ ^( M1 ?8 M
    110 b5 [8 i+ ?3 w! k
    12% H, @3 N6 Y! h  I
    13# L! r$ g" R% u' o9 q4 U
    141 i+ c6 c% q% _6 _
    15+ k2 S3 z% G0 a& m6 F: o1 n
    16
    : E3 M2 K: N% Z* m3 \8 q3 S17
    % S- T/ r( T3 r9 f7 p7 ]0 p" r  a180 P, ^9 @& q, i, l' ]1 b
    195 h' u, G2 S. l& s4 @. @
    20
    ( v* h: S2 w/ ^; U0 P) ^; q' q9 h21
    . F* e0 l7 T% j: @220 \- o2 `7 e5 P9 R6 \( ?
    23# y  c0 |4 B, N6 V
    24
    0 u, ^9 P0 L, x$ f: w! M" e8 D9 K$ h" ?" |25+ b7 ]5 G" [3 G: i- w8 }  M+ G
    26$ i; B& g0 b$ {3 G4 l1 i
    27
    ; z1 H$ q/ |4 ]2 B" D* I! b/ H28
    ) E0 s2 X+ k( f$ c1 O$ D$ w3 {10.4 日期偏置8 G& q+ ~. g% C9 d7 Y
    10.4.1 Offset对象
    . X( @& S/ Y8 ~$ T: i8 K' k  日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。
    ! t! R0 V$ W8 J6 j# ?( P9 b8 W7 ]! S
    DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:
    , ^" R9 \+ H- U7 R9 ]
    $ a3 s1 p6 R. Ks.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
    / Y: g& e. i. W& ps.kwds:{‘week’: 0, ‘weekday’: 0}
    . d+ R. Y- W  C7 p) u+ j6 D7 As.wek/s.weekday:顾名思义
    % z" O+ o" o! a3 a5 K有14个方法,包括:
    + A! F5 r7 [( b7 `
    ' P3 w3 n/ k0 h+ s8 c% n) A5 p1 dDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。2 w5 s: a$ M  h
    pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。0 a, F% R  m" s& |
    6 F) g$ s( c! J# z/ E
    有两个参数:
    ! }) z6 p* C8 h( Cweek:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。
    : A" J3 p* [; F0 o; Qweekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)
    * g& _2 s0 s; F# u4 `6 z% `: `. lpandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。
    " T/ K% t6 W  A1 i2 o1 I4 B# [) m$ U& {" J2 B( h
    pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)
    . T. y. {: R7 c- V" e6 c$ V) ~+ {Out[82]: Timestamp('2020-09-07 00:00:00')) E8 h8 ]4 L5 M5 x  b9 A

    + b$ G0 l& f) M1 ^$ }1 b% Spd.Timestamp('20200907') + pd.offsets.BDay(30)
    ) g7 O4 O) Z* o, L3 r3 \Out[83]: Timestamp('2020-10-19 00:00:00')8 d+ ]- m/ `: k! ?$ d
    1
    8 ^4 s7 M" `" s; b2
    2 @* S4 X: d/ B) Q2 D; x  D3. \2 `1 J) v# s  y) a; D, L7 L5 j
    4  B; L8 r( `4 ]# ~) V/ `
    5
    6 n0 E' f$ H. }" a( X  从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
    4 O" {( c# d' q3 e8 Y2 ]# e1 q: F  N4 d( B
    pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)+ a) s7 ~) ?# z; h! v; q; v
    Out[84]: Timestamp('2020-08-03 00:00:00')
    8 \  D. R  F/ S
    6 U3 K6 [8 o7 N+ Upd.Timestamp('20200907') - pd.offsets.BDay(30)
    : ^$ H, k. S% _7 v6 w7 ROut[85]: Timestamp('2020-07-27 00:00:00')
    0 h) I- Y, G. ?! |8 S& i) E( {5 l6 `% V# u' J
    pd.Timestamp('20200907') + pd.offsets.MonthEnd()
      i2 Z; q; V& j% ~5 }# C( ^5 tOut[86]: Timestamp('2020-09-30 00:00:00')+ x. u# Q& c! H/ r+ R
    1$ F8 C6 e" [. G
    2. S4 d9 c, x8 a0 @
    3& M2 f$ |0 c5 r8 g1 a8 v! Z
    4* |# H: n% ^8 E/ D2 Z! O
    5
    ) U+ ~9 e5 w$ w. K4 c; @4 N7 d6
    / \3 q% l, E% m! z* f. m78 D  q$ p- p! `  |& G7 K
    8- y  F6 \3 _8 S& O/ W7 [3 l; K
      常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
    5 \6 q* R- o( o# B4 v, F8 A  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:3 `$ u! |1 v! K' v  D; N- `
    $ u5 x0 S- X. L' J! X  h
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    / m0 T2 o& ?  Z& Qdr = pd.date_range('20200108', '20200111')1 t, j6 B& Q& ~: E2 A; W/ C0 v
    ! W, \. k2 L" N
    dr.to_series().dt.dayofweek
    ! \4 G8 e/ U/ e( NOut[89]: 9 |8 E" A1 v' j8 W, J
    2020-01-08    2
      D+ C) T3 H( w2 j9 }. J2020-01-09    3
    / T. V+ V& W* W7 N4 O& O- _2020-01-10    4
    , @- i6 w+ L# H! w* C4 v4 L1 t2020-01-11    5+ d7 Q) v+ l" V$ \6 I
    Freq: D, dtype: int64+ M* z* L4 q4 i% L/ C# H( C

    # b( n" `4 j  \( Q[i + my_filter for i in dr]
    % _3 _9 M! Y* k2 _Out[90]: 5 {/ M, v) `1 v4 |  t. H2 y  D; N
    [Timestamp('2020-01-10 00:00:00'),
    . C7 @6 ]0 y- x1 v4 E Timestamp('2020-01-10 00:00:00'),
    # k0 M2 H- S: `1 w& X Timestamp('2020-01-15 00:00:00'),
    # i+ o; B& F5 F& k Timestamp('2020-01-15 00:00:00')]( ~- W7 o5 Q# Q& H7 F# h

    ; l' g* i0 A# r% {) i( D; N# T16 H* y2 B9 J. |
    2( J/ Q/ l8 u2 Z+ h
    3
    0 x( _9 l$ k" J% _. d4) P* {. E9 v& A; i& v- i1 ^
    5
      z, V% _; _3 X6 y. _0 ?% a6
    . H- n6 k5 |; m; i7  j" n' P: F- f+ g  [8 @
    8+ O+ c; d' u! Q7 D) K, U. _! m% _
    96 X# |$ }( c# n) A  Z# Y
    10, v* h: e, I" b4 F4 n" `
    11
    # r- D8 P2 @8 y! R% L2 V2 [12& K- e$ R3 f! D4 G4 O4 H# N
    130 R/ ^' C. Y# y7 p& R) Q. J
    14% P! e$ X9 [9 e6 z+ W7 }
    153 J  \; I: p) ?
    16
    7 |- U: u" }, U4 @9 g17
    4 O; B. k: ?& Y; s# p; F7 ?  上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。, W  r8 t$ q3 e$ |; c, m

    1 g; x8 Z1 ?8 G8 k2 ?【CAUTION】不要使用部分Offset
    * W* z* Y2 G6 \) P' _- @# l; v2 Q# v在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
    0 D+ P" n( g8 X/ \) L$ e
    ) i3 D5 K! P. l% ^/ Y10.4.2 偏置字符串
    : a) `& S8 ]7 e4 _: u. C6 x& P  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。
    4 j) `3 Q6 _( z7 \1 _, i
    1 r6 q/ f$ j; [) k  Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
    # A' c6 g0 T: y* F& v  ?- a
    4 u$ @4 Q. g; L/ o$ zpd.date_range('20200101','20200331', freq='MS') # 月初
    $ r& A9 G4 ~" f0 ^/ TOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    ( N9 y/ `& C2 X# F" N5 h2 r) v5 L/ M: x1 E- q7 N1 ]
    pd.date_range('20200101','20200331', freq='M') # 月末$ L9 b* _7 p  {1 i
    Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')0 U0 V1 c9 Y+ p" X1 T( b& o" x

    % s1 W+ R  h  ~pd.date_range('20200101','20200110', freq='B') # 工作日
    / u- Q/ F9 L7 y3 ZOut[93]:
    ! _3 L+ |7 i) `' CDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    # Y4 P, _/ Z( C               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],1 U* o  _3 F* P! C6 s* m
                  dtype='datetime64[ns]', freq='B')3 i) g' z$ u8 i7 V6 L! m. k
    . x2 r8 }0 y0 A9 H7 a# U
    pd.date_range('20200101','20200201', freq='W-MON') # 周一
    ) b) W; A* b- d5 E/ K9 A6 COut[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')  s4 W% Z- J7 K

    4 b+ u+ w1 N6 d% b1 N, Fpd.date_range('20200101','20200201',
    & `) Y+ Q  J% X) s- @7 W; k, P/ \              freq='WOM-1MON') # 每月第一个周一. y- `3 v$ M" a) C% T7 J
    8 x. p7 S- c0 |$ e
    Out[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')6 [4 j3 ~; s  k  w

    1 @, Z& w" d) p; z# j1
    1 d6 w7 f- m2 p% s" l$ _25 @# ?% D% j% K
    3/ ]$ W3 T" a0 I, _4 B- v" z5 |, B
    4
    ! j$ z( ~1 p, v5
    $ J+ u' O: A' M. s) A8 q2 K6
    4 }3 ^- R# U1 Q8 C/ t: R7+ C" r$ I1 [2 S5 O/ u9 q1 W, f6 y
    8
    1 N0 q$ V1 u9 y2 e* x9
    8 ^- V6 b/ L; W: I10
    ' D5 i% z( Z, d11
    ! x- w1 l0 D& p12
    ! x- g1 H2 y3 J! W6 I- _13
    ! a) r  C  `2 d- j6 n, ?/ R) u14; O- m9 @$ L2 B& h) ~
    15
    ! q' X! l& z! g6 v# c7 T8 ^16! @8 h/ p! m- i# V5 h
    17
    ( r; U/ N* q4 [$ P18
    ) ^: a8 _% b5 u% Q, |' ?5 N19
    + D1 ^6 f, `; [0 V上面的这些字符串,等价于使用如下的 Offset 对象:
    / n( ~/ e6 P% R. y+ j# `7 z+ W; N! W% H- D0 Y6 F
    pd.date_range('20200101','20200331',
    ) j9 T& b& c! T; `              freq=pd.offsets.MonthBegin()); f6 j6 P! O# f/ _

    * v2 ~! L% e2 wOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')4 b- C' U! i, \: [9 r' s
    6 w  U7 \. T4 B! G% C( K. ^: J
    pd.date_range('20200101','20200331',
    , `. D8 G4 h% q3 S- e              freq=pd.offsets.MonthEnd())
    ; r3 A6 i7 ^2 u, Z1 G$ ^1 T( _
    * u2 Z) ?* s7 J8 ?' h5 oOut[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')! w: o/ E8 R7 b/ V7 c7 p4 K9 ]. t
    5 [$ p/ ~( j6 a- r6 B! I# r
    pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    2 @! Z3 c; A  y5 c6 H$ S3 QOut[98]: ! {( K  D% H$ E( b9 I+ ]7 l1 A
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',# l2 D3 y3 i' H: e. K" }
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
    1 }4 J7 h+ `/ O5 \+ R7 [              dtype='datetime64[ns]', freq='B')
    : `7 g& C; a/ p8 V
    ; t# ?. N! r: G  @pd.date_range('20200101','20200201',  R: P4 b3 C1 D2 o) K
                  freq=pd.offsets.CDay(weekmask='Mon'))2 V% ?8 a2 m' s% z
    ; l; F1 _7 p9 E& J5 C2 b
    Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')+ i- Y5 b8 y$ ^$ M

    5 I, U5 s% t$ H$ Y# tpd.date_range('20200101','20200201',
    " W. k& h' D' P2 Q- k2 h0 q3 m              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))+ ?& M: c& P( r  r! ^( F
    % R% u3 S+ E* B  y/ A. W
    Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON'), h! \: \; E" B, O6 s

    4 `9 y- x  s, Y# e! y7 G16 r6 Z2 I/ g& |9 \- \
    2
    7 h2 r) a0 I( l0 t3% i) ?/ Q5 f8 [
    4
    - L& f9 p2 O# h  {6 O5: g* G! i. `% p( F0 t" ?, M& a
    6
    # @8 d; G  D7 f7 w4 g. E& g. i7& s3 \( x0 H2 X
    8$ _4 E4 T' b  C  J2 l% ~/ C
    9
    ; w8 \  X' g  E  p' a8 `1 O10) g; N- ^" ^/ V+ }' v  O/ t
    11" {2 `( i  F6 b* t# a5 l2 C9 _3 D+ x
    12# Q# t2 C1 L- A0 l9 `3 B8 b. w
    13+ O5 r4 U( k0 M1 [# H$ {+ f5 S
    14# I3 V+ v: o& H; I. I( s
    15+ O* t2 b* Z* r! i
    16( P( |1 {7 Y# o
    17
    - k2 @+ `3 e7 x7 U) U18
    ( F/ z7 \: n% b- K195 s0 ^9 }& ~% f
    20
    ( A6 G. I$ H3 I+ T8 X8 f/ w; m21
    5 ?4 B6 A4 p' C+ L5 Y; t& ?  G22
    6 l6 y5 ]/ r6 Z$ b+ |23
    0 l% s5 N3 o! C+ U4 h243 B- ]: A/ X; A
    25: ^  o- H. k4 Z: p2 i- s) g  o) J
    【CAUTION】关于时区问题的说明0 s/ I2 p: g% r8 t' v# v
      各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。
    ' x, L& X" q4 n/ f; `5 @% [6 G) M& P$ M7 x% \( m  S" p& [8 k! U8 v
    10.5、时序中的滑窗与分组
    9 }  U  q$ R  X* z9 W10.5.1 滑动窗口
    & `, `3 W/ E6 P9 a  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:7 F1 a  `( |# K7 r! ^6 ~

    - \1 U; t, d) r# gimport matplotlib.pyplot as plt
    ' ]3 [  m' |! w- Aidx = pd.date_range('20200101', '20201231', freq='B')
    ' {& W  I7 w  n" I0 Onp.random.seed(2020), ?  W( B7 X, ~! y

    9 K8 r( A8 r: {/ |; }& A" i1 Q8 }data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加* j/ B5 s+ U4 z8 _* l! J/ E
    s = pd.Series(data,index=idx)1 H9 U: d# T0 u& O' @, l; s
    s.head()
    / D; v- T' S/ ?# \& Q! y  aOut[106]:
    ! n3 Y# n4 I+ B0 R3 A5 o2020-01-01   -1: u" O4 k9 q! l6 r
    2020-01-02   -2
    1 j" O: g; ~1 y" p  W5 F# L2020-01-03   -1
    . i3 G, A& }. L- c2020-01-06   -1) q0 S, m+ H  _) I3 x
    2020-01-07   -2
    . |3 |3 A3 P! t4 r5 EFreq: B, dtype: int32
    $ Y, M) D6 `6 M8 Q, b9 z2 ~/ mr = s.rolling('30D')# rolling可以指定freq或者offset对象0 f! h6 [6 M0 a  N1 z  m4 a
    ( J& ~0 t7 B% I' A' S
    plt.plot(s) # 蓝色线4 r, x+ R1 D# D* H/ U2 T4 o
    Out[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]
    4 M$ W, |) Q7 @* jplt.title('BOLL LINES')
    ( K& G7 J" C) K! u; U, GOut[109]: Text(0.5, 1.0, 'BOLL LINES')
    2 ^+ k) T5 t9 G' l( V( F
    0 q* O" H" d0 _plt.plot(r.mean()) #橙色线
    0 V$ a/ Y/ ?9 z- s5 }Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]$ a4 g" g5 I" h3 b: q

    ; g7 V8 a7 c+ ]3 hplt.plot(r.mean()+r.std()*2) # 绿色线
    & z: }9 }3 M( \, {  b4 n7 {, @  c2 IOut[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]
    2 d  f8 m6 ]  s9 K4 [1 Q& |& S3 P$ w6 u2 _" e  \$ l
    plt.plot(r.mean()-r.std()*2) # 红色线
    2 a( {( j9 d5 E" R+ r& y- pOut[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]* L3 @! `' v' D; T

    7 r) C5 N; h/ U9 X; I4 }# x8 A1
    $ x. @) z- U& }, X- `, B# v6 {2
    8 L! i9 S. N$ n/ i# r! P9 X3' P0 Z. N* O7 ^/ s# t# i' F
    4" f. [. ?) J0 x
    5
    ( O" j) V9 s8 T69 Z/ v2 |+ L% P% ~" [7 K
    7
    $ I( Q% k1 ?$ m8
    2 K1 v* \% k; g: V9 G- Z9
    * g- u7 }, y/ |# c$ ?/ `10
    7 k2 o: l# C5 N5 N+ ]) |11( q9 G6 r/ P1 ?
    12. t" T' {+ a5 B- u% @
    13' G; Y  b4 V1 X; D
    14: E; s/ u, A% T4 d. C. z5 a2 k% p  E
    15* O. t0 }  X( T. X$ F/ s
    16
    - @1 n  w$ X1 e  X3 |# d17$ g8 K5 `2 m$ [9 _9 ]- g- j
    18
    & }0 V$ z* }( M4 r7 u: J( o195 T8 T5 w( Q& |/ j2 M: Q' i
    20
    , a- |/ X; w0 S21
    " C" v; |: z6 `# D5 F9 a: g22
    ) T# m, ~' V9 k4 O$ r$ z+ z9 ~* S23
    % ~5 P$ i9 k: B, K5 K24
    ; \4 a* n% ^$ F& _$ Q: Q25
    ' G8 E8 V  R; Q# c. ^5 G26
    4 }4 u+ c; e* d27
    & {- A3 L. a5 y/ N; l% \' C28/ A# {) t- t6 z% R
    29
    + Z: x8 ~: H  y
    % \- f: c3 m7 B/ c6 C# I+ s   这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。& Z, m3 c& v) B/ q0 m) Y( k( k7 N
       首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。4 `+ E4 @8 ?5 S( g; W! z+ A
    5 R2 V8 Q  V0 s) x2 B+ u/ ^- D9 n
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]2 ]/ S9 j* G- h& r$ G* D
    bday_sum=select_bday.rolling(7,min_periods=1).sum()& r5 q( Q' x+ r% l6 |  Z
    result=bday_sum.reindex().ffill()
    ; b2 K2 u; A* n7 ~! T  A! h( Mresult
    , U" z8 R. s# s- h/ R2 N2 c) Y0 i$ e  o- l% m. e8 }. \" y
    2020-01-01     -1.0
    ' F1 y( f) P: L) A7 R2020-01-02     -3.00 f$ R) x! N& O
    2020-01-03     -4.0
    ( H; f$ a  @, e4 Y1 K& B" j6 N& t2020-01-06     -5.0* u0 G. a: `3 s/ x0 ?2 X) g+ ]
    2020-01-07     -7.08 X/ [, N! K. ~- L8 D% D% P
                  ...  * C( w8 O! q3 `6 L& s) H
    2020-12-25    136.0! `' {" b2 D4 b) a  l
    2020-12-28    133.0, |3 j" l- l. W$ W
    2020-12-29    131.0. C; F8 O% p9 d% ]$ S
    2020-12-30    130.0- `4 @$ D* h4 }$ Z# E, f& l
    2020-12-31    128.0
    0 l4 `" w6 l' S9 }Freq: B, Length: 262, dtype: float64: y1 u4 K# ?/ L( p/ o
    / w  l% y5 U* o
    1
      {' H% v4 U0 I8 G4 B7 g# o) @2
    " y' |4 z% b# \; C1 R5 d) X3
    , L  V5 |0 w! f4- G2 d$ l) Z# A- Q2 S, B
    5
    0 H! a* N( l0 d6
    8 e" x; G( S5 l) g79 {1 D# H4 m, |' ]0 `
    8
    , K$ \+ z: C0 j! \# L9
    8 W7 J0 P# q: v4 b' x1 w* _& o101 m- ^7 Z: `( R  c) ~5 i4 M8 k
    11
    % `$ t- f. F, [9 z) f6 T12
    * n) ?$ d% t& i+ T& M13
    % n0 ~; F4 g% ?3 `! E0 Q. f( o14
    - \: N$ D( X2 ^- F" k: d151 c) Z" w9 x9 Z" z& w$ T
    163 E. x! `& _& b/ @
    17
    / ]7 ?. P& H7 Q/ S, H, w  T0 `  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
    # |+ n  p! N9 X  W! b1 |7 X  @8 l: x5 W4 r$ e
      对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    ; E# G& w+ n! }0 q- S4 }
    ( Z8 W! O1 a* }- V1 gs.shift(freq='50D').head()/ I2 I0 D3 u$ l. _, l3 |& e
    Out[113]: 0 x9 z6 K; C6 t
    2020-02-20   -1# Y1 N# ~5 {& k* J
    2020-02-21   -28 n( x- l% N! J/ n# l! S. ?
    2020-02-22   -1
    ' u) S3 q+ @& I. u! M: W2020-02-25   -1
    # S! z  {4 B( Q7 E2 ]# n! l  M2020-02-26   -2
    # c+ y3 y* k/ y7 ^: ^dtype: int32
    $ l# c# T0 X" Q/ U: Q. r( ~& e1
    ! Y2 L: `' g2 K9 n# J! g- M3 \- r2
    , D4 l0 Z2 O# w3+ t: P/ ]' i$ B9 h
    4- A0 y4 }/ |" q8 I  \8 O
    5* p) I- Z* x  y( [4 Y: X. e
    6
    / k# H" ~2 D" r# H7
    # R8 A/ c4 T6 W5 b8
    9 h( t) D* W; {0 l  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:
    # ]. l( p3 ]5 w
    % G9 ~$ R/ t1 _* ]$ @$ r1 cmy_series = pd.Series(s.index)
    1 k5 ?2 e4 i. E: {my_series.head()
    1 q+ b8 p: \3 kOut[115]:
    2 \4 m, |7 n. U+ g: l0   2020-01-01
    2 L* d4 Q& [$ }+ S* m( U7 N1 K1   2020-01-025 N, C0 a# Y# I9 m& f8 e9 T. q
    2   2020-01-03
    & L- G4 l" v9 _+ h/ G3   2020-01-06* Y; n) s! i* c5 x3 E: V7 q
    4   2020-01-07" g: b1 }+ ^1 P* I! v$ U
    dtype: datetime64[ns]5 ^2 G8 O* X5 ^  p

    ( U' s8 G" A* s3 ~my_series.diff(1).head()
    0 d6 A2 d& F, u  D% ZOut[116]: 2 v8 Q  X: J- y
    0      NaT
    # T- R; o# l0 B  F$ j: i1   1 days$ Z" |( b* u# n: @0 v: d" _
    2   1 days
    ' g  r  B5 n% c7 A3 \3   3 days
    , F4 @7 l, C% O7 N5 Z4   1 days- b: G7 E' C  ~2 N" V$ o4 @! p* Y. {( q
    dtype: timedelta64[ns]
    ; z  t$ C8 ?" n5 J  t! S4 V, r9 @
    6 `' J' m' S5 R) J10 D. M# r  d4 `6 J/ `9 o) c
    2' p+ w, V/ @# G0 J/ ^
    3
    * q3 ~, R- O. C! Z, y49 {9 W5 W4 T$ d& \% c  d2 p8 Z
    5
    7 x6 B$ X5 R. o3 y5 v6
    8 ]" u4 }9 k: X( |2 [. n/ |! i$ o7. Q8 f7 Z" e- `
    8
    6 F+ T7 }0 g+ E2 [$ I% J# c! i, n9
    ) ^  K+ h4 v" Y% Q7 D% h) X4 U; @: F) C103 j( D0 _$ w0 d! q1 q' p" L
    115 @# S" Q2 i( B# q
    12: }# A- n* Y9 b7 s' t9 K
    13
    ! b$ I( Z5 |) p( m14
    $ N; [/ f) k4 H. l- K/ l2 {158 V+ N( c: l3 _6 K
    16$ f) g1 R$ ?4 x. R
    17
    / t+ y3 s8 l! h6 t/ z' D18/ B/ S9 i) Y% M  m
    10.5.2 重采样
    6 w1 p8 j3 F4 w! ]  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)
    / U0 X* K' p: H' g% a/ W5 S常用参数有:
    / P- k) x% a* C' s4 f, w0 F$ b" i5 |# R1 V/ X
    rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象
    + _+ H% @7 P, D( Iaxis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样
    , n- v0 N: A' G; `closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    : ~" u3 ~5 M$ [5 d4 ^$ r+ Hlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    ! K3 [. Z7 }" c" Aconvention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。$ N; r; E9 }% \
    on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。
    4 v- ]3 o( h; _/ ?7 e! x/ ^* K0 Ilevel:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。
    ; |9 ~) o. t& gorigin参数有5种取值:5 {3 F- O, @, Y( D( j
    ‘epoch’:从 1970-01-01开始算起5 ?+ N% K  c6 S, V) A" `
    ‘start’:原点是时间序列的第一个值+ [, |# [& N  s7 T7 E' L% ~" l
    ‘start_day’:默认值,表示原点是时间序列第一天的午夜。
    0 I# _+ G' o7 f" o* z5 \0 B# A'end':原点是时间序列的最后一个值(1.3.0版本才有)7 d" Q2 V' S7 U. p. G9 k4 i; j
    ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)
    4 x) n# n; B, o# K  A# Woffset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。
      Y# _6 o; o! T: r+ u+ K+ C  closed和计算有关,label和显示有关,closed才有开闭。. D" o( v# O; _7 d
      label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
    1 [/ v+ M! ?1 p- y
    - J" r. J0 [& J3 r+ l4 T重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:8 v+ N! J" M$ ?8 ^5 w3 }
    s.resample('10D').mean().head()
    % Y) {3 a; K8 h! V6 n# N1 TOut[117]: - F! ^0 D, w( G- F+ S! p' H
    2020-01-01   -2.0000000 r' j9 {  M5 [4 M  @
    2020-01-11   -3.1666675 O: T( l( e( Z) Z
    2020-01-21   -3.625000& \8 z) N- M/ v$ |0 C) R0 F+ s; M
    2020-01-31   -4.000000
    6 O- H* Z) l* V* b( s4 z8 y2020-02-10   -0.375000
    2 t7 A1 ^9 y5 q4 a4 A' nFreq: 10D, dtype: float64( W" P1 C/ \3 }
    14 ]1 \3 L3 E2 |# i5 N& o
    23 r# `* O8 W+ t: A- F
    3
    & `' [. {; q4 _4  [% {9 U5 v* Z: [& M5 o
    5+ p/ f  W& b: j
    69 @( g' f: A' G% Z+ L: _
    7" g6 p1 e" K3 a7 @! T+ v
    8
    * p" n- l/ N5 X, E3 |' Y% a可以通过apply方法自定义处理函数:
      g- Y  _7 ]% Y$ K+ J1 `0 ]- _  ks.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差! }; z, N) g: }" v+ d4 t
    * @; |4 N! [$ D. M1 y6 Q5 x  r
    Out[118]:
    1 y1 F$ q& E9 r7 d& {2020-01-01    36 U% z8 k4 j, t9 E! {& ~4 Q- r
    2020-01-11    4
    ( ^& t4 Z4 W2 j0 `! ]0 J2020-01-21    42 A5 \3 b# j* v8 Y- g
    2020-01-31    2  w* T3 H9 j) M7 F9 z: W! l
    2020-02-10    4
    ; [  X: b' L: U( {  V5 J- s7 ~# h8 j5 B- dFreq: 10D, dtype: int32# U: y$ h2 {. Z5 f3 n& r3 a" E
    13 T* Q! a( l8 W$ N: O
    2
    + |  r9 r4 @" c9 R8 L4 {3 F8 ?3
    5 v: W6 K$ ?: h$ i4( W1 R3 ^6 s. W, B
    5
    & z8 |  Z/ t/ }  L3 `8 c1 S6* k  M- h" {4 n0 s, @/ X+ ^1 K
    7
    / z: X; B( ^. s9 W" e8 ]" p8 _# w8
    ( b9 `% }! M  v3 K, u$ i9 \9$ [! H  |9 J. u. e% J1 V
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:+ z) ^: l; r# z8 z# L
    . l% @3 o  R; I# L# n1 R; z0 |
    idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')& r  N. K: `  X1 C
    data = np.random.randint(-1,2,len(idx)).cumsum()8 m2 I) _. f: f8 A7 x
    s = pd.Series(data,index=idx). V% j3 s8 e4 @* u
    s.head()
    $ n, y% x: ]" u; ~" R+ `9 n/ C2 s  B1 K
    Out[122]: % p9 x$ d4 W9 E, \) q, L; X& i; E
    2020-01-01 08:26:35   -1
    ; H* G! C0 f+ W$ C2020-01-01 08:27:52   -1
    2 {$ D7 r1 P" J$ L2020-01-01 08:29:09   -2
    ) Q" ^/ |  C4 |% F4 p5 g7 `) Z+ P2020-01-01 08:30:26   -3
    6 L3 E( q5 j1 ?3 q: l2020-01-01 08:31:43   -4' }# }" J5 o- m1 s% c. f9 c
    Freq: 77S, dtype: int32/ h% c, ^6 w% O7 @* x) c) q
    1/ \% a  k9 O' V( `" m
    2" k' L0 G) y+ H) `$ u6 J, B. P
    3
    , R) b" q8 b1 V% g! @4 z42 ^" a$ ^* n5 J) |9 ^- d2 g
    5
    0 j$ p4 H$ N0 a) C6
    2 U4 s: X4 `" U. @7( {0 |5 @9 K4 _/ C$ T
    8
    - K, F1 S( L: Z6 j2 y$ P2 m99 R- q( b* K5 J: Z3 x8 {8 Y5 r% q5 A
    10
    + w  W& D6 C" _11
    8 I  ~; x1 s* X! w4 h9 h( O8 Q128 \! k( L3 w0 g3 N8 T$ j
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:2 |9 w3 ?/ `. q# `$ \# [
    ' t1 _3 H0 F$ f8 K, z
    s.resample('7min').mean().head()4 K2 n' ^( L% O: j2 w/ @
    Out[123]: * }1 p) y- W& Y$ I/ x/ n$ H3 K
    2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值  p: |& D$ f. b" K9 ]) B
    2020-01-01 08:31:00   -2.600000
    2 {# Z3 c2 V! p/ R2020-01-01 08:38:00   -2.166667& \3 D8 p% k0 k8 F; r+ T
    2020-01-01 08:45:00    0.200000( `( J4 C3 }: P- x0 ]
    2020-01-01 08:52:00    2.833333# i* H7 z+ y, |
    Freq: 7T, dtype: float64
    % |& _" x2 C! j. W# R  f3 B6 X10 x9 s, a& I  N
    23 w8 C* ?2 B8 w& N
    3
    / v" @$ C3 A1 b# Z* g0 W& }7 ^+ \+ D' N4$ d4 m1 p! H0 c0 [' ^9 L
    5* I( ^5 h+ `; s$ a7 Z' D
    6
    ) a! @. W% _: P" N7
      G2 C2 L, X5 h% h/ ?8 Y/ _8' }# v/ ~5 S; `
      有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:1 }1 T4 J8 h# W; g

    & ?4 u. J6 R+ K! Ms.resample('7min', origin='start').mean().head()
    # V! r; s& D: ]7 c" o' LOut[124]: ' }! e' }/ y) U
    2020-01-01 08:26:35   -2.333333
    * C. {* I# u) g8 t2 m" s2020-01-01 08:33:35   -2.400000
    . M) d3 W) \; y3 u% ^2020-01-01 08:40:35   -1.333333
    " t& p) Q% j+ b: X  _+ t3 _2020-01-01 08:47:35    1.200000
    7 e) _' N+ B0 J) i0 |2020-01-01 08:54:35    3.166667. L4 [1 q6 V2 G/ g9 s* o5 u
    Freq: 7T, dtype: float64
    1 n+ Q% c; _1 a- H, m0 r' R13 L' |4 g. f$ }0 o2 ?% q& s! U* j
    2
    4 P; ^; K+ Z4 ~7 k; p0 Z3
    + v9 g7 R8 C; d0 T& x3 d4
    * m9 i- R" @7 s! r5% b, ^0 T& ^% m0 U+ c" Z
    68 m' K! _4 \4 j( A3 c( ~
    7
    - m8 Y/ P7 p1 b5 C8 B/ y80 {# `( }& V0 P) j5 B# h
      在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。
    0 q% R1 H0 d$ J0 c, ^! T2 ?: b
    4 E; S  {8 {. Ds = pd.Series(np.random.randint(2,size=366),
    / p8 n2 ^( A7 F0 O$ r              index=pd.date_range('2020-01-01',! K9 b& ~5 s9 j! X0 {/ i# U
                                      '2020-12-31'))
    ' z9 i/ u7 h% i1 A" I6 I0 O" [! |8 @5 U
    " c) e: S, B8 D
    s.resample('M').mean().head()
    ; |+ B! J/ T2 |1 A+ v3 BOut[126]: ' ?; F7 o3 w4 ?0 {/ L
    2020-01-31    0.451613, j1 N8 F  G0 j" }0 t: Q
    2020-02-29    0.448276
    5 u" M& p6 n- V2020-03-31    0.516129- H2 d  |5 q/ ^3 e7 p1 c
    2020-04-30    0.566667, S' N# ^+ a3 C+ S' C
    2020-05-31    0.451613
    " ^: U! M) N( C% ?# ?, b7 w% uFreq: M, dtype: float64
    ) ~- L7 }! Q1 R; U6 r. J2 J
    ) K$ T2 j+ e2 P7 vs.resample('MS').mean().head() # 结果一样,但索引是跟正常一样! w- }' ]5 Q. l1 }0 x' E* I
    Out[127]:
    : f5 t) {. I( K: A/ d" c' A& E2020-01-01    0.4516135 ?" S, J' ]0 G4 u& Z7 ?+ Z0 Y
    2020-02-01    0.448276
    1 d9 s1 V8 T+ [2020-03-01    0.516129
    / \  l" T" ]% n6 Y8 e; k2020-04-01    0.566667
    + F8 L" S, c* f6 |5 z) l# @  p2020-05-01    0.451613( k% n( W" T# [" u, u
    Freq: MS, dtype: float64
    ! e3 ~, X+ W" V2 b: H! ~- w2 H6 R% L1 `
    1 r9 Q8 L% j8 C. f5 d1
    ( v3 `' O* q/ B" o; ^% R+ |21 L( p) j, G; L2 [
    3
    , Y: ?" V: F  t4
    4 j7 }; _5 {2 @; c# L" W# c5- v& J" a, b, C4 G, q9 q
    63 f; ~8 ^9 x$ Y) Y! o5 M+ h/ @( n
    74 ^1 }- i1 G. m# Y  |) }+ H
    8# `( v% i( K0 O2 s( P9 u  ^
    9- X) q& z6 P8 \1 K. R
    108 u7 T# p0 o/ V+ e
    11- z5 z' x3 r! i+ d
    120 @) H& ^& }4 m: o$ i9 c
    13
    1 Q1 t) K9 p( R. z8 u14
    ; O% d4 ]" l7 ^+ g6 g! g3 {7 J15, Q7 w* j0 h) G6 y9 t- r$ r
    16" u9 b$ M# y4 t$ B8 o+ S
    17
    4 _: U+ H( m- x' I( N0 \18# H# Z$ N' H- G1 i
    19% Y8 n7 G* b$ f% V! k. S! G% b
    20: E  O5 q0 P6 X) a; s, \5 f1 x, V
    21
    3 E* w0 x$ J1 z. W5 K4 A22
    ' P8 E& }2 c# k; K对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:, c1 T5 D2 `1 A
    d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],6 M+ C# U/ A* K" o
         'volume': [50, 60, 40, 100, 50, 100, 40, 50]}2 F1 C+ A' W# }1 Q6 n7 f
    df = pd.DataFrame(d)
    ' L4 g  _1 d6 @2 @: I+ Ddf['week_starting'] = pd.date_range('01/01/2018',
    ; y5 G% e- g% n4 z: b0 h$ i                                    periods=8,3 G. {/ i4 \( ]8 H$ ^  }2 G9 N& t
                                        freq='W')
    % ?) P1 W$ Z0 G. s: X2 a8 rdf
    : p1 U' M  D. j; l6 P7 q   price  volume week_starting; [1 d; L/ Z# s/ R* u) j- X' Q) e6 y
    0     10      50    2018-01-07
    6 c3 W- N4 r  [' J4 A" x; i1     11      60    2018-01-14& U- j; [' A  i: O, J
    2      9      40    2018-01-21/ `0 n6 J. z/ Y! R
    3     13     100    2018-01-28
    : y! `: i( M+ N' W# {- l5 m4     14      50    2018-02-04! v: }9 |, o" z. _
    5     18     100    2018-02-11
    3 B% _4 n( k, ]9 s  f1 w. ^6     17      40    2018-02-18
    + y! K# A, _' ]  r$ c) w7     19      50    2018-02-25% e7 m: v. _4 J! v
    df.resample('M', on='week_starting').mean(); j8 ~+ N6 j  Y6 K3 e
                   price  volume
    ( T6 N! r- r5 ?; {9 Gweek_starting1 W6 U8 B+ p! J( B" h
    2018-01-31     10.75    62.5
    ; m  b6 s- I4 H8 Z, J% ~2018-02-28     17.00    60.0: p+ R( I% w7 G" Q- g( k3 L* ^

    . P$ t2 j# r8 J" n0 ^( n8 q1
    / a! b' \; g# I  R" V2
    0 s! m# |: H+ G! m  v, |$ D& j3& p: H6 S# e  T) _7 f3 e( o0 W
    4
    , Q. Q# Z+ F& v$ \* [2 h( L6 _7 F5
    ( Y/ I5 q" g8 g* a6
    9 L/ [1 o+ w5 p2 p. v0 q7
    6 u0 O( X7 ?; B0 A9 V  Q8
    $ F6 K/ ~3 T* w3 m- n7 Y2 d; {9 D9' q: b9 B4 c, n, p0 q. |7 r
    10+ y% h- n, w) g/ n' T1 o5 r
    11; B6 Q# u9 C8 _2 Q4 U; l8 Q& S# t
    12
    ! Z5 s6 j  n9 l13
    8 _6 M& P2 i" Z( [14) v  |; z2 P; Z; f6 u" b& Q
    15
    1 s; I' a" `: F- N' E16( U" F4 u, e" p2 l' p
    17- F1 o7 a; @7 r7 ^4 c; d
    18; b& S7 Z9 [0 p8 W
    19, U! X1 i5 {. @. u# f4 K; A5 Z+ S
    20
    , K) K- l  V, y+ R( o21
    # |3 Y" ^9 b3 T$ n1 f. l" x对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。  V6 u+ i2 l" _
    days = pd.date_range('1/1/2000', periods=4, freq='D')1 z. U4 h+ v* v
    d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],$ V* h4 e" B4 E! T8 d7 T" Y
          'volume': [50, 60, 40, 100, 50, 100, 40, 50]}
    3 m& `6 U& ]- Edf2 = pd.DataFrame(
    ; K& o, A+ k/ z: {1 T$ I+ ]+ g: y    d2,7 Y" u6 R2 W* t1 n6 W8 F8 Z
        index=pd.MultiIndex.from_product(
    # R; m8 i7 f  Q7 x# Z# y        [days, ['morning', 'afternoon']]
    2 U* l% J4 J: D    )  |( X% N. |; T; A% N
    )9 d( @. s8 G8 G6 z. q
    df2: x: l* o" i: U% H7 Z2 E* b/ {
                          price  volume
    9 |( h) Z# Q( v7 {8 B" ^. ]* x2000-01-01 morning       10      50
    5 h5 L6 h) N/ g: J; c" f1 E9 T           afternoon     11      60( N8 p* R8 v9 W0 m
    2000-01-02 morning        9      40
    - @. k  o' z6 I. w; l: G           afternoon     13     100
    ( ]" _4 \- t/ S( N, a4 k/ @* j2 B2000-01-03 morning       14      50
    5 X* U2 B$ k  i6 R           afternoon     18     100
    % }+ u9 A/ w6 S  Z2 T2000-01-04 morning       17      40  h+ q: {2 {* G, \4 o2 V
               afternoon     19      503 V" [+ j) r: p# h3 K/ y
    df2.resample('D', level=0).sum()9 H7 n% Q) \) K5 \7 H: S6 U4 W7 \
                price  volume
    ( d* e( x7 S6 f2000-01-01     21     110
    6 W3 b1 t( a- }3 }/ X7 Q2000-01-02     22     140
    ( P8 L$ J9 m  A9 ?' l! o2000-01-03     32     150
    $ E$ @" A& n$ B; ?! r% J2000-01-04     36      90
    + t3 {7 s0 K' m
    / A3 I  }1 ]) u+ b) N+ _$ o1  X/ e: @0 E. Z- m) T+ v6 C
    2
    9 C) e9 W0 Z! E& U3
    4 e" V" S  p7 {2 X9 k4
    , n+ }8 z) L. M, V4 W1 r/ g8 J7 a, I5
    ' u# m! {9 w! z  U, {  s9 B6: ~0 Q9 i# h1 z
    7
    + k4 C' S. F- x, `  ?8 R8
    " T: ?' d6 J5 h/ X/ H3 m6 n9
    " L  E2 z  A1 W/ f! H# J& p" O0 j% ^3 ?10
    & l' S/ `) U: }( D, O1 I11; C- V7 P& ?2 y8 b7 G2 ^
    12
    5 Q3 h( q4 {* x; b13# y; \! L3 A2 E( R+ s! {5 Z
    14
    " g/ ]* x) |+ p$ g9 H0 \15
    3 L1 T( L  \& C! e5 V165 `: k6 E' v( W" Z, v" {
    174 R9 n; L( c! b0 J$ E% i: @' G9 a
    18
    6 r* |; Z& m2 e5 r- ~19
    ! _. k# ~. N% S4 x0 j* I  e20% o, M- Z" A* b; ]' I
    21+ r& n8 d# {5 T  r1 Y3 z6 |- f+ A
    22" l, ?2 [7 g! }! B6 {! B  ]
    23! `" J( y( o* |, u; ^' g
    24; j$ y; d9 ~' O* Y' O
    25: d# {. L) x0 [& l% ?3 T9 O0 S3 }
    根据固定时间戳调整 bin 的开始:5 t. }3 a! C4 u7 `: f' @
    start, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'7 p# D) [' ^8 _3 w
    rng = pd.date_range(start, end, freq='7min')
    * `4 |3 |: s% E  `) Hts = pd.Series(np.arange(len(rng)) * 3, index=rng)
    % _3 \5 m( R# V( G) ]ts& V  P5 D# j" R" _
    2000-10-01 23:30:00     0" c$ q$ I: j2 }3 f
    2000-10-01 23:37:00     3
    6 n7 K5 |& e4 U2000-10-01 23:44:00     61 r* m3 O. x* K2 Y
    2000-10-01 23:51:00     9. R8 b- v( j6 X; R2 N* W: c
    2000-10-01 23:58:00    12! w' ?+ T4 d# p
    2000-10-02 00:05:00    15
    ' j2 {; p. R0 X" u+ y4 [9 E2000-10-02 00:12:00    18  K, n& f8 p$ T1 @5 Y
    2000-10-02 00:19:00    216 s" P2 {9 x) l$ U( o
    2000-10-02 00:26:00    24
    % i1 o6 h- _- yFreq: 7T, dtype: int64
    ! }3 Z) u4 H1 X9 c/ [+ g# m% v2 _
    , O: F, R$ l  ^: M% J9 v! N4 O2 Tts.resample('17min').sum()
    6 Q  D1 F7 U% [; f, C* q* P2000-10-01 23:14:00     0/ j% `% h; N" m& s: H
    2000-10-01 23:31:00     9
    % X) m0 H" F% a6 u8 L6 o2000-10-01 23:48:00    21& ]0 o* M! e$ [- o
    2000-10-02 00:05:00    545 B& b, H2 B, G5 J2 O
    2000-10-02 00:22:00    24
    & q9 e3 |5 c. q( uFreq: 17T, dtype: int64
    : S! }( b7 U8 [1 P) W) w4 m4 ]
    $ a1 {" D, U% t$ ats.resample('17min', origin='epoch').sum()
    , d+ P2 |& h* v' T) G2000-10-01 23:18:00     0+ i1 s! G6 n. T
    2000-10-01 23:35:00    18! R' C' ?/ h& Y
    2000-10-01 23:52:00    27
    + Y2 L2 _- _  c+ {# [2000-10-02 00:09:00    39
    8 S* [; O7 D- K" h2000-10-02 00:26:00    24" n& Q7 X5 t. A% G  k% {
    Freq: 17T, dtype: int64' N% p- \& x% B8 A* z- I; o' W: Y
    3 F+ f7 S; z. g' y. t
    ts.resample('17min', origin='2000-01-01').sum()
    / [2 e9 \/ G6 O3 T8 T4 [2 W( W2000-10-01 23:24:00     3
    ; z! Z" r% M1 G) [) c& q2000-10-01 23:41:00    15
    ; A/ C7 d1 Z2 _3 ?; X2000-10-01 23:58:00    45
    + t: ~- V5 |- X! `2000-10-02 00:15:00    45
    - @# W9 f# N1 uFreq: 17T, dtype: int64' y( U# w& n2 {$ o- A; v
    1 _. b2 z8 e- K
    14 T; R* h/ X. p! n4 ?
    2) Z  p6 \0 Q3 \' m0 K& Z4 V
    3. B* `# X7 V. [% ?. G8 @7 f
    42 J6 i- y# ^3 d& U
    5$ X- f% g: J/ q0 \7 F1 O& m+ _6 l
    6% I! N+ @, A5 d8 q- A
    7
    0 P+ j5 ?* t8 V" s( ~* ^7 K3 k; f8
    3 L" A0 D  @  ?9
    - H  K5 Y) c/ W! I: @, M' x) q10
    $ |8 Y; x$ K% u! Y11
    4 Z+ r2 z6 q# m12
      ?4 c3 M$ n: p! w5 R7 C% T13
    ' g5 q( E# C+ c; g! D) V" g6 h140 B* Y+ D9 O2 y  A$ T) b' H
    15
    / N: _$ E% A+ s: N7 V, v7 l169 k* \; `% T. ]% L  F! [' |$ a. ]
    17
    # Z1 H7 y3 R& D5 _: V18( o9 o/ l7 t; z+ k* q# g
    19' }; d8 c/ U; y; w" w
    20" j4 b' s+ Y9 P* `- J
    21
    : _/ Y+ p% z+ F# I+ W# E22
    % w6 V7 P) N5 O0 K& ?7 r) ?236 }) L( i6 D7 f+ J" {
    24; }9 A+ i9 E. O+ W, V5 Y) n8 x
    251 v1 h) ]: }; O- w  {
    26; I: v! w4 p$ V; v
    27
    * B5 v9 l: a* {6 D$ @6 Y6 v28
    " c% J1 z) s1 H* t  I9 _3 I290 ?! Q, o8 E) D2 D3 u+ s( z
    30+ Q' r5 ?$ p- t& I( U( }0 K( e
    31
      @7 b- g9 I% p6 s. p1 A32
    ; J/ r/ B0 z4 u2 K6 o. W$ i334 u1 y+ `' A1 j, E) Z
    34
    . p6 t% A) p) G! T+ Q; z1 P35
    % U* n7 d# d% c+ W36
    9 s' D# ?+ j" b3 U3 l0 _37
    9 s) z8 _& C; @如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
    ) t$ A- f/ T$ v0 ]ts.resample('17min', origin='start').sum()9 t  Z( D" z' N# N
    ts.resample('17min', offset='23h30min').sum()
    ' N8 S8 e! U  @" F& h2000-10-01 23:30:00     9  w' [9 k$ D! _% X9 R
    2000-10-01 23:47:00    21
    5 P8 V- n- T  Z2 |. p* X2000-10-02 00:04:00    542 V% i8 ~' ^8 X9 t
    2000-10-02 00:21:00    24+ X3 q3 V' _2 n: N' I
    Freq: 17T, dtype: int64
    0 U% G8 i) V; K- u1; H- Q1 |8 T0 S
    2
    1 Z7 o  F3 J9 m+ U0 Y/ u) u3, x7 M( ]" m2 K5 s+ t
    4
    2 ^/ ~0 D8 e, l7 x7 a( v' w& l5
    . n: H6 {! ]! h7 R66 M% `# m; A: o: c/ J' y7 F2 ~
    7! v) i) X" H' V  {# _7 ?
    10.6 练习" s2 a2 [6 O! K/ }# Z, f1 l6 b
    Ex1:太阳辐射数据集
    2 k3 N4 |+ d$ s1 X1 s0 S$ g% N现有一份关于太阳辐射的数据集:
    , Y2 `# ]$ i; B7 P5 x* C, p- W, p2 E! d5 I' T1 Q
    df = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature']); r1 y5 _# d6 m# G
    df.head(3)
    4 \& c8 r" L, O# Z' n1 u6 f2 ~  |8 @0 e9 W! q
    Out[129]:
    7 a1 C( [: b9 [3 k" j9 T                    Data      Time  Radiation  Temperature4 f/ M' g9 j9 K3 F4 L( N8 G
    0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
    ; W  t" o" H" H9 |# Z( q5 Z% Y% q1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    * {, b0 {/ ]/ ~) o3 L: V6 ]2  9/29/2016 12:00:00 AM  23:45:26       1.23           48$ e. `1 U4 o, s& Y
    1
    . b$ O; V4 g- H2
    1 q$ C8 \4 Z6 q/ Y; ]9 W/ a37 N7 z. f/ x& I/ e  k$ B
    4% n) l  j% N" M4 A8 k- h& w( S
    52 o; E- O+ \- e( W! B" G
    6
    9 G" H. U* l. P& C  w7% [# L$ ^6 z0 Z+ h  [5 _
    8
    # B# J; E4 r0 i" E2 ~3 q将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。6 ]- Y( ]2 a/ V4 d# A
    每条记录时间的间隔显然并不一致,请解决如下问题:
    1 S* U' `, p/ `9 r找出间隔时间的前三个最大值所对应的三组时间戳。5 R0 @, V# [4 w5 J6 V0 j- ^1 O
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    ' P1 X" p' |6 }. N9 @$ v4 e求如下指标对应的Series:% L3 u$ f, |  y# R/ w3 W0 f" o; R; }0 a
    温度与辐射量的6小时滑动相关系数5 g$ L6 O/ v2 F
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列0 ?! e8 A4 h+ B: E! ?
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    2 g/ f& y0 Q) o/ y  K7 i7 ^& mimport numpy as np
    % ~2 K) e( s( x" T2 U0 I9 pimport pandas as pd# }! Y, `* i4 ?, J
    1
    " u3 ]5 C# o4 }6 e# k, Q: m2
    2 Y2 b- Y- F$ v* C" M2 {" [将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    # b. @: e7 C' idata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列
    + K1 H6 T- l- F- ntimes=pd.to_timedelta(df.Time)
    ; |! w3 l- N4 Z$ Z  l* Y0 ?3 B7 `( `# zdf.Data=data+times! [  n2 F  P9 ^' p: E+ u% ~
    del df['Time']0 Q: u+ s0 `; r1 u  d
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留% j3 L. c! d) |1 o( |- D& Y9 w
    df- z1 S2 B, y3 w/ m
                                            Radiation        Temperature$ x! }& m+ ?! |/ c: i  X* Y
    Data                5 s$ X% b0 l0 J( R0 h, ]1 y4 o
    2016-09-01 00:00:08                2.58                51& N5 R' V6 t0 b% P6 S
    2016-09-01 00:05:10                2.83                51
    , f9 Y6 N: G( A+ r2016-09-01 00:20:06                2.16                51
    6 [9 m/ g; _/ R7 f1 j2016-09-01 00:25:05                2.21                51
    ' j( [3 o4 R; e) L' v6 B2016-09-01 00:30:09                2.25                51
    7 n* l! ^. b' j( Y; a4 U...        ...        ...5 a- j6 {- f! J9 R
    2016-12-31 23:35:02                1.22                41
    ; S, A0 k8 @  X6 q1 y; ]2016-12-31 23:40:01                1.21                41* I7 t: y+ J$ ~2 p( d5 U! J
    2016-12-31 23:45:04                1.21                423 U( b& F$ a# X1 S
    2016-12-31 23:50:03                1.19                41
    & z7 Y( [8 v4 y6 o) ~2016-12-31 23:55:01                1.21                41
      ~) E' C" W& K
    ! w$ h& x4 M/ U3 {1) |8 P1 s- k* ?: U2 z1 }
    27 V! N4 R: ~7 z/ ~6 E( j
    3
    + o! a4 M2 ?5 l3 V. Y, T4- }4 X6 V6 b1 T( |6 c5 s' h
    5! ~5 D3 I( e3 v  E0 |+ C7 X0 `
    6; P3 L( R: d' V1 u% H  Y
    7* q& G9 L1 O+ F( b2 Q- W
    8
    ( i' w4 c' I+ C6 R& c2 O9: }. M9 p; n+ s/ p; B; n2 B& v
    10
    " A  v, W* U6 p/ W& q0 y9 n7 M11  e$ D! f+ e8 `' X- s* _6 d
    12
    / `: Z9 c9 s* M1 @$ [% w5 S% ^136 O2 Z* M4 i) m% a1 \+ S( b, Z
    14
    . A9 k6 f) Y. `4 H, x9 p15. u! l. C+ Y6 M* V% ^+ P
    16
    + ^( ]! Q8 V4 f17
    3 Y$ H* p& q; d* X18
    . ^/ E) ]  n( ^( o  d* {  B- W19$ i( B2 h8 w1 |: T$ ~8 x, t9 L
    每条记录时间的间隔显然并不一致,请解决如下问题:
    2 V9 i. E$ |/ i2 a3 f找出间隔时间的前三个最大值所对应的三组时间戳。
    ! P: S. X( V3 [" h3 M$ }# 第一次做错了,不是找三组时间戳
    & R! j% a6 ?5 k* b! j( @, ^idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]3 a2 q2 u" m  X- Q
    df.reset_index().Data[idxmax3,idxmax3-1]6 v$ }' k- P5 C& N, U
    - u! ?& l# v5 y. T8 U4 {* V; c
    25923   2016-12-08 11:10:42
    + P! D4 P1 O" x8 L24522   2016-12-01 00:00:02
    4 ^% H! V3 F5 u4 i* w5 n7417    2016-10-01 00:00:19
    ! H  w1 C4 `3 T  qName: Data, dtype: datetime64[ns]
    / H/ [3 v5 d8 {; Y! k( S( M1
    + c$ O1 E6 b& g7 k- T26 ?+ U' G; A1 o4 w, ~! H
    3
    7 S: B' L& L) f* P4* S5 ^. P6 h  ?0 c1 D% y
    5: U; q  d5 I8 l$ f
    6/ K; B$ Z. {4 X9 v; ?
    7
    3 H% w3 Z1 g' Y9 Y8
    0 S9 B, w6 J# X& A1 Nidxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    3 i/ l, ?" i2 r/ [4 dlist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))* S' q% V* q) g1 x& g% U* l

    $ d, D2 N) o) C, {0 z/ D[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),* b. Q* C2 l7 C$ e
    (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
    9 H- e7 x+ R  @. X (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]0 l5 S0 F8 ?3 u$ Q( K
    1
    & f1 ~: e2 p% F& z" I7 r0 u2 @9 P+ M28 B0 o$ J/ p) L: X9 y) d+ b
    33 c% {/ m$ W6 O  B. w4 O9 s
    4: P9 |( X2 h/ e! q# e' A' x
    5' V9 _, p5 k8 k$ h1 y( @
    6) ~# n! U; ^& l- t3 i- i2 ?
    参考答案:0 f; b' e( K) Z( r: O6 {" ~8 g! J; V
    4 B1 z6 I9 }% |# [
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()
    9 x: F/ ~% `/ \2 j/ t1 [  I- {/ y' Vmax_3 = s.nlargest(3).index; \- |" K3 @! x. G% X; O, m
    df.index[max_3.union(max_3-1)]
    & X3 l2 i  g* d) f( M
    , f% g3 n. I  xOut[215]: ' O4 C  R8 F7 n  |( Q6 v
    DatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',; c/ J! _% Y# l0 }
                   '2016-11-29 19:05:02', '2016-12-01 00:00:02',! q' z  X  s  p4 q0 w0 f1 u
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],% t+ S: I/ q1 o! W6 L
                  dtype='datetime64[ns]', name='Datetime', freq=None)5 ]1 `2 R% @: ~: @) w+ Q
    1
    1 z/ B. g+ _! }# c9 X! R24 h8 @3 V7 Q2 |0 m
    3
    ( [. ?6 U- J4 ^4
    - ~) g4 H3 U  g0 E' F$ `5
    1 Z: n+ e( ~/ b# A* _6 K( b, g67 A2 }: X& H; i; R
    7) Y' o7 r9 Y" B0 B
    8
    / Z+ x! M& z1 t2 G& s, u9. i6 l8 m4 H+ Q  t
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。+ l  s  m6 c- s$ I7 s% Q
    # 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
    4 j- ?; V6 T# K  u9 E1 |, ss=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)
    $ k+ s% |0 Q& w: a% S# {5 m3 As.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)* A) W- S3 K+ d1 e% \7 w4 o0 |
    7 x+ @) c& b' W
    (304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0), t( K- B5 ~& Z3 D, i1 X+ @# S  ?
    1! W+ |4 {5 ^4 x" M
    24 |" C  T/ T8 c! k1 _6 |7 G
    3
    ! D6 k2 A8 _# `0 e4" f) S) a6 h6 e  F: z
    5
    & C3 X0 f' u4 X$ `; u! p0 s%pylab inline( ?' f0 ~+ O1 X/ q4 O9 G4 g) L
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    - L1 u5 Z3 c5 j0 Bplt.xlabel(' Timedelta')
    1 y% C, T( ~' _" g$ F% @0 Nplt.title(" Timedelta of solar")$ ?# e* v+ E. ^" W& a
    17 D# y: s5 ^* S& i- G4 d
    2
    6 Z1 s, ?: z# ?- D- K! Y! L3
    3 h& i8 c+ [0 e. J$ @48 ~$ Y6 G6 r2 ^

    ( q) e5 l- M1 w9 N2 j
    9 }# z; @/ f8 f$ [$ b% J4 [求如下指标对应的Series:9 |4 z( f1 A, A' ], n; a
    温度与辐射量的6小时滑动相关系数( ?! u( a) H$ C3 V' Z8 i
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    ' m. ]& E# O7 q8 C* q4 P. w每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    . S1 _) H( u% V" {3 |/ Fdf.Radiation.rolling('6H').corr(df.Temperature).tail()
    2 o& f/ }3 Z+ S
    0 ~# [* y& }" t9 P6 m& LData
    1 A* p. {1 `$ g0 q4 H2 |) G% ^2016-12-31 23:35:02    0.416187; \/ ^! v+ a& F& [; U# k4 \
    2016-12-31 23:40:01    0.416565
    . }: {0 l9 P/ Y# A! P, ?2016-12-31 23:45:04    0.328574
    $ }/ ~1 g! C, B$ `2016-12-31 23:50:03    0.261883
    2 L& y2 {" _5 k' ~$ Z$ S$ |# _7 h2016-12-31 23:55:01    0.262406% X; m0 h% I" p) ], C
    dtype: float643 k4 b2 c$ W. a7 G! p
    1
      c, H8 g- h# u8 D. Y$ v2# g0 h1 y. O  B6 o- t6 I. m
    3
    " W5 X" T. r* ^4
    ( e$ T* j# e! C: a# f5& T7 K9 _5 S: L6 Y$ N% W, E& Z
    6
    5 e. T) [: t1 Z' p1 e. N* R/ Z74 V! C5 Q& [/ e" ^$ l
    8/ b1 ?  f- {$ D$ J! n' U) y; q
    9
    % U$ a! @( p. H+ D# D& r) Xdf['Temperature'].resample('6H',offset='3H').mean().head()
    ! `4 O. \$ c7 w
    4 U5 A4 h# O" F6 HData
    ! p* |; F5 y) Z) x- x# ~% Z8 C2016-08-31 21:00:00    51.218750* O2 z; T1 @2 U
    2016-09-01 03:00:00    50.033333
    ; H0 q3 g$ V9 R2016-09-01 09:00:00    59.379310
    ) n4 x# x; h5 c' f" z/ }: t2016-09-01 15:00:00    57.984375
    . ?3 b+ a$ B, g$ D4 ]0 D2016-09-01 21:00:00    51.393939$ O! v( Y  r( Y% n) p6 V7 `2 x5 Z3 a
    Freq: 6H, Name: Temperature, dtype: float64
    ! c3 I, s* Z2 I+ A( s. T( r2 _19 t, M) [- t5 P- |0 f$ P, |& l
    2
    6 G; M( U7 _1 n0 F9 k: M: S5 I3" A: S' [% k; l
    4
    ( ^/ X8 R8 l' ~' B" V8 [5 e5
    * h& P- l5 t1 E' m, a+ \# A8 G6
    3 \/ ]* U4 A* U7
    ( \: f. t: y! N& Q9 W8
    1 w) r! c+ s' x97 l2 C- `; X; z6 D, {& ?) S
    最后一题参考答案:- Z' @! @+ r0 @9 J
    5 c: T$ P+ E7 H# u5 D8 ?. y
    # 非常慢+ x, c7 t0 W$ a" u  Y  y; {
    my_dt = df.index.shift(freq='-6H')
    " m; }& t1 l0 ]6 q& M' `7 e- oint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
    % y  }' d% S3 p- G9 hint_loc = np.array(int_loc).reshape(-1)
    , ^( n  g! E6 @  f7 {4 O5 V  K7 kres = df.Radiation.iloc[int_loc]
    5 p+ Y8 W" M9 I! @res.index = df.index. `) n: o7 u% ^# d7 g  x
    res.tail(3)) E& W" l) r8 {' h
    1
    8 s) |& o( R1 l; ]2$ L& J/ e/ M* F7 Z4 i
    3
    ' H3 s. |& K, [, ?1 {4
    ) _* K+ ~. b% N8 L$ R5* c; L( U% M; K
    6* s/ u0 c& A8 {
    7
    / X5 H3 c: m, ]. ^# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    ) o) N, z1 o8 O3 o3 R3 @% H9 F4 l' ytarget = pd.DataFrame($ t( V6 h- K0 `4 W6 m" B
        {
    1 j7 X  G) B* |+ V& X        "Time": df.index.shift(freq='-6H'),
    ( y; z7 I7 p. J' w4 f        "Datetime": df.index,
    ! L, G. Y5 Q; C& r. y4 [( o$ u    }
    9 I2 F# e1 L! P)
    ( {7 `: T# J' K4 X
    * b! g! r5 Z2 o9 V% h0 U0 Rres = pd.merge_asof(
    , _+ e6 h; e6 x6 f( F    target,# Q; ]0 `$ A3 ^2 v' `+ @
        df.reset_index().rename(columns={"Datetime": "Time"}),. e( P% R- K$ H6 {% ^
        left_on="Time",
    # S6 `, a7 g/ g    right_on="Time",. U  J6 N4 R6 S; b' y2 j
        direction="nearest"! ]# j' ~. {  m! T2 A( h
    ).set_index("Datetime").Radiation; T( i9 `7 {& K2 X  G

    9 d) J6 V4 v3 d8 nres.tail(3)
    : q, }; Z! N- Q6 b3 O/ V( ?Out[224]:   G2 f6 G3 N7 H0 W
    Datetime1 n5 ]: q6 Q& f, a+ j" L" e
    2016-12-31 23:45:04    9.33
    3 o5 r8 B. c* k" L! i4 K2016-12-31 23:50:03    8.49- {2 U" O. J1 ^; m" C% E0 Q% z# _
    2016-12-31 23:55:01    5.84( b+ Q- [0 A4 @5 c- j
    Name: Radiation, dtype: float644 ]# e$ G  b7 b6 V1 s2 o& d
    3 b0 x$ U% f3 z( B9 s
    1# H, U1 j: H# b7 m  Z, P* T6 h
    2
    ' y7 z6 k7 ], M6 ^8 h3
    * g' [  t( F5 z$ E8 {4
    9 s, Q, Z$ n2 e$ |% V5 a; ^51 N; c# q( P1 v2 `' c7 r5 E) @8 Z; o
    6  B1 c# d/ g9 Q5 P- r
    7
    6 q! Z9 {' l% O2 X8
    5 k) V, D6 z  P( f9$ v- o9 G. }$ `* j3 J/ n
    10
    7 ^& p" S3 ~. w5 E11
    ' C5 E3 c/ y" D& B' j* z) p12/ W! P# P: I" s8 B& S/ F# `1 O
    13
    3 M! \- y( @! {7 a14
    7 X' B4 ?3 G$ }15, _) z% s# ^* Y9 x
    16
    ( b& u% k4 R" r, z& P' p17+ ]8 e; b. T9 k1 j' I: b$ f
    18$ y0 d6 _( B: y: g  y
    19
    ' T. Z9 U) I6 D# V! ~) |2 a20
    6 r0 z# z1 C+ C; E1 l6 L21
    5 f2 _1 Q8 X- O8 ~2 d$ ]' _( u( L# f; S220 [9 {0 ^6 A; M! J
    23
    : I2 A" ^) j- m* T8 l$ fEx2:水果销量数据集
    . m. ], o- _  Q" s; T9 {8 l9 C现有一份2019年每日水果销量记录表:
    ! H, @: W3 j) q% Z! Y7 B: {. @- \$ R4 h8 X
    df = pd.read_csv('../data/fruit.csv')/ H  k7 `. F& r( N. s
    df.head(3)
    4 N: G4 E/ X! x  y: V4 Z: ?/ {  d# b1 [/ H; Z- Q" v
    Out[131]:
    " a7 r; z/ s/ F% R0 l  Z         Date  Fruit  Sale* S, Q$ H) S0 f0 B, [1 m/ x& l. J
    0  2019-04-18  Peach    15
    / N6 o2 V* g" Y3 J1  2019-12-29  Peach    15: J) @  x- j6 B4 z  @1 {
    2  2019-06-05  Peach    198 {7 T- f: l9 M; a/ I
    15 N, w3 N; g" X/ v) V, o
    24 k1 u# `) [9 G# C
    3! e' g  M8 V& T' }# Z- c' ~8 Y
    4' I6 x  X0 C5 J$ @
    59 y* @" w$ F: f1 H+ Q
    6( l7 |1 S4 i. U6 a( M6 h& w
    78 x3 I3 O- x6 P1 X- w
    8, [( l' I  ~% u- r$ z9 C/ ^
    统计如下指标:
    , D: S* V3 Y+ f" q每月上半月(15号及之前)与下半月葡萄销量的比值
    3 r: m7 z. |+ a* @, ~1 h每月最后一天的生梨销量总和4 ?( b! }5 Z: g0 T) _
    每月最后一天工作日的生梨销量总和4 F  C+ W4 o& T5 j6 v) v4 X8 P
    每月最后五天的苹果销量均值! }4 [6 {; W6 l. w
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    7 D  f6 t4 m: F  ^) e5 Q& ?按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    0 k! p& S7 {) c9 [% Yimport numpy as np: N5 l1 T! c8 B9 e: ]$ u) T
    import pandas as pd2 b5 p* u# \9 I* F& G
    1
    $ [$ \: @! m! `2 h+ W2
    & ]/ U- m, X# K2 ]% }2 W' N统计如下指标:
    5 C$ V! H/ v8 j4 \+ W: i每月上半月(15号及之前)与下半月葡萄销量的比值, O; {7 R$ B% h) F- m! m' p, i
    每月最后一天的生梨销量总和+ N- n* T5 |4 C! z) D8 [
    每月最后一天工作日的生梨销量总和% O0 |* y- V: F( J# b
    每月最后五天的苹果销量均值
    ' V! V1 }3 r7 @# @. x0 M# A" v# 每月上半月(15号及之前)与下半月葡萄销量的比值  T0 u* k: q9 n4 _2 r5 }5 c0 N) }
    df.Date=pd.to_datetime(df.Date)  k( C2 @# t; h* W
    sale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()
    + @9 X: r9 s/ Csale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊  t; c/ J0 S7 \  W1 T2 g- S/ w
    sale=pd.DataFrame(sale)
    1 l* i7 `  }/ I) F/ b) zsale=sale.unstack(1).rename_axis(index={'Date':'Month'},$ N, z5 p3 l5 K1 _% R
                     columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名9 T7 V9 A& P- x; Q) U- F6 @
    sale.head() # 每个月上下半月的销量
    + {$ C  O: u8 u$ G/ s5 x- ~  J( s+ W' W2 W4 v8 A
      Month        15Days        Sale
    6 Q$ }) ~: q  F% \" l3 K0        1        False        10503+ }! n% c0 J7 F4 g3 i. `
    1        1        True        12341
    * j/ Z' k1 R- s% ?2        2        False        100010 h# ~/ H& ~8 C( j$ o1 d6 |
    3        2        True        10106
    & E5 A7 r; e$ t4 B4        3        False        12814
    , g4 }9 H2 [5 Z7 ^& g0 b8 i+ l4 U- Y) \, S" v1 H7 h
    # 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
    - Y" j6 t. _0 n3 Psale.groupby(sale['Month'])['Sale'].agg(
    3 `% T* C$ W" p9 D' ]                lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max())
    8 T! `) j  S; o6 F' ~' K
    / g, V& ^. [6 v2 O4 D2 RMonth9 h/ T5 s4 J% w0 S
    1     1.174998
    + y4 ^/ {' }- H9 ~$ ~; J; z+ Q- ]7 l2     1.0104999 }" f+ Z  n) P# i/ T
    3     0.776338
    + F; y3 o9 A, A4 K6 s  k+ w9 a4     1.0263453 ]" Y* Q- Q5 k( J" w4 @/ ]
    5     0.900534
    0 c* r  F. M! t) h3 l/ {6     0.9801365 w# C9 D! y" i
    7     1.350960
    2 k. a- Q- B/ u, v) P; h1 w. d8 `! ?8     1.091584
      t/ ?1 e% a0 N0 N) {% [& x9     1.116508
    0 t$ b' t, _3 m0 b( ~5 ?10    1.0207848 y. G- i3 _8 g5 V. L
    11    1.275911& W: u0 V+ b1 \: F: U# V* S
    12    0.989662
    , @( G7 W9 q: MName: Sale, dtype: float64
    2 P4 J: L8 c, S  R. [; [5 [: ?; p# D* F, J1 E# z6 q! u
    1
    ! q$ |7 x9 E. \* w0 D* s2
    # A% g* g3 U, _% V" f) k/ Z! \: Q3; o& w+ k: a" S! i" Z
    4- y% V9 T5 g: r  d& P, |
    5
    4 D- B: s7 b' }' x6
    7 H1 X3 X* B8 O8 v7; `0 m  Q8 J* b
    8
    7 Z2 M+ p3 e% x- M92 w1 `$ i/ M7 B+ D7 r# K4 B
    107 D% J- D" n% L* p0 x& N2 k! Q- A
    11
    ( c% C" ?8 C+ I- c12
    6 Q1 u# |! e; C6 n: b; C131 W' ]- q4 V& {0 e
    14
    - n! t" u7 Z! x7 z' @5 L158 y+ L, r2 t8 S4 H' E$ I
    16$ J, B4 R- \* \" O
    17
    ; k8 M# H. b( T3 ^0 H( ]18. Z" t; h0 A: ]3 r
    19& P) q( |, f0 V) Z" m! ]
    208 X, {* C& g9 D% y
    219 z; c/ ~% \( d  j5 h9 Z
    22
    , U$ r! h! b4 B* Y! U23/ z; G$ B: q; t1 t* K# D, A3 P( P
    24
    ; n2 N: H6 B( Z! X! M25
    0 p# B$ w6 J& r; x, B. v, }26
    5 N# [& [$ s  v( q; r, a$ f27- U( ^; H/ ?0 m  ]0 M3 w. v8 b" L
    28
    $ H8 @/ ~7 a1 c$ c7 J  u# h8 V29
    + o+ ]( P0 V! X0 z1 _" q/ a! T3 m30' I" }% V  @" W, k3 j  w' w7 r
    31& |7 }  |, C9 Y# h9 M8 y
    32
    5 u) h3 ?  ]: u1 a+ N) K2 K33
    1 L- t( H  R4 B5 ]$ _7 A34
    / q0 N. F  ^4 ~8 E8 P4 e- Z, D* v8 C9 |# 每月最后一天的生梨销量总和4 @8 t# ]" k4 g5 @5 S2 B7 I
    df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()- A1 V& H' D+ [3 }/ c3 |- _& D: s

    8 o8 B  p# j% l* YDate
    9 W% b, x: _. Y- O  D/ N  P6 c2019-01-31    847
    2 L2 y2 U/ c" ^2019-02-28    7741 V# i! X. f3 [/ y3 ]: e
    2019-03-31    761* i. n  x4 r* N) v, Q+ _- y
    2019-04-30    648
    5 m1 f% n; E/ b" N" q0 t2019-05-31    616) i! O+ v( C, Q
    1
    0 o1 H. c9 _6 G2
    + O* k  n3 a3 _" r3; ^  E" v9 g8 L  N: F( }
    4
    * i/ r& @2 E- p/ t+ b4 A* ]7 |8 ?5( x0 V3 N6 S5 \$ _: {; V4 z+ K
    62 U6 t1 G3 v% t3 s) M+ K
    7
    & B; G5 n& u% g3 N, D( A8
    ' U$ d1 {  H* I  M94 G! h2 e( x" e4 Y' V
    # 每月最后一天工作日的生梨销量总和
    9 `9 l4 u' ]9 e% d+ zls=df.Date+pd.offsets.BMonthEnd()! ]0 Z# t- |$ L' R, R
    my_filter=pd.to_datetime(ls.unique())  H2 D8 `. S5 X# F! [1 {! E/ R
    df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum(): f8 `2 ]. d/ Q. y6 Y3 d2 [8 A  _

    3 a8 F0 t: `: `$ j7 E$ {- z, eDate, ]9 F8 ]7 x0 @; \
    2019-01-31     847
    ! [5 Y4 r: O* q% V  t, K2019-02-28     774
    - Y% q2 |. m% @+ O$ K2019-03-29     510- N& Y' C3 K- j
    2019-04-30     648
    8 |8 y* i0 ~( X5 q7 U- x2019-05-31     616! q) I. D) |0 I3 e& r
    1
    / D( }0 H1 M9 ?2
    & k8 P: {+ X- P8 O( A) m3, S( M% ~; @/ R% M# }5 g
    4
    2 a! {3 g5 x7 @0 _  z53 u& B9 s" w( W# A0 a) W8 }$ e
    6
    8 a- E; N! T- e7
    ( h" w5 ~# ]0 N' b8
    " H3 d/ ]( a; ^- ]7 E3 R: x9
    6 w. {3 P: J8 y+ V# q106 S( p/ {% t1 V0 |' y1 Q
    11
    0 R/ }8 `! T4 |3 ]3 r# 每月最后五天的苹果销量均值
    ' R6 r6 E1 `% _- x5 _) mstart, end = '2019-01-01', '2019-12-31'
    1 O1 `  I* ]1 f/ W4 `* m) Jend = pd.date_range(start, end, freq='M')! J8 b$ x3 ?3 I' X- ?
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
    3 ]3 S% D- m: `! ]& v* Y
    $ I9 I. ?4 ?2 `- x+ ?0 `td= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    + R6 Y: Q9 p  i' z6 Ctd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天
    6 m; B6 I' J# G) s+ w% \) j$ P- yend5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    6 T& P9 ?5 W6 ]% O5 @3 K- i: o3 ~9 P
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量: L6 c- \1 D8 K4 W9 N  p
    apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head()
    / ]6 e: P0 M% a- P
    ) m) ?6 D/ d" B* eDate- R# I! e* ^9 m
    1     65.313725
    / G' D; [  Y4 B( s9 ~2     54.061538
    3 [' p7 I! F% H4 r9 C3     59.325581
    6 v. S9 p3 t( e2 i; ?) \4     65.7954550 U' t3 x$ ~. l
    5     57.4651164 I9 g; g4 `1 r3 c6 V

    , `+ I* Y! z% t6 w1. K. Z- A, J* |# p  ?
    28 l! z! S3 P, `6 T' D! G
    3) X! Y( b9 X" _
    4
    . q: K# F( K7 x* Q9 z5 v) Q56 t% s5 h1 _" U
    6
    ' C: ~, P& C# t* Q5 i6 U, j7: y4 q* o" q* q; G" n: b' \, k
    8
    / Y9 \, Z9 E# i/ [- ~4 Z9& g" j8 [% h) \
    10% _1 k4 f' m9 S* |& }, f  ^
    115 {% d% D# y& b
    12
    ' h# u2 m, t3 U13) f; S" p2 f, u. \
    14
    8 m/ d$ G9 t. f' K/ A: y4 l6 g4 R6 p. n15' q$ x  A3 H+ j' i- P
    16
    % ^# o) R; L1 b17
    % r+ t4 U$ m* m) K+ I9 q18
    0 G  K& ]# @) D, z8 {# 参考答案:( @5 s/ c" \0 ?3 s4 Y' k: z
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
    % q/ t0 ~. q4 u/ k6 p0 ~            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)7 ?& z' A9 w- s
    2 k5 y; b+ b  A
    res = df.set_index('Date').loc[target_dt].reset_index(/ ~+ R! K  Q: \# C7 J
                ).query("Fruit == 'Apple'")
    ) p2 `* H% e% E$ l  M) Z" ]
    ! @5 I8 W' @9 |. Z0 i6 C( A/ Zres = res.groupby(res.Date.dt.month)['Sale'].mean(
    6 r& O% P- \' a/ Q  i            ).rename_axis('Month')
    . u" b. X) o( ?+ D  H
    5 C& [8 }9 }# a2 H. U& v
    - ?" O2 K- S  v$ X5 e! hres.head()/ v( l3 ]( F. C  ]
    Out[236]:
    0 _$ @& p) K7 A+ BMonth
    ) |( X$ x& D* D/ {! K2 n' p. E3 o1    65.3137254 H2 c4 u4 I  `/ r/ e7 O
    2    54.061538
    ) o5 {5 j3 a+ }0 v. `3    59.325581
    4 n5 n+ I3 q4 e; g8 ?4    65.795455  t, C# w. }# p0 Z
    5    57.465116+ t$ \* a6 G7 M6 \% v+ u& B
    Name: Sale, dtype: float64
    ' `7 j1 `( d' @# E. `3 Y& Q
    : n& ~! X' {3 T1
    - d9 C, Q1 n: B8 F2 e' [# H2
    , {* @, A8 d$ ~3
    + c; t- p& K6 M) X. e4! j$ v( X" f: k" p0 S
    56 @. E4 r* g# Z9 U/ n. p
    6: T+ B  `! @8 c7 A
    7
    6 E# M8 D% C/ z, B. R8# o" E5 m. D! {7 i4 W2 c
    9( B# r0 t9 E+ t+ D+ W# Z
    101 w+ M0 Z8 X8 \3 Z5 [
    11
    + u/ F# K8 |5 R% e* |* N123 E! q; e: e! K( N& S4 a& p
    13
    - {- I, |5 w% P7 R6 M# Q7 e5 S  W- i14/ K9 u: L2 T3 u0 [2 }9 W! ?; X
    15/ E2 e- n% K8 E2 p5 i$ u
    16# f) `1 v9 N7 C; d% u. C) C2 N8 v
    17. q$ X1 a4 @0 U, f4 \1 [
    18
    ; Y* v5 V$ n6 L7 G& @19
    , y# H8 V, T" J' K2 y# @20
    : Z5 z, o/ }) ]按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    5 Q7 ]7 p+ x9 v: }result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.. \& U3 z) y. M! [  [  O/ w1 d
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计
    : W- W  W% c) i* c: ?! ^- i                                        # r; m9 M2 Q5 }2 Z
    result=result.unstack(1).rename_axis(index={'Date':'Month'},2 V1 u9 j5 C( y; H! K$ L8 \
                     columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字.
    - z' Z$ Z" A' R9 P9 [; j' z2 {, Aresult=result.swaplevel(0,1,axis=0).droplevel(0,axis=1), w' s7 t: ?# C
    result.head() # 索引名有空再改吧6 j" y, |7 Y+ e& ?" R
    2 x5 M' z9 q) X! s1 H1 H
              Week        0        1        2        3        4        5        6
    8 q; H! P( R6 B2 v( l- m$ X5 Y; w+ HFruit Month                                                        : [# \- `) r0 g3 N2 Z
    Apple        1        46        50        50        45        32        42        23
    + b4 T+ k9 W* ?( ^$ x1 {7 mBanana        1        27        29        24        42        36        24        35/ ]4 [# M  U. r0 |! z
    Grape        1        42        75        53        63        36        57        46- J! X, n  {% J2 s6 _( r2 j
    Peach        1        67        78        73        88        59        49        72
    9 F% z6 I: J2 J: CPear        1        39        69        51        54        48        36        40
    ( L0 {* ?& S; X" m* [' _1
    6 p4 w* u# w) g# W4 C8 [* o( G2( n5 p% I/ g7 S1 Y$ q
    3+ ^5 v* H. h; z% y% K
    46 o$ s4 @$ n+ j  q; z' y, l, K2 H$ H
    5; H1 g" w, o" _- f2 Y2 b
    6
    4 K+ _  K" W6 n6 `4 l+ N7, B. Z7 r9 a8 a" q6 c$ J: i5 C
    8
    * ~) `% `! E, E9
    ) ]7 X+ k/ A8 T10, R& X; x- d# E) q9 P
    11
    5 Z2 x6 E. r% t" B; v( {" D4 {12" s4 D! U+ f! h
    13( o) u6 P& Q9 y$ V" e
    14$ {# Z4 A, @+ f. j4 O: ^. l
    15
    1 [6 O# ~. W$ y+ I6 W* X+ w按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    . s  x/ ?  m; P  b- X  e# 工作日苹果销量按日期排序4 Y5 p5 ]1 C4 P/ N
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()
    3 Q; G8 Z; L% y4 Dselect_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总5 Y* }( m( b) W' L
    select_bday.head()0 l4 K  ], Q; b! e4 L7 q
    - G2 N' x9 [) M/ }
    Date
    ( v# s+ |8 V" @- b2019-01-01    1896 i9 h! d( z9 J/ p. [
    2019-01-02    482* J, O* F6 e% ]' q" v
    2019-01-03    890" ~* s0 ~7 j+ t! D6 R3 s
    2019-01-04    5503 V9 t% v- B) X. H$ B' ]/ i( A
    2019-01-07    494
    4 p, D4 X7 W% i+ m% r6 Z1 h8 B' k$ ?# V5 f2 W6 I
    # 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。0 h4 v0 Q$ g/ `& _/ B- t0 p
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()' o7 I) o+ ]6 w% [& A
    - u# S3 P' H! j' _
    Date
    ! c3 o8 ?; z% V7 E2019-01-01    189.000000
    ' z0 Z# [- h# z& \! V2019-01-02    335.500000
    ! {: t2 ?9 l% w& [# l  D) a# O2019-01-03    520.333333  S7 D6 S% l; Y7 T7 l
    2019-01-04    527.750000
    ; @& E( k9 z5 O  N" n; w' N/ N2019-01-05    527.750000& n  n$ `+ I7 b$ s% @

    5 l" _+ X+ ~1 O* C! N" h! C% l" K————————————————) E$ r7 N1 y2 W: G+ o
    版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。! v- V4 f) j4 f! k$ t! r
    原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913
    ) V. N9 B6 L+ D9 B, e
    / w4 P1 K3 R( i5 P: B/ Z5 n: g) U% C# W5 r
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-6-14 11:28 , Processed in 1.457077 second(s), 51 queries .

    回顶部