QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 2765|回复: 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
    ! |9 R, `7 G( `9 j, d0 o+ E( H! c

    0 d2 a, }" g# j  E/ X$ p0 Y3 l5 t  B7 Z, N$ ?
    文章目录6 p* `- w8 N3 c# v# y5 ~* A4 M
    第八章 文本数据
    / f$ Y; x9 f+ i3 \1 |% n8.1 str对象: F( V) Q' `9 U) Z
    8.1.1 str对象的设计意图! ~  B" {0 ?0 e% O4 j1 c
    8.1.3 string类型
    " A" z' M2 k' H# y8.2 正则表达式基础
    / E* d2 Z" A8 x% b0 R8.2.1 . 一般字符的匹配. ~1 n- S* W+ U6 j8 \
    8.2.2 元字符基础
    7 }" l4 q1 U& J8.2.3 简写字符集
    1 b6 F  U* T- S; I- z8.3 文本处理的五类操作
    8 |- q7 T$ K+ R: D5 [1 S0 Q7 w* d8.3.1 `str.split `拆分
    ' S  f7 p' P; r- b; Q* A% e$ p8.3.2 `str.join` 或 `str.cat `合并7 a+ ?% I" `' z5 j6 N! U5 {' p
    8.3.3 匹配5 `1 j2 ~1 e% b
    8.3.5 提取, }# s1 Y* ]2 ?. a6 }4 x
    8.4、常用字符串函数
    ; o! k# ?3 f4 F; |* B% `8.4.1 字母型函数+ l0 a+ ~' F& U5 u3 `: ^
    8.4.2 数值型函数
    ( M; t  r) O, b6 N8.4.3 统计型函数
    ( f; x7 H* T4 {8.4.4 格式型函数' i, U- C0 E1 a/ [+ [( }
    8.5 练习- U- _( m# X5 m+ ?$ W
    Ex1:房屋信息数据集' G$ }. }6 [7 [+ O# ]/ T' r8 Z$ X
    Ex2:《权力的游戏》剧本数据集# X5 ^0 q. q2 ]/ D
    第九章 分类数据+ f3 T0 |8 W2 v& C' N
    9.1 cat对象' u7 }5 u* r6 w7 t
    9.1.1 cat对象的属性
    % l0 B7 _8 x& E  f5 U: L2 D9.1.2 类别的增加、删除和修改) V" d2 {! \  B! [7 G8 ?
    9.2 有序分类
    % `( y# |6 f5 p; Y+ P9.2.1 序的建立1 n& [$ |; ]% D$ h
    9.2.2 排序和比较
    9 w9 O9 j, l* T9.3 区间类别# w  u) [! A9 ?4 e' B- i- Z9 W1 U
    9.3.1 利用cut和qcut进行区间构造; s! `% F  G; h! l0 T8 u
    9.3.2 一般区间的构造. k+ v4 }9 t; o
    9.3.3 区间的属性与方法
    : t4 `- M1 k4 r9.4 练习
    ' A0 u/ I6 P/ ^' e" x% p# dEx1: 统计未出现的类别$ t0 W# |( l" r% `% ]: H$ s
    Ex2: 钻石数据集
    : b: ~2 `' J# w, n1 S! I0 p( t第十章 时序数据
    6 K3 n5 Y! i2 b9 L: K3 ^' ^10.1 时序中的基本对象
    ' @' U- |- ^, \- d10.2 时间戳9 e  Z7 v; C1 u
    10.2.1 Timestamp的构造与属性
    9 z. p- ]; Z; d7 y( u3 l' D' T/ [10.2.2 Datetime序列的生成# T$ Y7 X0 Y8 K+ k' }; r8 w
    10.2.3 dt对象0 K& W( r* ]  |8 a4 ?
    10.2.4 时间戳的切片与索引7 |( X, o' \! H0 O8 A3 X2 G
    10.3 时间差8 {' F" s+ o! I0 _1 u
    10.3.1 Timedelta的生成# @# b2 v! r0 m( i6 ~3 a
    10.2.2 Timedelta的运算
    + `" @8 j; i5 o10.4 日期偏置6 Q9 C' i+ W* f( w3 [% J
    10.4.1 Offset对象
    , u& `8 z: w( ^2 k" T+ U8 w( w* j10.4.2 偏置字符串
    6 \1 B1 x# P9 k) k  x0 k0 I" _1 B10.5、时序中的滑窗与分组/ X; b) G/ u/ M. P8 H
    10.5.1 滑动窗口8 v% \3 k& v5 b8 K
    10.5.2 重采样% z8 Q( F  C3 B% e+ M$ w
    10.6 练习
    3 M* B3 R6 @" q' v8 u8 {" cEx1:太阳辐射数据集& `, g+ w6 u6 ?
    Ex2:水果销量数据集
    ) c: s* k6 e9 m  课程资料《pandas数据处理与分析》、github地址、讲解视频、习题参考答案 、pandas官网
    2 z5 X" s% g4 ~8 @传送门:
    9 l- z9 o6 j- @% |$ }) Y, p5 `" \$ r0 @& ^
    datawhale8月组队学习《pandas数据处理与分析》(上)(基础、索引、分组): Q3 l: x+ _7 @, ?9 h
    datawhale8月组队学习《pandas数据处理与分析》(中)(变形、连接、缺失数据)5 ^. {; e& t, n/ {1 K0 I4 I
    第八章 文本数据
    : ~" ]  ~2 S% `0 Q7 P: w( [8.1 str对象8 s5 L: b6 K% z1 k! f8 U
    8.1.1 str对象的设计意图
    4 X2 ?: T2 U9 ]- ~- J7 S  str 对象是定义在 Index 或 Series上的属性,专门用于处理每个元素的文本内容,其内部定义了大量方法,因此对一个序列进行文本处理,首先需要获取其 str 对象。在Python标准库中也有 str 模块,为了使用上的便利,在 pandas 的50个 str 对象方法中,有31个是和标准库中的 str 模块方法同名且功能一致,例如字母转为大写的操作:
    9 ?9 k! L) @! w7 w, p( y; [# b$ C; i) o- `6 _
    var = 'abcd'% p( q" V2 K, q7 \. s* Q
    str.upper(var) # Python内置str模块
    5 V6 k& L& N6 O: x& FOut[4]: 'ABCD'6 ^( z) T7 }$ h

    & ?0 H% U$ }, ^" _! ps = pd.Series(['abcd', 'efg', 'hi']); n4 ]0 o" M3 o9 A
    ) R6 q; b$ F/ v/ X  @9 R
    s.str' N) [0 s; G* n5 }
    Out[6]: <pandas.core.strings.accessor.StringMethods at 0x2b796892d60>
    : O# [9 O4 Z: x9 A2 Q
    ) y8 X# D( p$ Y! G- ?s.str.upper() # pandas中str对象上的upper方法
    3 A; I! Q2 ]8 HOut[7]:
    3 \) v" B& _) {7 P0    ABCD
    $ M$ J; L/ ^/ b5 X1     EFG/ e: \: t7 `+ w" C( R
    2      HI
    + z9 [) @- d. Y- t$ fdtype: object0 Y, _& C6 @$ n/ J
    1* p$ T* i" n# ^* H8 {
    2! W3 ?$ C1 l& l; o5 d* c) m
    3
    - {7 |  p2 e; B0 N4! P; x, _" n% Y( Q  L# S
    5
    # i( ^5 @- {& `6
    # g7 f* l. u5 z7
    % R3 l4 [' J# Q1 \$ Y. {* {8; d8 b  ^( \0 T3 B. ^
    9
    ) j0 Q. b7 v6 V10
    ( k: I4 l9 j/ d, U11; {4 C! f  Y7 O/ m" a) T$ q
    12
    $ i1 O0 X- ?+ e13
    + ]0 J1 f8 ]& P4 V7 F5 |14
    ( x1 W, y1 w  ~& @5 }3 N( z# w15& g8 e8 ^% K: Z/ I
    8.1.2 []索引器
    * H) G) z) c" a" o7 B  C( [/ I8 a  对于 str 对象而言,可理解为其对字符串进行了序列化的操作,例如在一般的字符串中,通过 [] 可以取出某个位置的元素,同时也能通过切片得到子串。& E" g; Q+ ?* m. X: n) d; R6 K
      pandas中过对 str 对象使用 [] 索引器,可以完成完全一致的功能,并且如果超出范围则返回缺失值:
    5 U. |8 b* Q' ]; o/ P2 X
    5 k/ a' J1 `2 }4 N) d, Js.str[0]
    ! R% R* }& x2 T. WOut[10]: / a, i$ u( @8 Y6 w4 ^
    0    a
    6 ?  R' r' _8 e1    e6 T0 ?  f2 H8 K/ ?- M9 _% y* h0 Z
    2    h
    ' b! Q+ T5 \8 X# W1 k1 v; z2 Qdtype: object9 M* G6 g( @9 G' n

    5 G0 j9 ?* r" {# ds.str[-1: 0: -2]
    # N. s- O) O( F7 Z1 _Out[11]:
    & C! M" \5 x( B7 C$ \$ o0    db
    9 P$ H! C. l: o7 r# }: Y" ?3 y' G1     g! ]4 @# m* A" C2 z8 g1 J
    2     i
    0 A$ K3 x* n0 m( D" ]5 l+ adtype: object- }" e# z$ ?- J& \; r4 i
    ; w9 y, [9 [) i: C( x" O1 O
    s.str[2]( j0 ~9 D& b$ I) |: \
    Out[12]: & b5 F4 h4 K8 B- H% V
    0      c! ~: `" y( J) G! I
    1      g
    ( v7 C" G) @+ ]4 W" |5 j" S3 ]7 n2    NaN
    . R; X6 y$ A+ ^. f7 T& F) odtype: object6 ?& R" I( _) K$ s+ V

    , g* Q% _8 U1 D/ M1 F7 U, B1
    0 W' F+ M) v' a- G# S9 h& H) l2( w5 L1 `+ k5 C; {( c
    35 {) p  F; b1 N, |% Z' u0 {
    4
    ' V  V) E3 o; {$ e6 N% H56 w# A% I8 C# c4 Z4 a3 k
    6. N$ c! Q1 o6 M5 I& t& d) x2 V
    7
    $ ?$ z+ [& L: z, |* r83 G8 V" l5 x+ S3 F
    9
    2 E# p  d4 {8 r0 s' j( C10
    0 a& R1 {* w4 H11
    1 r2 n# ?% D2 Y+ x8 f! J126 q+ G8 Z. @& H1 E
    13
    . ~% V5 K  b8 y8 t' X147 i% D* U# i$ v! O0 A
    154 m2 }+ b+ Z$ R: ]. i9 @
    16( n' F! p. X: _" {6 |9 }
    17: q- ^/ H; a6 s/ H: k) _
    180 F2 Z# y6 C* C2 T/ Z9 k+ m
    19
    ( v! i3 F3 _! ]' b. d20
    # F, S5 T+ [* w4 {1 n) rimport numpy as np
    , ^* F7 x/ g% d& Yimport pandas as pd
    9 N/ t% t0 P; f& b* ?
    1 G& L! Y& Q# l! k' ^$ y' ls = pd.Series(['abcd', 'efg', 'hi'])
    6 `3 S1 Q/ {4 K; O% J+ S0 P9 as.str[0]  ~; ~- Y$ C% f: v3 N
    13 ^1 b7 X5 z7 I$ r3 k6 `% h
    2
    ) Y, U, \! X2 T3
    * E1 ^' a9 Y! E' q+ u4
    7 l6 s$ x( S2 O, {8 b7 Y4 K9 L5% a% H6 X% e" m- D7 v
    0    a
    & p+ L' O* R; _) b1 _  F1    e
    " c- ?+ Y3 g4 |8 N5 e# G3 x2    h
    . r5 M8 {& Y$ i  z  ^dtype: object
    . S8 Y1 o5 V0 |( [" Z9 a+ S1 f9 w10 o% {- _( w' j4 b6 X7 j
    2
    5 c) ?1 B7 ~6 f4 w6 z35 b1 _6 t+ N3 s" Y# `! ?& [
    4; E0 B. g$ ^: s1 t  v# U- M% e* S
    8.1.3 string类型
    7 }! Z; ]. c7 T( u/ h' n  在上一章提到,从 pandas 的 1.0.0 版本开始,引入了 string 类型,其引入的动机在于:原来所有的字符串类型都会以 object 类型的 Series 进行存储,但 object 类型只应当存储混合类型,例如同时存储浮点、字符串、字典、列表、自定义类型等,因此字符串有必要同数值型或 category 一样,具有自己的数据存储类型,从而引入了 string 类型。4 O" E/ y5 [7 S6 g" N
      总体上说,绝大多数对于 object 和 string 类型的序列使用 str 对象方法产生的结果是一致,但是在下面提到的两点上有较大差异:) m" L3 C1 C  [' @: J

    ; l% ?; w8 M! K) a+ ]7 h二者对于某些对象的 str 序列化方法不同。
    / N! Q- I: o; o' q, L+ N可迭代(Iterable)对象包括但不限于字符串、字典、列表。对于一个可迭代对象, string 类型和 object 类型对它们的序列化方式不同,序列化后str对象返回结果也可能不同。例如:
    # F" S- D( f8 |8 ^$ vs = pd.Series([{1: 'temp_1', 2: 'temp_2'}, ['a', 'b'], 0.5, 'my_string'])
    # V( J( b1 v& {! |# N' s$ L: zs/ }. F9 g% s4 e9 Z, C7 _1 Z: q/ K
    1
    ; ]5 B& D& g5 J2' N2 e* o, a/ u7 M8 O0 ]  e
    0    {1: 'temp_1', 2: 'temp_2'}" b! X; ~/ L* U4 [% v+ t% {
    1                        [a, b]. P( n7 d$ j1 s( _6 f( ?
    2                           0.5$ C% ~5 t+ h7 t* ~1 ^
    3                     my_string
    4 |8 J0 J$ d9 u0 I" Y8 ?% R; v  Cdtype: object; A" J, t; U  j2 g, M
    1
    & u( Y: v) o1 |7 m. g2
    . j( J0 i4 p; r; w) d9 `3 R38 S4 q: [; W/ e0 K0 j; Q
    4- ?/ W3 R. w9 R$ B2 N  k& r
    5
    + r' r+ n) s8 Q; as.str[1] # 对每个元素取[1]的操作/ \: [0 u; V4 ^5 g8 s7 O
    1- W1 N- m) r2 S5 N! K8 U) o
    0    temp_14 n: S" Z" I5 E7 v
    1         b
    8 r) F: _0 c+ x1 D2       NaN: O7 C" J( f$ d: x! ?5 D* z
    3         y
    + l! s& G& E. U  H2 @, p6 h  Bdtype: object: c6 }4 ~& B( r6 y! h
    18 z( g% m- R* D* a. T
    2: f2 A3 O! `6 d: l  i
    36 @+ G6 ^* N8 B
    41 O- X4 z, a/ t" D- {: R$ y) A
    5. k5 R& g) n, J; L) R6 O  W& M/ }% a* L
    s.astype('string').str[1]
    2 Q7 Z) m! s  T1 e2 K1
    ' r8 P5 n! L6 G3 b8 C9 D0    1
    0 @" t* _. f& x, J1 @& _6 j1    ', y2 H+ d+ i, Q. I$ P
    2    .
      _6 x- u+ {1 q: g3    y
    6 u  |6 |; k5 x3 e2 L" |dtype: string
    # I6 ^4 {' w, o1
    % I- u! g+ K/ Y- k; d23 j$ N( ]! A! r6 c. U, D
    3
    ' P. N; S/ }2 s4
    5 E# Q5 f4 Z- {2 b- T" @5
    ) C# i. [, ]8 b6 A" \除了最后一个字符串元素,前三个元素返回的值都不同,其原因在于:- n6 B9 }% L5 s+ `- C2 L
    4 E2 Q- T, r6 H& V% \, k
    当序列类型为 object 时,是对于每一个元素进行 [] 索引,因此对于字典而言,返回temp_1字符串,对于列表则返回第二个值,而第三个为不可迭代对象,返回缺失值,第四个是对字符串进行 [] 索引。
    9 y0 Z9 h9 f+ I$ Cstring 类型的 str 对象先把整个元素转为字面意义的字符串,例如对于列表而言,第一个元素即 “{”,而对于最后一个字符串元素而言,恰好转化前后的表示方法一致,因此结果和 object 类型一致。
    9 Z3 Z. ?& A+ R8 qstring 类型是 Nullable 类型,但 object 不是
      x/ _/ E! c4 [2 v( d0 x  Q% z  这意味着 string 类型的序列,如果调用的 str 方法返回值为整数 Series 和布尔 Series 时,其分别对应的 dtype 是 Int 和 boolean 的 Nullable 类型,而 object 类型则会分别返回 int/float 和 bool/object ,不过这取决于缺失值的存在与否。
    % u! `; g- F' N4 E' m$ r  同时,字符串的比较操作,也具有相似的特性, string 返回 Nullable 类型,但 object 不会。3 k' Z0 O6 ^0 u: D
    s = pd.Series(['a'])
    6 h  ^6 g( a# r; _
    + \$ C- ^% _4 `s.str.len()
    : Q/ i' }+ p  jOut[17]:
    : m0 Y! O2 Q0 v1 _% \0    1
    : L8 R  @1 \% H  D. x/ ^/ Hdtype: int64( q1 q" x8 e' a  j6 `' O# }

    & {7 z/ ]' d4 As.astype('string').str.len()
    5 h+ }9 V% o' {6 F% r. qOut[18]: 6 p6 T7 B2 R% S5 A* I5 g3 j* a
    0    11 E0 I* ]7 j$ z: s
    dtype: Int64
    ; Z  f! o+ H% h: H! v, Q9 _! Z! J* Z% c% d7 b& {# `
    s == 'a'4 P' U; E! d/ P( p, ^, \
    Out[19]: 8 j. F8 x& u; h8 k  b" b7 c; }0 y
    0    True
    " c! i" k% W7 K) ?; Adtype: bool3 w- L0 \1 Q7 O8 M; t0 t

    : j) A- Q3 m  ]6 D% c1 F3 P% fs.astype('string') == 'a'
    3 I# O; Q$ g3 x- o1 A$ U/ q0 P; c( yOut[20]: 6 }: ]3 D8 d8 Q, i
    0    True
    + x" s& }4 ], u4 J* ?dtype: boolean. I. d$ ^( F4 I6 b
    , h3 E' }  A7 x* u) Z" {
    s = pd.Series(['a', np.nan]) # 带有缺失值
    4 \" P$ O- O2 k; V! ~, a: g
    3 x# s3 f, O( ks.str.len()
    9 z% y; J, ^% T1 z- B4 ?9 i/ kOut[22]:
      u: G3 n! G3 E( x2 X4 w. k0    1.0
    ' R. A( i, X. J4 p1    NaN$ }/ d9 D! p: T+ A) N( _* ]
    dtype: float64% B3 U  [( q2 D

    & e/ ~% C/ ?0 ?% @8 ]8 ~2 `s.astype('string').str.len()! p2 a$ ?. c5 m; y
    Out[23]:
    " Q  k& }3 d) _6 _" X0       16 |- \! j: A: C9 ^6 B0 V. G2 r
    1    <NA>
    ! X" W0 m" W8 A7 O; `- z6 U/ `dtype: Int64; {  V6 Y3 R8 d: i4 w  e) c
    4 o$ L  _5 w+ ?$ V: Y) b, Z9 c& \
    s == 'a'
    ' l' Z: Z( O8 {Out[24]:
    5 R" L% v7 x# s- C! l- i4 \$ |" m7 L0     True
    & k; c$ Q4 L* Z) C9 r) r: w% J1    False3 S5 G; c0 G; Z, O
    dtype: bool7 V" ]" S% B/ c1 x+ B8 F5 X
    & \* p% n6 h$ I& @( x7 |5 _: I
    s.astype('string') == 'a') y; b# Y  p5 d- k. L
    Out[25]:
    1 @( E) s, t' S' `0    True
    # Q! d) Q7 h* z" V9 B2 c  w1    <NA>
    ; Q) @2 x( ^7 C  B) X: Cdtype: boolean4 U* _6 x& J" R4 l# I
    " F; s6 P+ ?7 a1 z8 w
    1" p# Q* u. w$ }& y
    2# ~- r2 w* O4 R/ P
    3" A. C' e# D, y
    4
      d" f, \! m! l3 R$ E. a4 e5
    7 |/ L9 g6 i! D- O- @6
    8 v% @/ ?$ G1 i' r) G; `3 z+ {% i7
    2 z# N4 o/ P8 g2 J9 z0 P8& U" T; ^: D' k5 ^9 `3 s( T  ?
    97 K5 G/ S( F$ a* S! G6 Z
    10" k6 U* Y% L+ m# M5 f4 w1 T
    11
    5 n1 H8 L. l" u4 p. d) u12
    6 @# A9 l0 c& J  u$ u  V, H137 J) ~% J0 A- X* W) W
    14
    3 e# A+ ~  s) I, V2 T15
    ( G7 R$ u) \6 a" k  }: ^- |4 k  a16  U( o' E. I4 x: @
    175 Q3 x2 P! E" z0 ~8 q. X0 V/ J
    18; I$ n, t0 U6 L& Y: y4 i5 m
    190 s0 q3 w( i, d, I
    20
    4 |6 r# L) b6 u* k% b21. ^7 s% }) d. i6 Z
    22
    . V  R6 V# h  a23
    - v6 S$ X! a' l% q24
    " O9 U$ ~' D7 H  Y6 @& D253 D0 P( U" }' d5 B2 B0 z
    26
    & Z0 s: Z7 X. v0 v+ o! ~27' R8 o6 `' |* `5 y& @2 w" ]  d
    28
    ( ^# |% F" Y* Q3 W0 w4 q29
    / n  s. \/ u" h* M4 H) F' x8 B0 _30
    ( d) m/ k4 O7 d& M6 D1 _31
    & S- P8 d( L, }32
    ) B6 j0 e, \& ]) p1 o) I33/ M: ^) b0 h% W! ~; w
    34* |. s0 a# m' e. s# ]8 H
    35$ ^( b. x; k: {5 e- ]
    36. t4 L& M0 T8 O- }+ p8 G/ l
    37- f( n5 S) }# C* z' y5 t! u
    380 ]; }: |& i4 b% ^. Z: q! n- D
    39
    # V- C! \# H" x4 i) [8 [8 u400 j; @: Y7 j5 n- Y
    416 i0 O& J9 j' @: k
    424 n. k* G1 T, k5 e8 n; k
    43
    4 e% K6 L2 J* g; `444 S$ K- m  N# n
    45
    * g% c; N7 n/ d: ^46, W) t4 R& i- d) @- G. j9 k
    47# z7 ]. y8 y" D: X6 Z
      对于全体元素为数值类型的序列,即使其类型为 object 或者 category 也不允许直接使用 str 属性。如果需要把数字当成 string 类型处理,可以使用 astype 强制转换为 string 类型的 Series :7 s) d6 V, ]6 C3 S4 T- W  t
    . V# l6 K6 l$ o0 l7 j$ J
    s = pd.Series([12, 345, 6789])+ H1 H' H, v+ Z0 C" j

    7 l6 O- s/ i7 c$ C% as.astype('string').str[1]
      P4 Z8 V! X9 O) J& v+ |( NOut[27]: ; ?: D6 h% [3 y0 B1 N4 b
    0    2* L" u7 H3 P# A1 j& z8 }
    1    4  v. c7 h8 j8 o# i* v( n% ?3 Y5 n
    2    7
    ( P2 `- o9 k. p) I' }) l  p4 gdtype: string/ \8 G- ]6 r( z% G
    1
    1 D# G: n$ u" ~+ @2 ~5 {) ~7 X2
      O8 Z$ V0 s. f7 t+ j  B: `$ t( C' j3
    - L0 c3 a" \8 I9 v% y' h  _4
    & `2 m* _  F/ s5, r2 R1 v. e+ Z* Y5 j& T
    6
    $ {1 A  M! ?2 h- p% m7
    : i- ?+ Y5 G/ Y8 z! ~* y8
    + ?, R0 G5 v4 k6 _, N$ ]9 B. F: ^5 P8.2 正则表达式基础% F0 J1 Z* V! ^) C( u9 ~
    这一节的两个表格来自于 learn-regex-zh 这个关于正则表达式项目,其使用 MIT 开源许可协议。这里只是介绍正则表达式的基本用法,需要系统学习的读者可参考《Python3 正则表达式》,或者《 正则表达式必知必会 》这本书
    ' f! t; ?! f% f% L& O6 k6 d- s  L8 \1 }% x
    8.2.1 . 一般字符的匹配
    2 h. v# L) }$ t& [+ ~1 }正则表达式是一种按照某种正则模式,从左到右匹配字符串中内容的一种工具。对于一般的字符而言,它可以找到其所在的位置,这里为了演示便利,使用了 python 中 re 模块的 findall 函数来匹配所有出现过但不重叠的模式,第一个参数是正则表达式,第二个参数是待匹配的字符串。例如,在下面的字符串中找出 apple :
    ( D8 \4 A) O* H' W/ @( W$ b! K3 J5 b) y" n
    import re
    $ F; F  S2 f# U; h" i9 |% F0 D9 d
    , Z, M4 y+ l# j9 o- B& g5 jre.findall(r'Apple', 'Apple! This Is an Apple!') # 字符串从左到右依次匹配, ~& Y- A) N; C: b0 F% u, m
    Out[29]: ['Apple', 'Apple']
    ( O3 Y2 P2 w' p) S  r5 u6 b0 ^1  Q) e% w* H% c- {+ y1 Z
    2. f( K7 e& K+ c4 T  q1 E. V6 C
    3
    + q! X  q8 A) f  K48 v+ {8 G! W$ [6 b- A: C
    8.2.2 元字符基础
    5 v/ ?4 i, |2 f) W7 F( d元字符        描述
    * d0 r$ h! o) s5 J, P# p" A.        匹配除换行符以外的任意字符2 z, F5 x) r* z" K8 ^0 g
    [ ]        字符类,匹配方括号中包含的任意字符& W3 q% i% ?. y, u5 a
    [^ ]        否定字符类,匹配方括号中不包含的任意字符
    " A- v1 U' }) ^% C*        匹配前面的子表达式零次或多次
    8 Z' e- G7 n4 l6 b' h1 Y/ \+        匹配前面的子表达式一次或多次。比如r’d+'就是匹配数字串,r’d’就是匹配单个数字% S8 |+ a# E0 D* f8 D( m
    ?        匹配前面的子表达式零次或一次,非贪婪方式8 z, h: a$ _8 t( m
    {n,m}        花括号,匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式9 g% T$ t# L" U& I  h
    (xyz)        字符组,按照确切的顺序匹配字符xyz
    3 T4 P$ k& \7 c* ||        分支结构,匹配符号之前的字符或后面的字符$ V/ ^! \4 V$ Y
    \        转义符,它可以还原元字符原来的含义
    5 O8 o9 A& g2 G" M  Q6 T; D7 v^        匹配行的开始; @2 r" t! `9 c: _1 p% n& q" p. c
    $        匹配行的结束
    * ]) U7 y: N/ [: ~% x% R" simport re
    3 R7 F5 Y6 r4 K6 Sre.findall(r'.', 'abc')4 D! @* j) T1 S0 G2 s$ d
    Out[30]: ['a', 'b', 'c']! L! {- y, `3 ]7 n# [6 |

    # @( @" s( ?0 N* U+ m6 ]re.findall(r'[ac]', 'abc') # []中有的子串都匹配
    $ b; ?9 {" k* uOut[31]: ['a', 'c']( X6 V( E! Z% L1 u3 e: v
    7 p" J% p* W7 M
    re.findall(r'[^ac]', 'abc')
    1 B! v+ ?! \; Z* COut[32]: ['b']
    $ m/ x2 |! u) q% H% a3 M5 w! z( L0 b" V
    re.findall(r'[ab]{2}', 'aaaabbbb') # {n}指匹配n次
    7 D( s# o2 G) C; LOut[33]: ['aa', 'aa', 'bb', 'bb']. q8 s( o- E( v, m8 E
      o( p1 B  e! t
    re.findall(r'aaa|bbc|ca', 'aacabbcbbc') # 匹配前面的或者后面的字符串
    2 ^; @0 R# h, ^7 X0 ^3 F& bOut[34]: ['ca', 'bbc', 'bbc']( R) n" J; e0 X2 r- {
    : P5 s, {$ f2 G3 ?! D- {
    # 上面的元字符都有特殊含义,要匹配其本来的意思就得用\进行转义。
    . u% }) W  |/ `; T"""
    . _) g* B, Y% G/ c( q1. ?匹配的是前一个字符,即被转义的\,所以|前面的内容就是匹配a\或者a,但是结果里面没有a\,相当于只能匹配a。' O" G# A1 c7 F/ Z% M0 s
    2. |右边是a\*,转义之后匹配a*,对于竖线而言左边优先级高于右边+ o4 J/ E" M& y' F
    3. 然后看目标字符串aa?a*a,第一个a匹配左边,第二个a匹配左边,第三个a虽然后面有*,) f, b, J5 n6 \) R1 _
    但是左边优先级高, 还是匹配左边,剩下一个a还是左边,所以结果是四个a: t2 V# `$ x" X! y  k
    """3 \7 ]1 Q9 t1 n+ a9 Q
    5 L1 N6 @) X; F6 n7 H4 h% N4 t2 D6 T& T
    re.findall(r'a\\?|a\*', 'aa?a*a')   # 第二次先匹配到a,就不会匹配a?。a*同理。' Z, c8 M/ q* q! V9 {$ z& s4 B0 |
    Out[35]: ['a', 'a', 'a', 'a']
    7 f+ q( _1 I$ M
    . x& v7 x0 I& F# 这里匹配不到是因为目标串'aa\a*a'中,\a是python的转义字符(\a\b\t\n等),所以匹配不到。
    3 K9 ?0 `# Z) `1 B: r$ A( P. ~# 如果是'aa\s*a'之内非python的转义字符,或者'aa\\s*a',或者r'aa\\s*a'就可以匹配到\字符。# ]( Q0 D5 f3 {7 W; _2 w
    re.findall(r'\\', 'aa\a*a')
    : A9 M1 J! n4 b- ^" e; Y3 Y[], M! i4 n4 N( q+ O5 M# B

    6 G2 j6 Q  O+ ere.findall(r'a?.', 'abaacadaae')! |& F) h# ?) I2 s( k4 p
    Out[36]: ['ab', 'aa', 'c', 'ad', 'aa', 'e']4 @2 c4 b5 ^9 t" Z

    % z  }8 t. [" e' B! q9 Rre.findall(r'(\w+)=(\d+)', 'set width=20 and height=10') # 多个匹配模式,返回元组列表, j, |& M* H7 H" {4 V2 t3 ^
    [('width', '20'), ('height', '10')]) C9 Q- I9 @+ S$ t0 p: z  j
    0 h! |5 S& `( @. C" o& i) x
    13 A8 g1 L) D1 n* ]. [+ j" l  T
    21 d2 E: h) a! t1 W  t4 w
    34 C  S" U# y6 B$ \$ _! H% r
    4$ r+ C6 M4 G" ^5 P0 ]" H- I  a/ |
    5( Y% W# o1 m( Q8 \# v
    62 [1 l$ q# W; I! T
    7+ j0 T( l% C/ d: y1 A
    8. p0 [8 ^8 ~8 a
    9
    ) e( ~; V& O$ |4 m109 Q1 b4 ^$ k8 W! C
    11
    ; J* K/ O. J# Q12; t, ^; I+ `% [- m: ?1 O. X) d7 r
    13& R* [2 Y9 n6 M
    14
    6 c( X" T- T" k, o0 @9 k  T15
    . T4 f3 N, o6 [" H! Q3 \- ]16
    4 J# u# k; E6 o0 ^4 _1 d17  t& o9 s' d' ~5 x! q% A
    18
    4 E2 j3 ~: A! i- H7 v  O  E& ]' O199 C9 M$ A( e% V' D$ a$ D# B( J$ R
    20; z+ `2 q* E4 U3 O% g/ V
    213 z5 I( Z& d7 e7 c4 u, p
    22/ U1 q- A5 s5 D' V! f/ r* S5 [
    230 d* j  e: x3 o9 j( H+ C
    24
    ) K4 V( B+ I  A/ H25" N2 C; A& q0 ]. _* ~
    269 T. ]/ B  M/ A3 i) B2 m2 o% E3 z
    279 W, ?& s; f# \
    28
    0 F6 f. S# C$ e9 O0 r, w& g, C29( k7 a6 W' P, `0 _. U) v3 T* k
    30* }1 W/ Q6 s# L6 V: P
    31
    # b( @0 C8 M' j; {! h1 B32
    6 v8 E: Q3 c/ j; \6 b338 |  [' L& y7 o. y  r
    34
    . Y6 a2 K& ?' z4 z9 y35) ]/ P% ?/ l  p" I  Z" Z9 Z3 J; _
    36; ]$ ]. h: v6 @7 T, R" K
    37  J' J7 n  F& V
    8.2.3 简写字符集, `  H- O/ f( E1 u2 Q
    则表达式中还有一类简写字符集,其等价于一组字符的集合:3 X; p0 [- G9 g3 o
      S: N* B/ {: ~2 ~! I2 M, C$ D$ f" ]3 ^
    简写        描述8 I5 b: M; ?" @8 O$ q
    \w        匹配所有字母、数字、下划线: [a-zA-Z0-9_]. ?1 }3 c$ c5 j! j
    \W        匹配非字母和数字的字符: [^\w]
    8 `# e7 o4 C; S4 M; ^: R# O\d        匹配数字: [0-9]
    7 {, B& _9 a5 a- O/ [6 n1 N  h3 [+ \- Z\D        匹配非数字: [^\d]
    4 @9 C3 I0 ?, Z% S* p4 p7 _1 s\s        匹配空格符: [\t\n\f\r\p{Z}]
    ( B/ \) G' f0 C' m. T+ n\S        匹配非空格符: [^\s]
    2 p, o" t7 }( w. p  E" O\B        匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
    8 |# f4 r/ }( \9 V9 V( ~re.findall(r'.s', 'Apple! This Is an Apple!')# l4 c# f. d9 X" {& D- C3 f
    Out[37]: ['is', 'Is']1 k0 W/ e& D# Y6 ]5 k/ s, q1 b
    ! Y( e+ R" V1 n) T0 Y+ J  ^; D
    re.findall(r'\w{2}', '09 8? 7w c_ 9q p@') # 匹配任意数字字母下划线的组合,但必须是两次! Q- v* t8 e2 r4 x& c
    Out[38]: ['09', '7w', 'c_', '9q']0 ^8 B" C4 C1 a

    # z; V& M2 w: ~+ @" w0 kre.findall(r'\w\W\B', '09 8? 7w c_ 9q p@') # 匹配的是两个字符串,前一个是任意数字字母下划线(\W),后一个不是(\W)$ p6 J+ L$ z! b. `! a6 A0 Y' j/ O1 Q
    Out[39]: ['8?', 'p@']: |, E) o9 _/ d8 c5 m

    ; l. R3 O6 A2 m5 Dre.findall(r'.\s.', 'Constant dropping wears the stone.')
    0 @0 l( b3 x7 _9 t1 T! z  t5 hOut[40]: ['t d', 'g w', 's t', 'e s']6 }" j) }/ Q- L! \4 d
    6 U' p9 z. {  `, F9 r
    re.findall(r'上海市(.{2,3}区)(.{2,3}路)(\d+号)',+ J6 F, H2 F' [8 H
               '上海市黄浦区方浜中路249号 上海市宝山区密山路5号')
    / K0 o; `' t9 r2 l8 X$ l9 [9 x( q: Z& J6 S* s) z: W
    Out[41]: [('黄浦区', '方浜中路', '249号'), ('宝山区', '密山路', '5号')]8 j$ u/ O% |4 b4 c

    % W5 ~' Y, l. K* z1
    6 d, x, C* S( B26 }; _) v7 x- i, q
    3
    $ {0 O: l* ?# l4: s1 v/ W# K' u3 c' A7 }
    5. r! D5 K& V3 U1 K; M7 d4 e. ]& `$ i
    6" V: x- x  ?$ b' q' \: M
    7
    / m' ?& U6 b+ V# k8 X& v8  K4 o+ K# A) X# y
    9) \/ x0 m; t2 {3 P
    10$ m9 K6 a+ r. Q* T. ^+ s9 \$ a
    11
    4 p+ y2 w4 E" ^3 g7 u1 E/ n12; r7 Z7 ^, \! I1 S
    13& c* u8 [6 s; ]; p; c9 X) B
    14$ D. S/ t/ {+ n# D( n
    15
    8 h! B4 [8 l' g2 h16( u) ?* y: q& C/ c: N) b1 d- D' T
    8.3 文本处理的五类操作" a7 k! y/ H$ O" S. d4 j
    8.3.1 str.split 拆分+ Q. B. a( a) s+ i: j' P
      str.split 能够把字符串的列进行拆分,其中第一个参数为正则表达式,可选参数包括从左到右的最大拆分次数 n ,是否展开为多个列 expand 。: B/ C3 I- e. N+ x# _, ^
    7 e0 x. Z7 `1 C) H
    s = pd.Series(['上海市黄浦区方浜中路249号',
    " p8 x: s, V# J3 L) ]+ X            '上海市宝山区密山路5号'])8 W& {% {9 m; o% R  u, u

    4 d! M3 w# q3 O9 H1 _& h
    & a0 \4 S5 y( t/ Ws.str.split('[市区路]') # 每条结果为一行,相当于Series
    4 R# }9 v8 G5 C. g6 c, m0 i- XOut[43]:
    ( E, v$ \) E' g+ P/ [) k' s1 O4 t) n0    [上海, 黄浦, 方浜中, 249号]( N1 {1 S* V* z- e8 J! I2 h
    1       [上海, 宝山, 密山, 5号]
    4 x6 x7 q: ?0 i9 Z  v% l6 Gdtype: object( t& Y6 N0 s: N4 N: z
    : @3 E: S/ X$ p3 S; t* S, c
    s.str.split('[市区路]', n=2, expand=True) # 结果分成多个列展示,结果相当于DataFrame' Z2 O6 v) f( H6 _( G6 S" a
    Out[44]:
    ! U  R) U. Z5 N9 x2 e3 }9 z4 ?: B    0   1         2
    5 q: U6 ^+ h! \0  上海  黄浦  方浜中路249号
    9 n6 ?; m( q8 v6 O7 O1  上海  宝山     密山路5号
    # Y+ F, f* ?; V+ a4 @  S5 `. J$ E1
    % Z/ X# Y6 K, c- Q' Y& |: k2; E; S4 T& Q# q8 r3 T9 u: Q* n
    3* i1 a6 f) }. c6 K5 o7 M
    4
    , S7 c% x# }6 P- R5
    5 Q8 R/ _; c. l* \8 u( X* d- V6
    # Y& _0 P8 n" y0 _/ ~7 ~7 T7
    ( l$ O! K+ }% N- P% s8
    ' x1 s4 l, Q; s$ P; o9
    5 r# n7 y1 D% k; J% q$ _+ B- s10
    9 E# L+ w7 |; T; p11+ x4 d! U, [! k" e
    129 n4 T. X- Z* P$ N6 F( E9 ?* \
    13: J, K3 w: d; x. x7 v7 |
    14
    / Z  G/ k/ u: w; E( A; n15
    ! P0 q. \% V3 Y+ t  类似的函数是 str.rsplit ,其区别在于使用 n 参数的时候是从右到左限制最大拆分次数。但是当前版本下 rsplit 因为 bug 而无法使用正则表达式进行分割:
    ! p+ B, |0 Q, h# [! \! f" ?  z% n" p* x4 f# P- v$ R& K
    s.str.rsplit('[市区路]', n=2, expand=True)
    , [$ {3 C( _  u: S$ j8 LOut[45]:
    , d; R3 A4 b( z8 D- h& J: Q                0$ N$ p- }% L9 ]) M" Z) x
    0  上海市黄浦区方浜中路249号
    4 @' V2 g! x7 |) ?' _. w  u4 a& ]1     上海市宝山区密山路5号
    ) ]+ C- e# B/ U/ R* |! n/ G& M1
    0 `2 {) _% j; x; s! K9 G20 R, P2 {4 x4 L1 x5 @
    3
    + m' x' [' \# \# G  b. W" Z  ~& j4
    2 D5 j$ v5 t" v; T) X3 m; I( p9 T9 C5
    ! X/ w2 H3 T7 R0 O& _8.3.2 str.join 或 str.cat 合并
    2 Q$ E; E* z1 k/ W8 K! A) dstr.join 表示用某个连接符把 Series 中的字符串列表连接起来,如果列表中出现了非字符串元素则返回缺失值。
    / [+ E1 t' M" I0 e* jstr.cat 用于合并两个序列,主要参数为:+ S5 z" W' x4 A
    sep:连接符、) U) g. V$ W, t1 m
    join:连接形式默认为以索引为键的左连接, K8 P' _  j# Q% D2 J6 A
    na_rep:缺失值替代符号0 g1 B; n8 J. m
    s = pd.Series([['a','b'], [1, 'a'], [['a', 'b'], 'c']]): m8 C6 R! e$ t0 ^3 N5 f; d
    s.str.join('-')
    & I; w! w9 i9 s' K. `Out[47]:
    3 j, c, V# r$ g% W  R  x+ B4 r" g0 P0    a-b
    8 S$ e3 x$ q; M+ v6 w+ |) k1    NaN: h; Q5 q# z! G! B  `* p, a
    2    NaN
    / B5 O1 d5 @7 C& f2 M4 Tdtype: object! N& m4 |2 w7 o! t; C$ d! E; F
    1$ S' q: L+ H6 v! x  c8 N. @
    2
    # A+ L1 z2 ]# J5 X& K3
    $ o- f- O/ r8 ^6 R4, g7 p& b; G* f7 j
    5+ K5 G7 [3 S- Q# D6 E
    6" {1 S- x" i: f; ]
    7& g4 D. r2 |9 \! G  p
    s1 = pd.Series(['a','b'])& G9 W' M, ?* l6 C3 s# \- |
    s2 = pd.Series(['cat','dog'])
    1 A1 W( S" Z2 F& q0 @# Ls1.str.cat(s2,sep='-')
    5 i# P, L7 f" R( S/ J9 r. SOut[50]: 4 |: ~7 h/ @6 C' x9 ?, b: `, L
    0    a-cat8 [% o1 |/ T" i' ^, |: k# s/ g
    1    b-dog
    . F1 a: S: {. L  |  n& X$ H: Cdtype: object2 P# M+ R  P  a9 |* `

    ) N  b  c* X# ls2.index = [1, 2]. y% J/ j3 i4 ]+ @: O( o+ b
    s1.str.cat(s2, sep='-', na_rep='?', join='outer')
    0 O  {& z/ R$ ]. |# g: jOut[52]:
    3 E8 [( q  S* H! c3 v, Z# o* e8 o  B0      a-?
    ( u" L2 e9 s* H+ E3 v3 l, @$ f1    b-cat
    * t$ ]' L+ J6 E2 P1 h  u6 c2    ?-dog
    : l1 r% Z2 b5 O8 {, N1 xdtype: object
    / e: d9 A& ]0 O6 V/ a8 _$ X! F1$ ^/ a  E6 K% x1 ^0 x1 m! Y1 g4 K
    2
    - z, l1 ~: _6 T# C' q5 z3. f, j( b- O. C) j8 D8 P1 c
    43 Q4 O( B# o3 N' u. m3 t4 J( \
    5" X* c8 f# o1 S& }5 E; w1 x! e
    6
    ; Q! a+ K, a) B( _) o: U4 z- I6 O7
    ; A  }$ t0 J% {0 y' n" G8; U8 Z& R( _8 v* t. t  ?$ ~/ I
    9. T/ D* r4 z. _% [+ b& W
    10
    & k1 j& q& v) ?' b3 C9 v/ l11
    4 f/ b9 d/ G$ J( X) |9 A) Q12
    : a3 n" Y2 M# E8 f+ D2 |8 M/ o/ h139 y; p" b# ~2 p# Z; [) x
    14
    + O8 \' T( K+ }# w15
    ' R* [1 i7 k+ O& H2 b9 Z8.3.3 匹配
    1 {/ ^2 H; A  ]  Nstr.contains返回了每个字符串是否包含正则模式的布尔序列:1 l, q  H2 Z3 d4 \
    s = pd.Series(['my cat', 'he is fat', 'railway station'])* o3 {) d2 C0 f* ^9 n1 ^  W
    s.str.contains('\s\wat')1 B' V6 I( n- V$ w( h3 O" g
    . h4 R2 l  D: M: Q& f! R
    0     True
    5 C' o* P0 B, ~$ v1     True/ D" u- t6 L  X# ~
    2    False
    & K- D& F. ?+ ^2 wdtype: bool
    * V+ p& z2 g, F6 ~) X" n1* W& j, M- t. Z
    2
    9 y! P* `3 ^0 l6 X  ~3
    ( C! d" p: f9 i5 v0 l% n/ s; }5 G4
    ; @1 K# X' t! e% w! R" i' E5
    , x3 R8 O9 B; m' k- Y2 h# Z) _8 g6
    0 ~5 C, D' q2 |3 {- M7 ~. x) G7+ r% O( ?$ h' y. U9 k! S3 Z
    str.startswith和str.endswith返回了每个字符串以给定模式为开始和结束的布尔序列,它们都不支持正则表达式:
    # W: d. ?  u& G$ Hs.str.startswith('my')
    " E* m, j# K9 j- p
    ( ]  \. g1 ^) o; V0     True
    , I# x5 i" q: E' k$ w  a4 ]/ q1    False
    " ?  ?) {  G( M; L6 O" S2    False/ d2 p' t& o/ f3 W1 j+ n
    dtype: bool0 h4 U' ^( x3 O- J* V
    1: i7 F3 C0 k3 `# G* u; G. }' U: R/ m
    2
    4 j0 S: S" N. Z$ B3
    ' F. G; b* p2 x. p4
    5 j2 j/ V; a+ D, m3 V+ E56 ^- B8 _* k/ {" F  l2 j/ y
    6/ D, N6 \& f; W5 F! {$ K
    s.str.endswith('t')
    9 Q2 f# Y7 g2 L* M
      Q$ S1 ~6 p: j3 ]( j0     True
    ; y6 A2 o1 Y8 D6 b+ I1     True9 U# D9 t2 h+ H8 Q7 u& Y
    2    False4 D/ t6 k. w6 v. l$ `- I0 Z
    dtype: bool( A/ W' A& d) q- D) i. r+ Z
    1# E: Q" h7 I0 D
    2
    ' Z3 u& u8 P3 N8 z0 I* i# v3
    # R0 _0 i3 m4 y" j# Q48 {; q8 \( O/ ?: U5 Y! \- w* x
    5
    8 ?& X  C+ s: e; F4 h6 \2 z6% j& B0 \, l+ u+ n5 i2 O" S. |8 L0 m' r
    str.match可以用正则表达式来检测开始或结束字符串的模式,其返回了每个字符串起始处是否符合给定正则模式的布尔序列。当然,这些也能通过在str.contains的正则中使用^和$来实现。(貌似没有python里的search方法)' `6 p* C9 a4 Y: }4 @
    s.str.match('m|h')
    - g3 a- a/ ^! K" \7 E, @) zs.str.contains('^[m|h]') # 二者等价. r& M# H( C+ N

    7 F9 H/ w" R( X! ]9 q0     True
    0 ^3 `3 D5 T+ W% u& Q# b1     True
    8 h, |4 V) c7 o2    False/ h/ q. K) c1 O& A1 m9 u
    dtype: bool
    5 E+ V1 h, b8 C+ b- [1
    ' M* |* E- ]+ `3 \1 {0 g2  }9 z0 o) r4 u& R; _7 ^1 @/ u6 z
    3
    : J7 ?" h% q$ y+ ^4# s0 z6 H8 Y4 s7 ]1 l2 R" l
    5
    7 h' n! W1 ^; \' h& v6
    # m# p. G4 y  W# E. D, C8 |7
    / ~/ J4 t) ?4 d% |9 y+ k; p( j6 hs.str[::-1].str.match('ta[f|g]|n') # 反转后匹配$ c% u4 J  e5 a# x) D2 K
    s.str.contains('[f|g]at|n$')       # 二者等价
    + p7 b: p2 o4 Z3 P# S7 D) j
    5 |  Q$ r5 z) q+ B: C# L0    False$ R7 A5 F+ x( E4 z1 x/ _5 Z
    1     True1 h( l) O% d8 E& g8 ?3 v, H
    2     True
    8 e8 w3 k# m- M+ @dtype: bool6 |* Q! |+ V+ ~+ z. j% y* w
    1
    3 w& M, c* {$ w2 ~9 Q; @6 q2
    2 {* [0 g8 A* m  [/ e" D" h33 k0 l# z' E1 r% G" c6 ]
    4  g/ ^; h% Q' F4 y/ B
    5. }5 R6 x$ f0 ~( p* K; t
    6
    ! N; ]9 e. F( v1 l, f79 z0 ?) a. ^& z. g1 g& B2 g' j& k  h: q% Y
    str.find与str.rfind返回索引的匹配函数,其分别返回从左到右和从右到左第一次匹配的位置的索引,未找到则返回-1。需要注意的是这两个函数不支持正则匹配,只能用于字符子串的匹配:
    0 n( o/ X1 ?* |- A% As = pd.Series(['This is an apple. That is not an apple.'])
    ; ^  ?  Q& g) n4 C: c% r5 j& T/ R
    s.str.find('apple')' Z) {9 x9 G4 z5 m% e$ m
    Out[62]:
    & c6 X5 [) o9 Y& R5 j) H3 K+ y0    11. D  C7 t7 x6 q  A. ?1 ?% x6 v
    dtype: int64  E' q' {' E; l* i: n" V
    ( K3 C4 l) N3 l# O
    s.str.rfind('apple')0 r4 T9 R) r- ~* `3 O) `( @
    Out[63]:
    & l9 Y  A! B/ N7 L1 x4 A0    33
    8 _) ^( {# l' ?: j/ M# Z% j5 f$ [dtype: int64
    4 l, w  h' N& v- U18 J2 H) u& B! Z/ N) V, C- \
    2: o' {1 q0 b2 p5 ?8 V8 v
    3* X& L; }' k$ g2 N; g3 \
    4: O& Y+ p) }4 S' r1 o
    50 h  |3 G3 m% t  S0 H9 S
    6
    : g; E* _) r- W3 ^3 _7
    ' X6 r! h. g, Z& h6 _, g8
    " U  w! D) g# k8 w9
      r! M1 j3 D$ j1 s9 Q2 i10
    1 o4 \, c& b$ Q% m& N9 S118 L, L8 ^, h6 o+ m0 t3 e9 Z
    替换
    2 s7 Q$ e# z6 r; A+ Kstr.replace和replace并不是一个函数,在使用字符串替换时应当使用前者。
    7 @. ]5 C# O; z0 u. es = pd.Series(['a_1_b','c_?'])
    7 h3 \+ n' c" C* z1 J" j) e# regex默认为True,表示是正则模式,否则第一个参数内容表示是单纯的字符串,也就是匹配字符串\d|\?
    - h3 j) f; m8 n$ P6 b, Os.str.replace('\d|\?', 'new', regex=True) 0 u/ G3 c* ]/ e
    ) a+ d% \: p2 {
    0    a_new_b
    ; I' S" a# Q  L8 Q7 L6 q1 y1      c_new$ o. b' K+ F, i8 z' j6 a
    dtype: object
    , u+ i# Z0 s( }1
    ( J- K% K, f; O$ q6 b, {2
    * j/ y  A: R( {( D# F3
    7 L7 k, V+ k. L4
    4 p/ q; i  u( m59 Q8 M; b4 _( d, ?/ g* r
    6' ?5 R, m( b6 ]2 |" Z
    7
    6 ~* i3 X& o  B3 V- y+ z  当需要对不同部分进行有差别的替换时,可以利用子组的方法,并且此时可以通过传入自定义的替换函数来分别进行处理,注意group(k)代表匹配到的第k个子组(圆括号之间的内容):6 {/ m7 G4 F% @
    * y6 K* J% ?1 ?3 t, o2 G) }
    s = pd.Series(['上海市黄浦区方浜中路249号',0 H. E% u/ z" h1 y& v  V8 z- d7 c
                    '上海市宝山区密山路5号',+ s: ], x# V) |' K1 ^1 D5 k
                    '北京市昌平区北农路2号'])  R& M  A) l5 Y' m1 b6 f. @7 @
    pat = '(\w+市)(\w+区)(\w+路)(\d+号)'% E, s. s% ], W; V/ T
    city = {'上海市': 'Shanghai', '北京市': 'Beijing'}0 e# q2 a5 \6 A  a  Z
    district = {'昌平区': 'CP District',
    6 Z: K2 ~! Y( L2 J            '黄浦区': 'HP District',
    ( r/ E0 R( I( O' ?# k2 q' T: m' l            '宝山区': 'BS District'}
    , ?+ i$ T$ f5 qroad = {'方浜中路': 'Mid Fangbin Road',
    6 G5 }  l5 A8 L8 D; W" y6 \  _        '密山路': 'Mishan Road',
    : E9 y, o* Z5 P        '北农路': 'Beinong Road'}
    ! u/ K2 Z  ~, m1 Qdef my_func(m):
    ) U% g% [% L% y1 b" d# g/ Q3 n2 r    str_city = city[m.group(1)]8 h; s- c$ z3 X9 n; g. u% d; ]
        str_district = district[m.group(2)]
    / h  w$ X7 r) W6 g: s) ?. l    str_road = road[m.group(3)]% t1 Y9 P( Q0 p) R" d2 h
        str_no = 'No. ' + m.group(4)[:-1]" A8 \1 I) F) o8 f6 k
        return ' '.join([str_city,
    , K+ F, J+ i# z+ M2 s                     str_district,3 @, V- Z- A+ b" O5 \; D
                         str_road,
    - i! Y8 c. i5 r0 z4 i# G                     str_no]). z9 v8 B. q$ |
    s.str.replace(pat, my_func, regex=True)
    4 O( v# ~# d/ J% h
    2 o' y* k8 m$ v5 e1
    8 |) a3 F8 o% L9 V- c" e2
    2 A1 J( k$ h+ _" m! M3) N, u$ H. i8 W  O
    4
    ' R+ X2 F$ [  c0 |# v8 U  z7 a5
    * ]+ p# |% \1 `: U6, t' L2 `/ ?; Q8 A
    7
    ; K8 v1 l9 I; C, y: w) s8
    + r0 m7 G) c1 q8 R3 i2 k% d9
    * I# N9 P$ W' M" E4 F, c10- G- R1 Z3 g. r2 z( F9 \1 l6 ?
    11; h% A' S4 a/ ^. C0 X4 o
    12
    1 i! _4 C, y( g3 D' }  x# e$ G13( @2 ?7 [2 s& b9 X8 Y  `+ U" ~& k
    143 _5 n" B0 P4 s0 o- [- j. \2 r5 F- X
    151 s# \7 [: |2 p
    16
    6 g- T6 P2 T, ]) c17
    - D# @& U; ]+ _; B, l18, V2 m  P7 G4 T1 f, A
    194 J/ V! X' r" i
    20
    ; m7 U$ x' v6 Q21; B: z+ f; Y1 S; e% x# N# c5 u
    0    Shanghai HP District Mid Fangbin Road No. 249
    3 y; S* b# h1 D* {; r0 D1 M1           Shanghai BS District Mishan Road No. 5
    $ \$ i* b: ]/ W+ u2           Beijing CP District Beinong Road No. 2
    6 E9 V/ \. B/ S% {  ~" v+ fdtype: object
    % F  x9 ]' X6 s4 @8 q  G  ?; @1# |5 F: ^2 K9 l* @8 g
    2
    4 Y* _5 Z) E3 x  p% Q$ |3, [2 `$ X" o3 {' F4 _4 d+ s/ v$ W
    4
    8 O7 c# g" m$ D& q( ^" R( ~8 e这里的数字标识并不直观,可以使用命名子组更加清晰地写出子组代表的含义:# d& H9 d9 j) J: e2 S5 N6 h7 I8 O

    ; |0 ^6 r8 n: H0 B  t9 `# 将各个子组进行命名0 b; M- \, Z. H8 m$ h; l
    pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)'
    / D9 y( N+ h+ xdef my_func(m):
    # n' ^7 \# t! \    str_city = city[m.group('市名')]
    % r2 @3 z' V4 c; D    str_district = district[m.group('区名')]
    3 V2 v  `- `, n* h! P( i- @    str_road = road[m.group('路名')]
    - ^  s' f# H* F    str_no = 'No. ' + m.group('编号')[:-1]) A. L4 K8 \- N2 L, H7 G+ @
        return ' '.join([str_city,
    ! j  T  s# K; z! j$ S) o: S& T; y                     str_district,
    " i2 m3 |4 A8 o3 a                     str_road,
    5 c/ v- n& ~6 U& L# a0 f8 F                     str_no])* L; O7 n' l$ E' u% k7 g6 P
    s.str.replace(pat, my_func, regex=True)3 W8 w1 x( \; H% H
    1
    ) |6 f9 I& ]% L! _$ B2
    3 l2 A( A5 C  z& M' y5 @. B! j3; ?4 m1 t9 B: K- l; w
    4
    8 K4 p! D: N" n8 v. G$ Q55 J  h, d# E% }% }8 Y% u
    6- w4 ?5 R- ~2 |0 e
    7
    & Y1 _; U' @0 E# ]  P9 H8) F) E, A- _% V
    9
    # F* }) I, W* ^, ]1 T103 ]4 Y/ q3 m/ i' d4 h: O
    11( j" ^7 |7 p3 ]" Z# r- Y
    12
    % C* C$ N4 Q2 I# W' D/ Z' g/ X0    Shanghai HP District Mid Fangbin Road No. 249
    / s4 T$ o" ^2 @; I9 W$ m, s' `( S1           Shanghai BS District Mishan Road No. 5. {1 W5 m' T* J8 F! O
    2           Beijing CP District Beinong Road No. 20 \5 N; b8 J' r  b! w. C
    dtype: object0 Q+ S5 {: ~- ]9 D' ~5 w$ Q2 h8 M! t
    1
    + S4 m( g9 t  v) C8 }29 I5 x1 J5 }$ l7 r3 E0 _
    3! h* o$ E, {1 R* R+ `7 u* n5 i
    4
    9 l- ?- Z/ Y( R0 p9 U5 C' ?- g  这里虽然看起来有些繁杂,但是实际数据处理中对应的替换,一般都会通过代码来获取数据从而构造字典映射,在具体写法上会简洁的多。
    . q+ a. |% k8 S0 ?6 Z& H
    9 n1 ^: Z" s  @) G7 U8.3.5 提取, c! r( \+ m/ Y! X5 L6 X
    str.extract进行提取:提取既可以认为是一种返回具体元素值(而不是布尔值或元素对应的索引位置)的匹配操作,也可以认为是一种特殊的拆分操作。前面提到的str.split例子中会把分隔符去除,这并不是用户想要的效果,这时候就可以用str.extract进行提取:
    4 S) @" P- X+ Q5 Xs.str.split('[市区路]')/ ?+ o0 [; \6 T/ O1 C; i) ]1 g- `
    Out[43]:
    & @( }% X# E0 P( ~2 p" y0    [上海, 黄浦, 方浜中, 249号]4 Q, }3 B! Z( C0 g0 k; K/ P( D% R
    1       [上海, 宝山, 密山, 5号]/ P# Q. S0 X, b# @- T- j1 L
    dtype: object! g* ], J5 M4 o& e3 s5 _" ?

    3 n) u0 H- R* d% W5 |8 R2 Z2 `pat = '(\w+市)(\w+区)(\w+路)(\d+号)'4 |% m+ c( x8 h) x4 ^
    s.str.extract(pat)
    1 K+ Y. ?0 M- J4 Y8 p% Y: gOut[78]:
    3 E" G: K% |  |5 ?6 F$ ^4 t    0    1     2     3  w# u( Z3 m+ H& j
    0  上海市  黄浦区  方浜中路  249号
    . y8 p: [3 h# S" P: e2 z; a2 N1  上海市  宝山区   密山路    5号
    : u$ f$ u) R% D& Z( c0 C2  北京市  昌平区   北农路    2号1 [1 D- @( U$ H# H2 F! i
    1/ I- S" K/ S9 ^' a2 N) T- q8 Z
    2, e/ N: i5 b( L* n
    3# D! y/ O7 ]+ K9 B" p) i
    4
    ' v* T! [; a$ i: Z/ k5. \8 M" s6 e3 u: A6 Y' M
    6
    " V, p" I9 }3 D5 d8 I, p76 R- b1 _: S3 g/ q$ T) O! z- L& X0 a
    8, ^' w% j5 C: s1 n! K8 O
    93 F6 a; @/ ~- m9 B+ d5 a
    10# n  e$ Z9 H8 x" k
    11
    7 p' W$ D( K8 L! w$ H3 h' M- ]12
    + Z8 T0 w( X# B9 a13
    8 \! I0 i# w0 A6 l0 Y通过子组的命名,可以直接对新生成DataFrame的列命名:
    5 O! p" j9 D) I8 Q) {
    ' l" C1 K% k" H" y- ]" M: B' @pat = '(?P<市名>\w+市)(?P<区名>\w+区)(?P<路名>\w+路)(?P<编号>\d+号)', u' t5 r$ D0 ?! e' _" d
    s.str.extract(pat)
    ' z1 V/ w' K: aOut[79]:
    7 R+ E% H- Q8 a" R    市名   区名    路名    编号
    " l* g5 v5 _* I' u( M0  上海市  黄浦区  方浜中路  249号
    2 h$ v+ H6 G! s5 y) d1 \1  上海市  宝山区   密山路    5号3 }* l; W5 R) S: _' T
    2  北京市  昌平区   北农路    2号
    ' D  v3 m/ `( \1
    - e2 w9 ]2 T9 x$ c8 H& G" i2
    9 w7 `! d9 Y# X) m- v, ~1 p3! |8 k* f/ D! V
    4
    & ?# j: d2 P' }1 J59 e- w5 L9 n- i" `5 A5 P9 U
    6
    5 \5 O  `6 a3 w( Z, B" ~7% E/ K' P' W1 S- m. M
    str.extractall:不同于str.extract只匹配一次,它会把所有符合条件的模式全部匹配出来,如果存在多个结果,则以多级索引的方式存储:& M/ q9 R) Q  [" ?0 n" G
    s = pd.Series(['A135T15,A26S5','B674S2,B25T6'], index = ['my_A','my_B'])( q6 J1 i' y! {5 A2 q
    pat = '[A|B](\d+)[T|S](\d+)'
    $ ~# f' a. e7 E. i# i  js.str.extractall(pat)/ C+ o+ \" s1 {+ o
    Out[83]:9 t3 |3 Q( f+ k
           0   1
    % M/ k. T# i* Q; m+ Z/ q0 [- X* X     match         ) o. ]( g% g; B1 p
    my_A 0      135  15
      [3 D5 g. G. h     1       26   5
    ! c8 v- I/ }' m0 w# P$ u1 rmy_B 0      674   27 ^6 t: [+ R5 u6 w; [- S7 s
         1       25   6; f% D: t" P9 P0 w# m  c: c) G
    1" \! Z0 J$ s6 T/ t
    22 v7 g- }% `, B4 ^
    3  `! n- Y8 w5 b' k1 x. {
    4
    . G1 S0 T8 |3 n# d5
    . J1 j( Z9 }/ r. [" H( V6
    ! L+ A( R9 q* x: Q: V: {: P7. ?) Z9 H( @6 ^# C/ Q9 I
    8+ Q$ q) j6 L/ n' z
    9$ N2 D; S6 w) Z* e
    10
    ) I% F- B1 q: u3 |pat_with_name = '[A|B](?P<name1>\d+)[T|S](?P<name2>\d+)'
    * g) J- z+ R7 k8 h2 Hs.str.extractall(pat_with_name)
    6 _& }) n7 l$ i0 g  R, k1 xOut[84]: ! s6 [( j, \  F# y' t
               name1 name27 ?+ X7 n5 q4 b
         match            3 g5 `+ s( Y8 Y+ R& N
    my_A 0       135    156 J4 y  k& {$ H; g. y
         1        26     5/ _+ j' k- F# @1 p
    my_B 0       674     2
    . F% {2 o4 v' q     1        25     6$ K" T# _2 B1 {" H) |( |
    16 m; c, q5 I$ F$ A' Y$ J
    27 N1 {/ A) Z2 t" O+ q, H% A& z) R
    38 G& U) @' z, b# ~. R
    4+ |& w! Z2 O# `' ]
    5, p- ~; y. m8 e
    6
    4 I% w% F. c: u* T7
    ( w+ s6 u: M# T: v; A. m/ j" Q8
    ; h8 c+ {' u! y9
    8 j: s+ z' I) H3 W" P6 b: ystr.findall:功能类似于str.extractall,区别在于前者把结果存入列表中,而后者处理为多级索引,每个行只对应一组匹配,而不是把所有匹配组合构成列表。
    ( g& o, i- O+ M$ G/ ts.str.findall(pat)3 D0 |2 _6 h- y, @- V$ f  Y
    1/ C, p/ }# K8 y' W* E7 h* z# j
    my_A    [(135, 15), (26, 5)]8 D; U" |# |* G6 \5 P: y; q5 z
    my_B     [(674, 2), (25, 6)]5 B6 Q$ g9 d5 O+ h3 @! G
    dtype: object0 B! W6 H9 {5 |' s$ c4 O4 [
    1
    8 g9 t, C( v# ]6 S1 R2
    7 f) z/ G$ `/ Y  _3* C6 ?' W' ^8 n0 T1 g
    8.4、常用字符串函数1 E  H6 z7 J6 e' D8 g
      除了上述介绍的五类字符串操作有关的函数之外,str对象上还定义了一些实用的其他方法,在此进行介绍。
    / t9 `5 L6 R' H% C- E
    ) E' ?/ z  @) [/ `1 Z, ^8.4.1 字母型函数1 `3 b% U  S  b: O4 }; T
      upper, lower, title, capitalize, swapcase这五个函数主要用于字母的大小写转化,从下面的例子中就容易领会其功能:
    $ W  Y2 R" t+ r! d* P& G, y- ?5 c1 Q- g
    s = pd.Series(['lower', 'CAPITALS', 'this is a sentence', 'SwApCaSe'])
    ' d% f, ]) H! a
    ( G' G7 Y. y' S2 ?( p6 C  Y4 ^* Qs.str.upper()
    % ]* |" p* W% f5 GOut[87]:
    1 c6 m8 ?- @" D# C0                 LOWER
    % \; r  L7 E! Q$ q+ ~1              CAPITALS
    5 ^6 X6 K0 ]  s0 a  g) I2    THIS IS A SENTENCE8 k( v! C9 ~9 W& O
    3              SWAPCASE
    5 S. \" [. Y/ N9 Q1 l! d5 |" D2 Xdtype: object
    & I6 L  S2 I- M+ \% j* Q2 s# u/ V& w- u* r0 t
    s.str.lower()
    4 t( O* X' n; X) A3 O" DOut[88]: 8 g" q* J! q, S8 j* R
    0                 lower
    ' E. [: ~0 V4 V1 q! V" w# r1              capitals
    2 q0 z: r! v/ H1 {8 c/ L" X. k2    this is a sentence8 k0 I; ~4 L* J, ~& R
    3              swapcase
    / p; g# b, n0 R  D9 z' h/ Idtype: object( N' c/ ]( w( w2 ~$ X8 [% L+ |
    4 E# K  e/ {5 h; i3 y& O( E
    s.str.title()  # 首字母大写
    + a& ]3 c: S. U2 Y$ bOut[89]: 1 {2 t/ x9 {, e3 X
    0                 Lower
    / ^" t2 J9 |9 [1 n# F$ Z) q1              Capitals* q* o1 R; I7 h8 b! U; A
    2    This Is A Sentence4 J5 |* w: |) e& J6 h
    3              Swapcase
    6 D8 }# E% {" xdtype: object
    : m! |7 s0 \9 R8 O0 f" }9 \% Q) p3 W
    ! E% R$ ~3 H+ V+ Cs.str.capitalize()  # 句首大写! i/ t$ G9 t  f
    Out[90]:
    # T6 W+ a" u: Q0                 Lower
    ! F$ }; |# u! s# Z, v2 w9 e6 L1              Capitals5 ~5 H: U  y- N, A' S
    2    This is a sentence! e0 t1 t0 N1 O$ s- T
    3              Swapcase6 {( [- c; U1 a6 \# [
    dtype: object  D3 p2 v* S% f" f/ h
    6 c; J# Y$ J% C: M0 h
    s.str.swapcase() # 将大写转换为小写,将小写转换为大写。
    & J/ i5 F5 c+ E7 l% Y( M- oOut[91]: % F! O1 o) F/ |6 l, w! z* e# H: z
    0                 LOWER. w0 Y6 e) Y$ F' \- _  m: ]
    1              capitals9 c+ u" y6 N6 x& w5 C, |& Q
    2    THIS IS A SENTENCE# ~& t5 x2 k% g) g3 x
    3              sWaPcAsE
    9 ~; n7 s& P$ I  m5 [dtype: object" G6 L$ Z, i+ ~3 ]1 h

    + o- u. v7 Y4 R: e4 g2 @# us.str.casefold()  # 去除字符串中所有大小写区别
    . f8 X2 A* S& f. Y  `: q( v
    5 Q; l! D, A- Y  J' b8 W3 q0                 lower
    + Q/ p! j; L5 w: j% r+ Z" O1              capitals: j) B' S/ N/ k6 A  P
    2    this is a sentence
    6 D9 b$ n8 p# v8 P0 t3              swapcase* O! u1 l( \* a* q" K6 J9 D0 H
    6 ]  }9 o# J! l- g
    1
    : y, Z0 P& F6 t: c# S2' o  T$ t. ?$ C8 z" @+ e
    3
    ; R) V. `0 v# s- ]5 q1 t  h/ E' g4+ d: _7 e4 j, I, ]$ n
    5
    * P2 G) m% m2 G/ @/ }6
    - F2 L9 D8 g- h' z. P/ J7
    " v$ i% h8 r, r+ C: e9 J, ?' M6 V8
    3 F! Q2 e0 }+ b" a9 `; R9
    6 U* ^1 ?# p* o5 g6 l10, W: @( T& b7 r- i0 J  w3 G
    11( V* C9 ]+ B$ u! y
    12/ F  _" z9 O5 K, [% p" d% C' y
    13, x- n5 R* g+ c% \# Q
    14
    ' u% H! Y! C, u1 ^15
      {. x. V3 O  C$ ?+ |16
    ! u# @1 y( a! k3 j17
    5 H, z# g) D* i3 e  z3 J1 {180 y0 z& L. A# q
    19
    9 O. M* g2 V% O/ G+ i+ C4 t9 f20/ B& [" l/ W$ }1 M+ ^
    21
    8 j5 S6 I3 U, X) n. Y9 o- y22# L# b' e+ m/ z: L; Q& [% F" E
    23
    1 j) F0 j" G( ^+ t6 g% W24# x% W* d9 a  y5 Z+ a) d
    25  J6 c2 F$ g* ]
    265 H  ~  ], [3 g; F
    27
    + `5 o$ u0 R# g1 X4 Q28
    " w7 B% R- @; V0 p  d# [0 V6 i29
    - w  Z; \6 F* E- T+ G! ^+ U30& Q9 f. ], D4 W5 f# q' k" V
    31; @% X& ]; r+ v5 N. ]: Z$ {$ X
    32& j. v( w9 V/ `+ [7 l
    33
    ' ?5 l* O( L" Q34
    2 r0 r5 C- F5 G3 T  e, m359 {$ `* Q. V! _
    36
    ; y( z. [; @. k37
    , s/ A7 m) d  b8 L8 N38
    ; R# R) V& V1 X4 `% b1 t39
    5 n/ z- ~! T8 |2 d1 l! L40/ a+ B2 Q( U8 E; y
    41+ f& |5 P  h' ~
    428 h+ h9 |- R! q
    43" Z6 g; ?+ B# @2 q2 Q! X3 J% d
    44. j$ Y3 ^, n! m7 `. ?* q
    45* P3 O- r0 f" I0 T
    469 ]3 G% A/ `9 p9 c
    47  A2 g9 M* j; r2 e( j
    480 g4 f! }& o8 G
    8.4.2 数值型函数
    2 r7 P8 P* J: W+ c4 r  这里着重需要介绍的是pd.to_numeric方法,它虽然不是str对象上的方法,但是能够对字符格式的数值进行快速转换和筛选。其主要参数包括:
    5 T' Y& N$ p0 G; I) ?
    # {- U' l5 r. s, R& K8 s* Berrors:非数值的处理模式。对于不能转换为数值的有三种errors选项:+ M7 T9 L1 }) X5 F) q/ ~
    raise:直接报错,默认选项
    9 B3 \' P& F/ [* ]% scoerce:设为缺失值
    / ]$ [$ Q  s( n9 {! Nignore:保持原来的字符串。
    7 l6 w2 J! F5 R* e8 mdowncast:转换类型,转成 ‘integer’, ‘signed’, ‘unsigned’, 或 ‘float’的最小dtype。比如可以转成float32就不会转成float64。
    8 a% A* r, O& |( O4 \2 i2 S+ q6 a, Qs = pd.Series(['1', '2.2', '2e', '??', '-2.1', '0'])
    7 F* D0 [2 A1 R" N9 E( O/ @" {/ w2 W, u* j; _( G; F
    pd.to_numeric(s, errors='ignore')2 M; L, F( J9 n+ \8 _" [8 v
    Out[93]: 3 }  ]6 N) }# j4 N+ b3 w) Q, w6 e
    0       13 ?2 E. m7 a  n1 j0 ^1 b
    1     2.23 `) m, V2 r. @  V6 q" _; [
    2      2e
    ' r6 ?9 [$ ^/ k/ y- w4 W' V3      ??# \6 i! g- g! P9 V
    4    -2.1
    " p! p7 P) v; N. e3 S( U5       0
    ; u+ H+ |! x) S( L3 U0 V% adtype: object0 W- c* l# V0 T, |# X" d! c

    & A( X  B8 X+ }* Zpd.to_numeric(s, errors='coerce')
    9 P0 q% u! p. r( z6 r" Z! u. k. a/ ^Out[94]: / q$ e( R) L- P3 e# {$ u; g4 ~
    0    1.0% c. R8 v" C7 z% P% R# W
    1    2.2; B" t4 ]3 [& u* \- i. a2 K; [0 N
    2    NaN
    9 s& W# D, S+ c1 l/ T  {3    NaN
    0 B6 u/ T' }1 j: r& X2 S4   -2.1* y8 ~. E" i( ^! W% E7 t' V/ z+ j
    5    0.0
    1 l; O8 |! ?! r3 j2 Gdtype: float64# B" S- w+ T1 X& `! f9 N2 G

    5 k$ r2 C3 e! y. Z3 [1
    5 A4 C/ p5 ?' B; ^1 s  y26 O' A  W& `# v: Y. ^
    3
    & E' f8 u2 S; X+ z' A4- E4 Y! Z$ t. g+ P5 M5 P  G
    5
    ( G" ^6 \' J& v7 B" M! |6
    $ G' O1 Z; u# ^6 V7
      d  m: H6 [# \# v8 P! F83 R9 ]+ ^5 x8 |7 N
    9% Y' q: {% E- t+ }
    10
    2 i5 n) W0 f4 G# O! m7 F' k11) K9 B" I5 i& G
    12
    5 ]6 r5 }" z5 @5 D131 f% t; N0 U3 j# e  [- z, U
    14
    1 [" z2 E' J! ~15
    * i9 B4 E; w9 t) b5 C2 y9 _164 D" A# E' J0 C; Y. F* n. Y; Q$ T: J+ Y
    17
    0 {: y6 r: K9 i18
    : Q# a4 V  i  @0 a0 q199 ]6 e0 v& U! d) Y$ Y
    20  M( z$ j& o9 U# M1 ?8 J4 b
    210 [  R) x3 e5 K
      在数据清洗时,可以利用coerce的设定,快速查看非数值型的行:
    6 j" `/ u8 u* d# n4 c: L/ i! _1 D7 T7 R8 @0 Q4 J2 e
    s[pd.to_numeric(s, errors='coerce').isna()]" U. `) k8 g  Y. L+ L: w
    Out[95]:
    0 I- X4 _0 r" \4 _7 w2    2e
    0 n9 ?7 k! }0 a3 H2 S4 y8 k3    ??
      T( M9 D9 A: B7 e+ Kdtype: object- [+ k& n. s  r' o. M  k6 t
    1
    ; o  ?# Y  M* W( E2# a* e- ~) \( N! O3 C9 A
    3
    5 j# I5 t$ p8 G+ }4 i. s4) [! X+ y# @. X+ {. _% u
    5
    ) \1 d/ z1 X0 U0 C8.4.3 统计型函数. q: X, x2 X- h8 P: A: ^5 A" w
      count和len的作用分别是返回出现正则模式的次数和字符串的长度:. {  w% H0 ~  B; ^. I. @% l

    ! s( J! v, O; E# es = pd.Series(['cat rat fat at', 'get feed sheet heat']); n" F' n5 F. l
    - @3 p: u2 I. F2 |8 G0 x  X1 O) [
    s.str.count('[r|f]at|ee') # |左右两种子串都匹配了两次" R3 _6 [, j( b9 n
    Out[97]:
    . b0 l/ l! }- b. J0    2
    0 e5 W/ s0 A  u- Y5 a: ~1 @1    2
    / c& a4 v. ?1 u' [: Ydtype: int643 @3 S( |% }  L# M6 q. A# D
    - D$ w6 f# v* b4 Z# J7 F% z
    s.str.len()
    " W8 y; _6 g* I% b6 Y# c) aOut[98]: 6 V, G" P, W) H) X
    0    14
    ! Z6 Q" ~6 A/ m- `1    19
    + _' f( `/ X- d' y% D' odtype: int64
    $ \' N6 B" h/ z2 u1: [* N  \+ Y9 Z7 _
    2
    * k+ P0 F9 ^" {: b0 F7 [% D8 t2 i3! K/ W( f; X/ l$ g1 K: R! I4 Z
    4+ ^" ^  s% K- G9 {$ `/ m7 h  }. a
    5
    ( `$ z% ^. P/ O3 H+ `1 h2 U- p6% C& z+ _( s! ~9 v  `) H
    7
    ' x9 H/ \6 c5 p4 Q8
    2 U2 v4 X' Q. v# [+ F  y  y: ?/ q9
    1 O/ |; @( L* B  Y) E* j* |+ x10
    : \0 I" ?. M/ V113 Z9 a% p( `" D& ]) Y- h, V
    12# M. e. y. p. b& m
    13
    0 |/ H2 z' ~- j+ s& O  o3 @8.4.4 格式型函数
    + @/ }# c1 Y! \# g  格式型函数主要分为两类,第一种是除空型,第二种是填充型。其中,第一类函数一共有三种,它们分别是strip, rstrip, lstrip,分别代表去除两侧空格、右侧空格和左侧空格。这些函数在数据清洗时是有用的,特别是列名含有非法空格的时候。: n3 V6 b( n+ S5 T

    4 k" F5 L6 X+ v8 u# O0 m4 Xmy_index = pd.Index([' col1', 'col2 ', ' col3 '])
    6 _8 I$ k$ S  b7 l$ k
    7 V5 G8 v7 S* q4 Vmy_index.str.strip().str.len()
    ( ~* p7 o$ J6 O+ P% SOut[100]: Int64Index([4, 4, 4], dtype='int64')
    , C' ]* t$ @4 L+ D
    & y$ H9 [2 ~6 i% V2 ~my_index.str.rstrip().str.len()6 }% N! v2 N2 }+ K' f, s9 W
    Out[101]: Int64Index([5, 4, 5], dtype='int64')8 c* F5 k# e8 J7 I, }
    8 g1 I1 o+ Z* C6 o0 P, Q
    my_index.str.lstrip().str.len()
    9 c7 s2 @( H6 ^* e) uOut[102]: Int64Index([4, 5, 5], dtype='int64')
    ) k" z! ^2 @5 B: g. J1" g4 m1 u! j' [- V
    2# q& T6 t: x/ d; [
    3- m9 O' x3 K$ d7 b9 K. f
    4- k8 J1 i9 c% a  o0 Q) J  ^# C4 [! q
    5
    2 l4 ^! w+ ^9 d; c, b  e6$ y: l* e  {7 |$ v3 k# o3 |
    7
    7 d- I7 M5 ^) [$ P, c) C. ?1 \8& A% u" ~( m  @' J' V
    9
    1 s! \3 f, G3 t( t" p10
    - ^1 `3 [/ c. d8 B) g6 M  对于填充型函数而言,pad是最灵活的,它可以选定字符串长度、填充的方向和填充内容:3 \* x4 \0 C1 s/ w

    : _! J8 O: g# O- E( p6 H! Xs = pd.Series(['a','b','c'])
    & Q. v" a# P+ o2 E. S/ [! C
    " w8 J: `7 A+ ~8 Ks.str.pad(5,'left','*')
    # t7 w$ \5 W' ~% P0 S5 n7 F$ E" dOut[104]:
    2 x: f' B0 [- ~8 @" Z2 @, U- y0    ****a
    & ^# j) h4 u6 x1    ****b" P. M- I7 H0 {; y4 E$ f2 K, B
    2    ****c/ y7 [( G' i/ f$ v; m& d
    dtype: object( U3 w/ s5 R, ]' U; ?

    ! B/ S. B0 u% ss.str.pad(5,'right','*')
    ; }! a5 t' P- a6 COut[105]: + K) g$ V5 Z8 _4 ^
    0    a****
    # ^6 L) t- C( v/ l4 F1    b****7 a$ ]) ^# G8 a, O1 \: e( ]& g
    2    c****
    2 M$ D$ o6 c& u! n$ cdtype: object
    ! `3 [5 C" c# z4 q: x6 ?: b0 x1 q: R/ P( X- D
    s.str.pad(5,'both','*'). X# W9 \/ T" k0 X, @4 q2 [
    Out[106]:
    9 N% W1 m7 [8 ~* W" P* ~, `/ g0    **a**
    ( h8 g( z9 Z/ k/ W1 L( [! ]1    **b**
    / r; Y* }# |- ?/ p1 Q2    **c**
    1 _8 j/ {1 U9 {2 h$ p* Edtype: object  e* e1 R4 m: H: o& A0 z( E9 N

    6 k8 }4 A( g) K6 H  G# r/ d1
    + K8 c9 O5 z( b, o7 Z2
    & H. Y3 n0 F0 S5 V& N6 a3
    " A+ v$ h6 i: M4
    9 q  t4 S( a" G5 Q$ `5 N51 g6 P; R3 P' Y, q% E9 r; H
    6
    , V- b; ?* L- H7
    1 A) e) ], z# B" k8# C& `+ m  `* o$ X* h0 v$ v! @+ i
    9* m) m( O1 a. m7 s5 W! O1 o
    10
    8 l% E% c, o: B; i11- K5 ^6 l9 K# e( d1 M
    12
    9 e# ^( w- v9 A" K( O13( S, b  T) Y; ^" }
    14+ [: B( c0 N# Y9 G* a6 E' {
    15$ I$ q6 F. m. e: Y+ P3 E
    16
    ) A: x- j) Q% \* I17
    + `3 p. m# ^$ Z0 L' `) ?. V% }18
      k  \/ e' p& o$ l* |2 i9 U19
    2 ]; Q. g& }% S! I+ @7 S6 Y20( h1 B. J/ c  A
    21# A8 z  m3 z- d1 U/ A$ ?! m/ `, v( H
    22
    1 o; s! _; X* @9 O6 V. @  上述的三种情况可以分别用rjust, ljust, center来等效完成,需要注意ljust是指右侧填充而不是左侧填充:
    . U" P% n& h  g1 D/ q% ?# b. Q: b
    s.str.rjust(5, '*')* x2 P0 k' q1 m
    Out[107]:
    : t& i9 }4 g  f) I+ f- S& f0    ****a* I$ {* H( T! Z1 [4 ~
    1    ****b
    % Y4 t+ p- q$ N" H2    ****c
    7 W. s! q- ~8 X3 Q6 T: S: W- _9 Z9 ^1 sdtype: object
    0 W, a* F/ T( W/ K6 R% r7 ^5 d5 `
    * Y: r) ~; V! s& d* w5 [7 Vs.str.ljust(5, '*')$ ~3 d: ]2 }5 L  A* K" l
    Out[108]:
    # s( D, K3 w) u5 J% L! G0    a****
    7 _7 D3 H, p( D, w1 F1    b****" s( m2 H2 S0 @- K$ o" h; G4 R
    2    c****
    / v5 @: _. L) q( {0 }; p' _dtype: object
    - W* c$ A, _) Q& O4 d/ @% g1 Q  @7 H' W6 Z
    s.str.center(5, '*')7 [" w6 f" `% F8 z
    Out[109]: 4 V3 @. L+ U, R, m3 K. F. e! T9 G$ r
    0    **a**1 K0 g6 {: b, n; M- J6 R3 M  Z; {
    1    **b**
    3 x' T8 y4 E& n$ D2    **c**! b5 B) Q4 X8 m' _4 k. X
    dtype: object
      v0 w7 n/ x6 i. P* R5 p, G! r1 g* m/ A! c
    1) M0 r2 c6 Q2 P
    25 S& J( o& q. b. D+ V
    3# [2 Y! r3 J8 Y1 ^8 g8 f
    4" Q$ p# I9 ~  j7 G! T4 H) e
    52 x2 w2 @7 f. d1 E0 }" q) K
    6- z/ `, n% A% P+ \- G
    74 A7 x$ C0 Y& s5 g
    8
    $ M7 a/ j# S+ w4 T0 C/ u9
    4 y! h' c% l6 b& T, E0 k2 S109 f) B( h1 f7 e* B& e# }
    11% d1 J9 V( q8 g) I
    12$ [7 _: g8 X3 v8 i' G
    13
    8 N# V3 G9 m* K, ]; s! {1 b! J147 U1 A; G6 E$ C2 ^# g
    15
    # k3 u1 t+ z: b3 w  F3 H16# ~! L2 B; T# \) D
    17$ h$ \5 a) E/ ]. O- ^, b
    18
    + s$ d, I6 p2 f19
    7 r0 y8 ^: T) s2 E4 q  ^20
    * p( D1 f" M3 ?" r5 Q( g0 N4 \. {) w* H  在读取excel文件时,经常会出现数字前补0的需求,例如证券代码读入的时候会把"000007"作为数值7来处理,pandas中除了可以使用上面的左侧填充函数进行操作之外,还可用zfill来实现。
    : J% g  ~0 L1 b$ f5 S0 o( M3 ^6 Q6 S  k! }
    s = pd.Series([7, 155, 303000]).astype('string')
    : d) {3 ?9 s2 f6 H0 @
    * r; J. q2 L+ i4 ns.str.pad(6,'left','0')4 `3 ?# }9 `, S
    Out[111]:
    $ S, t2 ]/ l; K5 z* ~9 x0    000007+ {1 P# N& Q& B/ |$ C
    1    000155
    + J* y) d; C( h1 L- W3 x. M2    303000
    / ^4 J; H9 z. A7 d% X/ Edtype: string
    . a1 t9 S( u" O0 Z# v4 d# @) x+ Z; n6 u; X. ^9 F
    s.str.rjust(6,'0')
    ) [9 a$ f1 _) l4 D5 gOut[112]:
    ! s: u4 G* S" P- R0    000007! W3 O  W, d+ v5 N$ ^3 T
    1    0001556 j0 y" D( m) m- |; I2 |8 r; S/ S
    2    303000
    % z6 L0 J. z. Y) P* mdtype: string
    / L* i% u3 o1 {8 g! u
    % [$ D: e/ J$ Is.str.zfill(6)4 [# A* F. a4 \& F+ _
    Out[113]: 9 J$ D, t) }2 ^
    0    000007. H( c* L4 @' B; d2 d( y
    1    000155& ~0 J! q: c" w+ y7 N# b, ?
    2    303000
    4 K2 V8 ^0 j1 I/ O$ Edtype: string8 P6 f# c; a4 y- b2 w
    ' r+ V9 m6 R  A* U3 ]3 t0 J
    1
    + t% Z) s  n! z/ g  J$ b6 ~2. e- G' m2 i1 K& r6 l
    3+ y/ {6 o) @( q2 U
    4  v2 m) x" U+ D% ?5 R
    5% Z9 ^0 G7 `! H9 O
    6
    ! c2 C4 x( h2 ^- T7
    5 f% z& k  M4 C# K$ ?$ N4 I( a. X8
    1 e4 O* ~2 A' O- o+ D" ]! m8 I  ^9
    1 ^* l4 h: v  s( c1 G10
    9 I  J6 U' T- r* S. B) p% }% o6 P11
    0 r) ~7 ^) j1 `1 s& I7 E( D12
      |; M: n) ~+ H, B* t7 Y: }+ t13
    % x$ Q- R4 a, F$ r; [5 F- l% ]/ D14
    : ~6 i; \- i' ~( o# B157 ^0 r' {% ]! G* a1 r
    16/ |) Y5 ^, H7 C% K( A, Z
    17
    $ k1 H! I( P- ?189 E" y" O8 o' r" f; v9 ^9 ], a
    19
    3 L  C8 f# d8 F' L. {9 [0 w20
    3 O' i, V, f; v2 j) n21
    $ M: b' d/ N  _) G0 l  _$ T22
    " a7 H; Q/ L& T; m" b2 A' P/ A8.5 练习1 s) T! l/ {6 z- Y& u
    Ex1:房屋信息数据集" z" @" s8 @/ M; b
    现有一份房屋信息数据集如下:/ a: I  _5 K. J

    0 j: [' c* M$ Pdf = pd.read_excel('../data/house_info.xls', usecols=['floor','year','area','price'])
    : v. c& ?) O! ^( @df.head(3)
    - ?" k# l& [" H7 L$ n; ~6 POut[115]:
    " |! q% g* o* X2 H0 E5 V0 }/ o+ v      floor    year    area price% I( q9 v" v3 K% |
    0   高层(共6层)  1986年建  58.23㎡  155万5 @7 d  G' X1 k/ R
    1  中层(共20层)  2020年建     88㎡  155万
    * P0 {1 g9 h( ^6 u0 U: U2  低层(共28层)  2010年建  89.33㎡  365万8 J. m7 O" b) g
    1; c4 S( |- ~$ I# l3 e
    2: T5 |1 z1 F4 s2 m
    3
    3 V$ W$ p) p/ m% B8 V5 t4* y! F$ P* R% U* R0 w
    5
    * o- U& p( q; e69 g( W" s. j+ T$ q# K8 r2 p9 W
    7; [2 E! Q/ X5 j
    将year列改为整数年份存储。
    ( c8 Z% a/ T5 [将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。$ E5 m) u" \! [, B
    计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数
    " S. R8 t* l' k& _( g7 q将year列改为整数年份存储。1 n* h9 L8 b& f8 T* o
    """
    ! S* T6 R, r8 [  C- d整个序列需要先转成Nullable类型的String类型,取出年份,再将年份转为Int64类型。
    ' B  D& }; F2 ^7 O注意,转换的类型是Int64不是int,否则报错。即使astype加参数errors='ignore'跳过缺失值,
    ' B( V3 E" A( Z& y, v3 {3 p转成int后,序列还有缺失值所以,还是变成了object。
    2 O4 C( j3 P( r1 ~. ]而整个序列转为Int,就还是Int类型,缺失值变成了 pd.NA 。
    4 I3 @+ m, q2 Q0 X"""
    0 G' X* ^+ j( U/ Vdf = df.convert_dtypes(), [: r/ _  [- o- K
    df['year']=df['year'].str.replace('\D','',regex=True).astype('Int64')
    0 s) c0 o4 \) H% M; Udf.loc[df.year.notna()]['year'].head()
    0 Q2 x! i0 s; @" p1 n$ V& I# O8 n/ ~1 d6 b1 ^4 r( d
    0        1986
    ; o7 K: u: b7 G1 m# F- M2 ?1        2020
    / p  ?, C7 J: k% A8 ^7 y% b& r2        2010  p5 j$ N3 S6 M- K$ f1 Q( J
    3        20140 s5 G1 ]" |  [- I' e' u
    4        2015
    , F7 {+ d: n9 T3 uName: year, Length: 12850, dtype: Int64
    ( j. I5 w! v" Z) G9 @$ r) T4 I
    * `& r; |  v( J" M: R: j! a1# n% P8 t9 g) M
    2; \, B1 O' ]" {
    3
    1 Z: V" i- u9 M4 M/ V5 V6 ?4
    4 w3 @  d. x; {# n- O0 r: t) {" {! S5
    6 c1 C* Z7 Q3 \& i! D6/ y# a4 D1 i; m( I8 ]/ I7 Q
    78 T- D' s+ i9 T: Q
    8" [+ Y5 A) M8 s8 n) ^. N
    9
    # n9 P' \+ M8 j" i/ i+ w7 S10
    5 T; @  N, @! @/ n6 p* h: H+ S11
    , l5 o3 v  Z* ^+ A, g12
    ; h. k: [- v# e( l" A; [6 R0 U13+ F- g. n% S  w. g# n4 q  ^; P% d  d
    142 u  I0 ^- }8 w
    159 v- [! N7 o6 T7 f0 \: Q$ N: R3 U6 a
    16
    , N  N* W" {+ j, o' n参考答案:
    : {( a; l% B  s" m" Y. b% _, w
    9 m" G7 P& [' e, K# v9 x不知道为啥pd.to_numeric(df.year.str[:-2],downcast="integer")类型为float32,不应该是整型么4 B2 R" t; K& w& q# c

    ' F$ l# e+ w; g" D* b. e7 s2 p  s! |df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64') # x) }7 O. a* O
    df.loc[df.year.notna()]['year']
    " J( X0 v1 x3 m1. z9 J. D2 g) v5 L/ }
    24 R1 f. w1 t- B, v- T/ v# g
    将floor列替换为Level, Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。
      ^  Q: e+ h0 d' o* z8 Xpat = '(?P<Level>\w+层)(?P<Highest>\(\w+层)'6 _4 S) q" z9 m9 c$ n. X, U% L
    df2=df['floor'].str.extract(pat)  # 拆分成两列,第二列还是(共6层得形式,所以还的替换一次" K; {' H- d# |7 R
    df=pd.concat([df,df2],axis=1).convert_dtypes()  # 新增列拼接在后面,再次转为Nullable类型
    5 Q! }& q' J7 d: ~df['Highest']=df['Highest'].str.replace('\D+','',regex=True).astype('Int64')              
    . y/ |5 y7 R* |/ B  e* Jdf=df[['Level','Highest','year','area','price']]
    4 c3 e$ e3 u2 O# @& m/ Qdf.head()
    " G; i" W+ Q* ?4 c
    0 b4 d2 T' b, g1 Y2 n# ^   Level  Highest        year        area        price
    4 f8 a& y( d# @+ N( R5 T  {0        高层                6                1986        58.23㎡        155万7 I( ^% e$ r# o3 C2 `+ [, Y+ i+ C
    1        中层                20                2020        88㎡        155万: q8 k& Q; d4 K# w1 W2 @9 _9 a
    2        低层                28                2010        89.33㎡        365万
    1 X6 m, ^, x5 ]5 H3        低层                20                2014        82㎡        308万
    8 l$ j- o: V" L% E+ v, @. ?8 O4        高层                1                2015        98㎡        117万' M9 U$ D3 T. q7 v! r* z" T
    1
    4 A: z$ o* L; }* D8 p2
      K5 j% P5 l) U  k& G, Z  O3
    7 z& A0 r. B/ `0 z: G) T9 V47 i+ `8 l* l5 ~: M% Y
    5
    # i# [7 h* Y+ G$ f67 t  M7 i; F2 Z5 j
    7, f( ^' X3 a; |5 j5 |
    8) P, T/ L7 b( Y3 g& K- }
    9
    ) l/ i( _( t, N10
    $ {3 S, _4 f$ _: M( ^/ X: D- }* M112 S/ W" a* c5 `, ]4 a
    12
    5 E. M% Q( G0 c- {6 @- {13
    5 o: H; Z. D+ `4 e  k7 z9 z# 参考答案。感觉是第二个字段加了中文的()可以准备匹配出数字,但是不好直接命令子组了
    : E7 N( [2 r$ cpat = '(\w层)(共(\d+)层)'
    5 f7 g/ f' i; P) U5 snew_cols = df.floor.str.extract(pat).rename(, V5 K4 h4 h) e& Q: Q/ a' }
                        columns={0:'Level', 1:'Highest'})3 U, l0 [1 o: p0 g

    9 u1 B# d- Q  t' G: E) t# udf = pd.concat([df.drop(columns=['floor']), new_cols], 1)7 z( H9 v7 `+ f1 t& B
    df.head(3)
    0 K+ U( ]8 J$ z  @( W
    : D# j& k& M5 z- c, Y7 A8 zOut[163]:
    ' a1 n- l5 \; S$ b' H   year    area price    Level Highest  Z  f3 G% i, E
    0  1986  58.23㎡  155万    高层       60 A: {; @# p9 H4 c! p' O1 W
    1  2020     88㎡  155万    中层      20; G! @/ t) X7 Z% N
    2  2010  89.33㎡  365万    低层      28
    # a+ t# g5 _9 e% L1* a5 m- h8 s, h4 F" }  h
    2+ \4 ]/ K) ?6 ~+ d5 N" ?6 a
    3: Y, `1 A# R" }5 w5 }
    4- H% q+ N4 y, n$ _9 x- L# y
    57 {! B. N. N8 ]: _
    6
    8 A: V$ N6 s$ V5 ?9 X$ Q1 }6 d7! M: M! }) s  P7 q
    8
    % X5 b% Z" }9 H( e9
    7 a5 b& t) x4 t# s. ]4 J6 ^10: l, D: O( y+ y5 L7 c
    11- F9 S# _; [8 k$ z2 {# R8 A1 O& L
    121 m% D" N! R0 X- m( g
    13
    3 Z! X% \/ N% C; n$ N3 F计算房屋每平米的均价avg_price,以***元/平米的格式存储到表中,其中***为整数。
    5 L4 Z% A8 J0 X8 h6 `* W"""
      n: C# H& M8 F' b0 b- e9 ystr.findall返回的结果都是列表,只能用apply取值去掉列表形式
    " z0 b( B" F5 Y# o  F) G3 D3 ]参考答案用pd.to_numeric(df.area.str[:-1])更简洁
    ( Y, p. j$ g; b, }& k由于area和price都没有缺失值,所以可以直接转类型
    9 d6 W  k7 S4 g" G3 E9 g. L, ["""
    ( R% G) w% s5 f. ~; C2 Fdf['new_area']=df['area'].str.findall(r'\d+.\d+|\d+').apply(lambda x:float(x[0]))
    : v. s" H9 O& t, ndf['new_price']=df['price'].str.replace('\D+','',regex=True).astype('int64')6 r: l5 _7 q3 N, }, e# W/ ]
    df.eval('avg_price=10000*new_price/new_area',inplace=True)7 |% \7 Q1 Z# `( A8 V/ V
    # 最后均价这一列小数转整型直接用.astype('int')就行,我还准备.apply(lambda x:int(round(x,0))); K: ~, A' r. l# e9 {- a
    # 最后数字+元/平米写法更简单
    & v+ l; C9 x$ J. F2 k; l6 _df['avg_price']=df['avg_price'].astype('int').astype('string')+'元/平米'
    / j. D6 f4 q" X6 x  W0 p9 Fdel df['new_area'],df['new_price']
    % D% p% k# V' f* ?4 d* adf.head()' Q  e$ L9 ?5 a$ z1 `

    ( c  w; n* l/ y   Level        Highest        year        area        price        avg_price
    1 I/ z) v. f  o) @& q% Z0        高层                        6        1986        58.23㎡        155万        26618元/平米
    ; K& n4 [, `4 d7 h; `5 J; [: f/ N' s1        中层                        20        2020        88㎡        155万        17613元/平米
    " S& f. M* k: t* W9 Q1 d; ^3 D2        低层                        28        2010        89.33㎡        365万        40859元/平米
    7 p& C1 c& d4 `6 b3        低层                        20        2014        82㎡        308万        37560元/平米- J, Q% l" j( _6 c2 p
    4        高层                        1        2015        98㎡        117万        11938元/平米' N* p6 R0 ]# Y6 i4 r/ x: |) K

    $ g  O7 {  Y0 e% ?- c1
    6 i3 [8 @9 i& C! k' y, P! f- }" [2) c( x% N" j- s
    32 B4 L3 R; o6 u5 o+ u- r4 C
    4- R/ z; q8 k' \- B- N( h/ d9 ?+ C
    5) @7 K& F* `- Y/ H% G1 _# A! h7 s
    67 C! [0 R1 `6 I. Q' D5 z5 G
    7+ {4 B2 u5 a# j: s6 D
    8
    + M- z4 q7 x6 c9 V( B) |, }  a9
    ' Q8 M5 m! x  y5 e7 {: K108 c* G' u, q; J) Y' {' G$ Q
    114 n! w5 _, v- s% b. C' v
    12
    ; w8 X5 }% n( o3 E8 v$ x13
    6 ?" c! q$ i- a4 v4 L# O. D- P14  f4 [! t" }- f# b! j
    15
    , m" n( C* V( N$ y6 A16
    3 u' Z5 Z8 T' D" D17
    4 \: @0 s- X) Z. Z: Q18
    8 @$ ]. B7 y* i) ]19
    / T! |4 w* V1 z8 U% A4 k! K207 v' `" k& u* I4 g# r# o9 M  @/ o
    # 参考答案
    ; w' y: h. K1 Us_area = pd.to_numeric(df.area.str[:-1])
    1 d% {1 l. G+ u7 Xs_price = pd.to_numeric(df.price.str[:-1])
    4 l. n- k7 e% h( Fdf['avg_price'] = ((s_price/s_area)*10000).astype($ p( q$ I% T4 l9 b& b
                        'int').astype('string') + '元/平米': r% \  A: L: ?3 p- N

    + C6 |+ i) _( p% jdf.head(3)5 V  U6 p% p$ }) E( D* S! F
    Out[167]: , y* \$ H0 D; j* I; \
       year    area   price   Level Highest  avg_price* J2 o7 i. N$ G# R
    0  1986  58.23㎡  155万    高层     6          26618元/平米
    $ L: I& b9 @+ c( [5 W3 L% {' m1  2020     88㎡  155万    中层     20          17613元/平米1 m$ r. d0 [3 m5 ^% z+ [4 |0 @0 y
    2  2010  89.33㎡  365万    低层     28          40859元/平米
    5 u% Y6 N6 ^' S0 Y14 x4 c8 Z: v3 t6 |% K
    2
    6 k2 _. y% {( _) Q31 n$ T; C3 A5 e" m% F) n
    4  D) [7 |6 c& Z8 A+ u$ j- ?) h' s
    5% g2 C; B1 Z* {2 i
    60 F/ r: I1 }5 a0 L$ ?8 C% ?
    7
    6 u+ A3 d4 c5 G; `, d1 l! D  b8. a8 F% g. @9 g1 q- h3 a
    9
    ; a! u5 i# d6 c# y9 b101 \& }$ [6 U' y& q
    11
    0 C, E5 d0 r" H2 Z- u8 Q12
    + Z9 K( H2 L6 n. |+ _Ex2:《权力的游戏》剧本数据集
    ) j" g# ^0 B$ s' q, u* C现有一份权力的游戏剧本数据集如下:
    - S7 z+ I5 c" |( u! |. @  m& g9 P0 [" W' @1 l; w
    df = pd.read_csv('../data/script.csv')
    ) R" p1 a* V5 ^3 S& J7 c* t3 Q$ p% Mdf.head(3). p& @% S6 Q1 {6 g
    6 ?- i/ J( N: ~' l: e/ }' }$ Q5 c3 u  u
    Out[115]: . h* g/ R. Z1 @# h$ B; ]) i& F
    Out[117]: % t0 y% w( r; H+ b+ F
      Release Date    Season   Episode      Episode Title          Name                                           Sentence/ P# Q# e- O# }* C+ V/ A
    0   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce  What do you expect? They're savages. One lot s...
    4 K  J" z$ D2 m6 |0 ]& O; O- `; `$ N( U1   2011-04-17  Season 1  Episode 1  Winter is Coming          will  I've never seen wildlings do a thing like this...8 E  k: B! z/ @! D
    2   2011-04-17  Season 1  Episode 1  Winter is Coming  waymar royce
    / Z. ~& G$ N: K$ o; U/ [1
    : j- {0 C0 M/ ?* J1 s: j; S0 W+ n22 `; T$ [( T1 [/ I0 W- x1 `. H
    3
    4 D0 f! g4 ]# `; u4
    ( v+ x$ e; g5 F7 }51 y  g( D+ x; u; L; f
    6
    ' y# x. e9 J$ L, B7
    1 V. l- b  Y( l) z8& X; W  {) D. ^: e% d
    92 x, G$ T+ `; O3 Y; A/ Y
    计算每一个Episode的台词条数。4 _& S! X' h9 I( B" i
    以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。! x0 e$ ^: _- P
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有 &#119899; 个问号,则认为回答者回答了 &#119899; 个问题,请求出回答最多问题的前五个人。( j& `7 C( Z& c$ e
    计算每一个Episode的台词条数。' ~; q5 F; o7 u; v
    df.columns =df.columns.str.strip() #  列名中有空格
    : F% e7 p4 f( H: _) X  o5 g7 G8 V2 O" ]6 Hdf.groupby(['Season','Episode'])['Sentence'].count().sort_values(ascending=False).head()
    6 s; ~& Q( X2 V, \- o
    - V' ?: }, j7 U1 ^4 r! A( n+ pseason    Episode  3 E* D4 `1 d: G, U  h: Q9 a" F) O
    Season 7  Episode 5    5057 v  y& Y# }8 r8 j# F2 \3 P! W
    Season 3  Episode 2    480
    : F/ h- E5 d: s$ m; t& sSeason 4  Episode 1    475
    + K# r8 n' m2 a/ JSeason 3  Episode 5    440
    6 b. t. w9 b+ k% _Season 2  Episode 2    432
    & n' Z% E7 i) U8 Y- \4 r* S1# B; `. c4 L7 P1 k% ?6 l; V/ Z
    21 v2 ~$ b" M- G: M' ]  [
    3
    3 e2 W5 W! o, l6 |+ R3 _4" A2 ~1 W" H1 g7 @- S6 C. \
    5+ {2 F* y- ^0 Y4 t9 ~
    6) |1 l" U$ p* X+ _; P1 Y9 w
    7' i# W' Q* Z. w& f/ e. [# ~+ V
    8( x% H' i4 l; {/ V$ l
    9
    / |  ?' r9 T0 R8 a; p" r以空格为单词的分割符号,请求出单句台词平均单词量最多的前五个人。
    1 ^% G" T9 n4 L6 o4 ]# str.count是可以计算每个字符串被正则匹配了多少次,+1就是单词数
    $ E* _% s. f  [4 i9 edf['len_words']=df['Sentence'].str.count(r' ')+1
    , b0 B0 s4 X0 j% H" d1 Fdf.groupby(['Name'])['len_words'].mean().sort_values(ascending=False).head()( Y" T! J3 k( n

    " I, k) d: p3 X: XName4 j: l2 g, W* T8 z* d
    male singer          109.000000
    % k' g+ t0 R/ tslave owner           77.000000
    ' |* I9 V( K6 [3 H/ z4 N: tmanderly              62.0000003 q+ ~" W& ^. R. G: T7 W8 e
    lollys stokeworth     62.000000
    : ~  }( k% |* `, m  Odothraki matron       56.666667
    % \: ~1 w3 w- f% }& x, M; AName: len_words, dtype: float64
    * x3 _4 K4 q2 a8 ]( V# ?1
    2 |, Z$ B1 Q5 g& d  M% w, D2
    $ c1 r) [; J+ v+ }3  M* z2 g: O. c' f) e* s
    4: E! G0 G/ H5 H2 u0 ]) m
    5& ~& O0 H; a  x9 w6 P- p" G. L
    68 s/ }6 q8 w) h4 q' Y6 ?& T
    77 Q8 u! e8 _$ f6 @. T, S2 {
    89 i! f( h$ V6 R+ {
    9
    - i* Y, R% S2 S% p- E, P106 }& K/ A) h) m7 ?
    112 u0 `/ U) o1 v
    若某人的台词中含有问号,那么下一个说台词的人即为回答者。若上一人台词中含有n nn个问号,则认为回答者回答了n nn个问题,请求出回答最多问题的前五个人。9 }( x/ o) Y* b1 q; W" N
    df['Sentence'].str.count(r'\?') #  计算每人提问数
    . z% c9 f( d) C& ^! w4 N- Als=pd.concat([pd.Series(0),ls]).reset_index(drop=True)# 首行填0
    2 h# }& C) o% X, v! T! _6 ^/ A5 Ddel ls[23911] # 末行删去
    % \6 Y* e+ u9 [5 Bdf['len_questions']=ls# v1 k2 L: H$ |7 n' _' H
    df.groupby(['Name'])['len_questions'].sum().sort_values(ascending=False).head()
    5 k" s# n7 e2 E! S8 t! K) a, `
    & |. H; G% ?# C& ]. l7 oName+ Q% q8 n- N' J3 A( F
    tyrion lannister    527
    % h# l1 z0 A" X8 D% n3 ^jon snow            374
    ' r7 _1 |1 _: M. O7 wjaime lannister     283" Q$ k4 J+ p, q+ j- N& j; n
    arya stark          265
    - ]$ M$ g& ]/ ?1 t" a9 f1 B& bcersei lannister    246
    : o+ t7 ?1 W5 c, a) x+ ^" |Name: len_questions, dtype: int644 d8 W- Z; P8 \! E% ?
    5 C6 V! ?1 N4 U
    # 参考答案
    5 H- M( q6 z9 v4 C- Xs = pd.Series(df.Sentence.values, index=df.Name.shift(-1))( e( s/ B5 v/ [( m) O1 k, v% j
    s.str.count('\?').groupby('Name').sum().sort_values(ascending=False).head()1 l% _' h, V  t* ^5 z* q
    : Q  ]* @, {( E2 ~8 A: c2 s2 G: i
    15 D& a$ e& H$ u3 D' W9 e! }! V8 O, f# a
    2
    : |2 z: @9 R# J( b3! W: \" j' O2 S6 E  G* r& g7 D
    4! C$ o: I' _& x8 n/ G) f
    56 l: Y5 M% Z5 z$ t- d% l
    69 N6 h: z0 |$ b  f) z% j$ K
    7
    8 D, @4 j& o6 a9 P- n8
    / m- e2 n( Y7 ~2 O3 B- y, ^; U9- A7 ?0 [- r0 \$ K! P: W6 D( o! b
    10
    : L1 L% R( P- C9 u9 V; q4 v& m11% U3 ]. ?( A) ?! ^* ]) T* J4 b
    12
    # x; S* ?6 u; q  T4 `: F13# t8 R% Y! I. S: Q( ^
    146 g1 j, z0 u0 C; C" w
    159 J( _2 P" Q% g! U: s
    161 S' [5 S+ c9 l; g5 v+ z# v1 u
    17* v; e! N5 k; S% D  N. p
    第九章 分类数据
    8 |+ e' o! C3 R2 O" eimport numpy as np
      X' S+ c! O; Pimport pandas as pd
    - }9 {$ d  y6 C+ b# k0 R1
    8 s) R6 G7 N8 f% {, |; I2
    ( N: ~, r( `' r8 S2 V8 [9.1 cat对象
    1 W9 y) U3 ^' \  `1 y% a9.1.1 cat对象的属性) W! J. v  T9 l+ F9 w
      在pandas中提供了category类型,使用户能够处理分类类型的变量,将一个普通序列转换成分类变量可以使用astype方法。- p; W/ S) R# D2 M, s

    # a' ~3 Q9 X( V% fdf = pd.read_csv('data/learn_pandas.csv',' ~+ X2 w+ K3 n2 [' ]
         usecols = ['Grade', 'Name', 'Gender', 'Height', 'Weight'])6 A0 R& b9 K6 v0 f9 O- i1 A
    s = df.Grade.astype('category')3 A& _, q3 K& f9 _
    3 w: G+ N& N* h6 w5 j: ?( j% h
    s.head()
      _! m7 |+ f5 y2 kOut[5]: 4 l( h& L6 Z! G3 {: c
    0     Freshman
    5 c2 G9 j" a8 w3 W1     Freshman' k( K- n6 t  B
    2       Senior5 U! f& c$ Y" P
    3    Sophomore
    8 C" U# j$ |: b0 L4    Sophomore, Z! E4 m0 T; l" ?/ ^
    Name: Grade, dtype: category" v: w3 I: s; g) t9 k  u' N. @+ P
    Categories (4, object): ['Freshman', 'Junior', 'Senior', 'Sophomore']
    % t6 a  c! Q$ J17 D* j4 Q; \2 K
    2
    5 j* F; m! b% j+ _2 E3  I7 T. v: Z0 O" T" c! l6 f
    4
      W/ r. A/ g$ U1 X1 _$ D5
    3 I0 H6 ]# v* c$ L4 }6( Z9 A, S' _4 g: e
    7; t+ s0 ?# E& Q' C
    8" r0 v- I' s7 L6 u5 a0 Q1 X
    9  O9 A; w- O+ w- W; n3 [2 P5 W
    10
    7 O; l7 t9 d/ @0 S$ P1 z0 W$ k$ M11
    ! ^/ z7 v) ?4 y' S  z7 [8 U12# b  a  t+ ^% Q* J" U  M4 d; Y! b
    13
    - S0 T# z8 Y% S. N; r  在一个分类类型的Series中定义了cat对象,它和上一章中介绍的str对象类似,定义了一些属性和方法来进行分类类别的操作。4 A, p, D0 e7 y+ N+ C5 j& I

    5 N" j( r# b$ S% P% k+ vs.cat
    - A, Z( `! L3 G, p1 WOut[6]: <pandas.core.arrays.categorical.CategoricalAccessor object at 0x000002B7974C20A0>& l( c$ w, m1 _4 A1 B- ]
    1
    ! S5 \* G( o5 A2
    + g; t- S2 C4 i6 ~& Z% M$ T- Ccat的属性:
    , \9 ?8 _7 N7 v2 K' W& h
    : K5 V% H: }. A# pcat.categories:查看类别的本身,它以Index类型存储1 f5 @2 |% H1 ?
    cat.ordered:类别是否有序
    . f) j0 \1 c! V0 r5 T( ~8 y- bcat.codes:访问类别编号。每一个序列的类别会被赋予唯一的整数编号,它们的编号取决于cat.categories中的顺序  ^/ o( w9 H% ?* X
    s.cat.categories
    3 [* P! ?' n3 U$ nOut[7]: Index(['Freshman', 'Junior', 'Senior', 'Sophomore'], dtype='object')
    & e8 a4 u" W& t. U4 [0 E- a
    9 Q( i% `1 y8 L4 F0 L: e' ts.cat.ordered
    6 a  c) A) l6 D: dOut[8]: False
    ! h( I, s# I: ~) ?$ R2 n0 P
    & f3 R0 ^$ Y8 o; d$ o) zs.cat.codes.head()8 T- H4 z* x2 ~( c
    Out[9]: ! p, J6 g9 P* M. D1 q) U
    0    0! i  e! D6 v+ G
    1    09 N2 O+ R- r2 |. R$ D! k
    2    2% \4 ?$ L6 f) g2 O$ P0 H9 @2 k
    3    3% R% M" ?+ |2 H; O: _: P
    4    3
    8 F" D& w* g, k$ M2 gdtype: int8
    1 q+ s/ ~) I; R! {1
    # y) v: l# o6 b" c+ z# G" N) D2
    $ P/ f* R: A4 F" o% D# b. }3) H( J3 \9 N: x) ?
    4
    - X" \0 L0 a- q9 T7 _5  U7 q, B3 f/ c# u+ G% m
    6
    * g4 N  |+ M; @+ w7
    1 K- l' A4 j) Z/ f- S8" x+ }) t' n- e% Y6 V4 z
    9! {% u: b% J$ H6 h: v7 z: V# ?
    10
    2 E% ]: L% @4 m; W$ @11
    0 W( \6 q8 @' D; @12
    ' a( ~8 }  }  g- a/ `7 X  v* V) {13* l6 S6 l" c: r
    14+ ]5 U6 s) I* F5 X/ Z7 y6 A1 q0 I
    9.1.2 类别的增加、删除和修改
    / H1 \4 `7 W5 K4 S6 k; Z  通过cat对象的categories属性能够完成对类别的查询,那么应该如何进行“增改查删”的其他三个操作呢?8 T  K7 Z1 l( w  [+ t, C. S% p
    9 S8 D. a  S; i: O
    【NOTE】类别不得直接修改
    2 o  {* X( ?, V4 y7 }% z在第三章中曾提到,索引 Index 类型是无法用 index_obj[0] = item 来修改的,而 categories 被存储在 Index 中,因此 pandas 在 cat 属性上定义了若干方法来达到相同的目的。
    $ e. f0 i' |# Y5 Z; h$ I2 H/ z6 l4 l* Z: @" s) }
    add_categories:增加类别
    3 g- d$ \  s# s" [1 ~! cs = s.cat.add_categories('Graduate') # 增加一个毕业生类别0 T' ~; P6 [1 K
    s.cat.categories
      E* f; D5 s/ ]( B. e0 f% U6 x! {* a, L
    Index(['Freshman', 'Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')
    # u) \* z- R3 o, S1 s1
    + R1 t, e2 t$ @1 Q2' `( f* H& F$ s5 t! `6 c% q" `
    3+ h3 T7 `: }8 Q! z
    4; _- w9 {* I3 Y7 A$ }: g/ Z" r8 {
    remove_categories:删除类别。同时所有原来序列中的该类会被设置为缺失。
    3 h  L) F6 `9 _8 F- c! G6 A6 ws = s.cat.remove_categories('Freshman')1 T( [' i. l3 Y# h$ v  T! h

    3 J( P* G* P# q  m/ ?$ as.cat.categories/ b: a& N1 I1 j0 \1 ]9 Y8 p
    Out[13]: Index(['Junior', 'Senior', 'Sophomore', 'Graduate'], dtype='object')1 c# C& Z3 J& \) s
    " O( ~( \( a5 K7 h% ^) G; K6 x
    s.head()
    / ]; s" O. V& K; U# O4 U7 xOut[14]:
    6 j/ q6 c5 ^. E( f0          NaN
    # w  J9 O$ j) g! D* H1          NaN/ [. k2 O9 k. \
    2       Senior" `( H  Y% ]' n4 r! s; T( E
    3    Sophomore8 x6 y. z9 F' ^6 q( a( k
    4    Sophomore
    1 V1 K3 ]! b9 C) M& }* @7 @: GName: Grade, dtype: category
    / u" I$ |! f  m9 o  YCategories (4, object): ['Junior', 'Senior', 'Sophomore', 'Graduate']+ y8 ]' x& y7 v% e% _4 J1 F
    1
    % O+ i! |8 _" ]21 {$ O3 l' m, _) B5 V
    3
    " |  c/ `( i' k% }4
    & ], k# Y2 [4 P- P5
      r% L% ~) {% T% \. n  @68 v" I9 m& z' g3 R
    75 b+ H6 |1 K( @  O# ?1 G" ?0 r
    8
    * c5 ]0 _1 |5 B! Y% t$ \9" O$ J4 W2 I9 @* u( o3 a8 B
    10! Z; u9 ~' u0 {+ c# \- o
    11
    4 ~; N1 A& J$ I! K12
    ) k2 L4 t* m' ^137 k; Z' [* n/ ]9 \! g+ C
    14
    - B/ Z4 a2 G5 O* _" }8 V" t4 x5 Qset_categories:直接设置序列的新类别,原来的类别中如果存在元素不属于新类别,那么会被设置为缺失。相当于索引重设。
    ! `9 R" L3 V6 s$ [' V% Ys = s.cat.set_categories(['Sophomore','PhD']) # 新类别为大二学生和博士# i( @% w2 e# p* T5 X) M
    s.cat.categories
    & L% c* B$ v- h8 w7 O8 t; T0 TOut[16]: Index(['Sophomore', 'PhD'], dtype='object')
    4 _$ T+ W' W& F1 Q
    8 H1 C7 e9 Y9 E% y! j: j- t+ l$ n0 os.head()
    / c$ T2 n0 I0 |5 l. `/ kOut[17]: ' N0 y1 W% D. H, {4 y/ I! w
    0          NaN
    0 x4 e  n# ^, f; Y& `+ W( o. C) }  _1          NaN. g9 N4 l8 |! f' q
    2          NaN& ~5 A9 Y1 P/ |+ G1 k
    3    Sophomore2 J: j: a5 N$ N) Y
    4    Sophomore
    . i; }2 j9 _& D) v5 PName: Grade, dtype: category
    * t* L4 }8 G# O- d: T9 s2 uCategories (2, object): ['Sophomore', 'PhD']
    $ i' `* f8 L# E3 A7 Z5 \0 ~& {1! w6 [; J5 u) d; Q7 {
    2; \% W# B( m3 i
    3
    5 o1 J  E- E9 Q2 K45 y4 c' _6 w$ u# d# o5 Q
    5% ?. u. V4 E4 P5 C! O; ^
    6& h. a; i; u3 x; O5 l1 M2 c6 Z
    7
    6 j% H5 @+ Y7 z; ~89 z; }  T( [# i4 J
    9
    8 l1 T5 `- V$ o8 t7 ]104 |. Z9 W& d/ _0 d1 y0 n
    11- o' D7 R0 A+ ^. a2 I4 o3 C
    12
    5 D( g, l0 m" H/ [5 U* Y13; M% ]6 V2 X( c' q8 b& H- d. i
    remove_unused_categories:删除未出现在序列中的类别
    ! Q( I9 H& c( E" a* Is = s.cat.remove_unused_categories() # 移除了未出现的博士生类别
    1 v& c$ \- x' O; ps.cat.categories1 s% g% S( U3 ?

    # W" r0 v9 P1 s# C7 hIndex(['Sophomore'], dtype='object'): M5 m. ]! O$ J' {. I; c/ ~- Q( L
    1
    $ b. x& m/ B2 \4 q6 ^9 n+ I( @/ p27 T8 o7 L4 L5 f2 t7 c4 r
    3+ k* u& k* |7 G! X; E4 H
    4
    1 ]. J+ L' z3 Erename_categories:修改序列的类别。注意,这个方法会对原序列的对应值也进行相应修改。例如,现在把Sophomore改成中文的本科二年级学生:
    ! e2 X% b2 n; B9 _s = s.cat.rename_categories({'Sophomore':'本科二年级学生'})
      {% F% P. P0 x- vs.head()
    7 J. H. d- v3 \. k/ M# ?; ~) \# o( z) G; a( T( j3 P/ E+ h, {4 y
    0        NaN2 q! X7 V: d5 {6 m& K; C; Y1 q, c
    1        NaN
    * ^% h) S4 o6 z9 w' A  F2        NaN
    8 q$ y; H7 @; z/ H& e& \3    本科二年级学生
    / d& ?* q' B) }4    本科二年级学生
    / I7 d3 F) G9 C  ^4 `# o. d1 d  eName: Grade, dtype: category
    + `; Q  H8 L' vCategories (1, object): ['本科二年级学生']+ d0 j& }, C8 I8 v) D/ k' r: \; [
    16 B% R% b* `4 G% w
    2
    , ?' _( [# N- ?5 q. v, [1 z. A3
    # H2 z+ {- G7 I7 j4! Y# d& ]$ {" t
    53 o; A; f4 X5 f1 L) ^
    68 J- E+ M2 G! ]. X
    7
    - \+ i- [. m# K. I$ M! V# N8
    # w- |1 \# H3 C$ u9
    4 q# c: G8 C7 b1 j10
    * B7 J9 f# b4 {. a9.2 有序分类
    % W7 d5 W& Q7 t. d9.2.1 序的建立: H! e- m6 W0 _& |, n; N
      有序类别和无序类别可以通过as_unordered和reorder_categories互相转化。reorder_categories传入的参数必须是由当前序列的无序类别构成的列表,不能够新增或减少原先的类别,且必须指定参数ordered=True,否则方法无效。例如,对年级高低进行相对大小的类别划分,然后再恢复无序状态:; O4 w2 ?% N( U

    1 @% f" N: u" r, U! w  ks = df.Grade.astype('category')2 ]; F) ^9 T: {5 O1 M
    s = s.cat.reorder_categories(['Freshman', 'Sophomore',
    & N- m+ o1 Y0 p& K5 i                              'Junior', 'Senior'],ordered=True)
    + F! J. V% p/ d8 ?) W6 X" j8 U2 Zs.head()5 P  S4 m/ r: d1 u+ m
    Out[24]:
    * `+ f6 c" t& c6 i) ~2 R1 m0     Freshman$ I3 K, E9 x' G+ l' c
    1     Freshman
    ) D! o3 N2 i4 b2       Senior( N- Z2 }9 [& a& R, [
    3    Sophomore7 n2 ^6 v) G: R! b8 M
    4    Sophomore
    ; t: O/ `3 b" J& f6 `- l$ G: b7 PName: Grade, dtype: category6 X6 n/ W/ ~. X* z8 R
    Categories (4, object): ['Freshman' < 'Sophomore' < 'Junior' < 'Senior']
    " \4 h+ @0 v' R5 ]) {
    " _7 e3 F- i8 P0 [5 y6 R& Es.cat.as_unordered().head()
    / M- ~' ?, O& m7 H1 E% h4 HOut[25]: " j& i1 z5 t5 d4 O' t0 Q! d, g
    0     Freshman
    $ j' e- W, @- ?) R' p1     Freshman. O' o) k4 U* U2 @0 ~' E% {2 U  p
    2       Senior+ {' {3 A6 z( g! C
    3    Sophomore% }, U! J; r" ~2 z$ d3 I2 _6 s
    4    Sophomore
    & O* T" D+ h% H4 S$ I# DName: Grade, dtype: category; q/ L" G+ x$ _
    Categories (4, object): ['Freshman', 'Sophomore', 'Junior', 'Senior']
    / d+ K1 Y! Q- s( z0 G
    " y" W2 z  Y" Z% b6 g( a9 A, G' i1) }5 i2 W/ T  V  r2 Y/ R! x
    24 x) |& o8 L* g( `
    3
    " G+ R8 d0 e1 n4" V! r" Y/ A/ F7 d# A
    5  e# \0 T; f0 o- r3 L( N# M
    6
    , ]$ c% N2 C* n! u6 |: M  P2 q7
    2 ]7 C/ i; H  |$ f" d8
    - i3 B7 D' a6 ]5 C9, v; Q, F* d5 f1 m- J+ J- g. L
    10! f/ ]$ f4 B8 \: M/ u
    117 W/ S! M* ]. S7 x
    12
    / @1 m2 e9 m! ~) q" G13
    5 a- F; ]8 N+ b, k& ]. K+ I$ L14
    : @" L$ A5 I5 B8 f# R0 d) n15" f* q+ ^, F/ |6 i7 z, c4 X" G
    16  S! [& l* Q+ U
    17
    3 G, e8 a5 k5 I6 x3 Z  L- ?18
    & L: ]2 C0 }6 \6 S19  o9 W3 M7 V! M5 n& J) S5 r
    20; J, L& _( K; t! L
    21
    8 L3 _( F2 W- V8 O1 I% W8 k6 F22
    . X# l0 D  b( ?) c7 D  如果不想指定ordered=True参数,那么可以先用s.cat.as_ordered()转化为有序类别,再利用reorder_categories进行具体的相对大小调整。
    3 e! y! U+ I1 K) N2 o7 o$ I/ V
    # v$ |, q. J  r3 u# Y! h7 X9.2.2 排序和比较
    2 z; B0 x0 \: f+ h' N# H+ b在第二章中,曾提到了字符串和数值类型序列的排序。前者按照字母顺序排序,后者按照数值大小排序。6 h! }- N4 `8 Z1 d, v1 ^) t/ t
    7 W7 J: B& ]1 H8 A/ }7 P
      分类变量排序,只需把列的类型修改为category后,再赋予相应的大小关系,就能正常地使用sort_index和sort_values。例如,对年级进行排序:
    6 h: v3 I5 J5 K, ~; P0 e! {( h2 s# V. H% d
    df.Grade = df.Grade.astype('category')# w# E( @( L8 e0 n  Z
    df.Grade = df.Grade.cat.reorder_categories(['Freshman', 'Sophomore', 'Junior', 'Senior'],ordered=True)
    + I* J# A1 G$ V  Wdf.sort_values('Grade').head() # 值排序9 ~& F7 T0 X$ m! ], p5 T$ ?( p2 I1 ]( o
    Out[28]: ' g0 ^1 E& u* P, @+ T" E
            Grade           Name  Gender  Height  Weight
    0 T4 \$ |2 w2 _$ v( v, q0    Freshman   Gaopeng Yang  Female   158.9    46.02 {! ]. z- I, R# B6 p2 ~8 u
    105  Freshman      Qiang Shi  Female   164.5    52.0- u) Y" H4 l& A  a. P, Q
    96   Freshman  Changmei Feng  Female   163.8    56.02 ?. Z* h8 `2 X. w7 Q6 w) q5 J" _
    88   Freshman   Xiaopeng Han  Female   164.1    53.0
    + \) B$ C2 |; Q3 i4 h: i; N81   Freshman    Yanli Zhang  Female   165.1    52.0" `- I! i9 ~% |3 M& `

    4 b( I: l( q' Bdf.set_index('Grade').sort_index().head() # 索引排序
    ! ?( s$ [* r9 ]Out[29]:
    , f( ^4 j  g2 n$ Y9 z) a                   Name  Gender  Height  Weight
    # `/ i2 R! E. t$ E. }4 {Grade                                          
    ( O. R1 s8 u) wFreshman   Gaopeng Yang  Female   158.9    46.0
    , ]: Z4 t( T: Z9 m  P9 Z, BFreshman      Qiang Shi  Female   164.5    52.0
    5 Z5 @/ R/ L* c( j& nFreshman  Changmei Feng  Female   163.8    56.0
    ! q% |" k! K0 Q) Y  E4 eFreshman   Xiaopeng Han  Female   164.1    53.05 m( T1 C, m6 H! C2 a  T3 G
    Freshman    Yanli Zhang  Female   165.1    52.0& ~. m! `. z. r2 c$ q( T

    % P( r2 p9 j- N0 E1
    3 J4 ^5 W+ S8 g. E- A. s2- a' s6 F/ y0 l& {
    3, K9 a4 x( q9 Z5 Q: C' l  d
    4
    8 [- X9 \2 S1 g6 `: J+ h+ N  x$ H5
    % i, t' _2 R% }# `6
      z$ M. a: d# S% L& p' f7
    & K* {; I- r9 n2 X8 o' z4 I8
    5 t4 M) P# {% t5 f8 k: B9
    $ Z$ M+ o# W. i) _. u7 b! [10( ^# o, R' o  q: F1 A
    11
    ) A2 N8 |1 s% R8 Q3 J# T12! O% A& n% V* T0 X2 i  F
    13
    . l4 ?; S  _( P$ d3 @( l2 F: |14
    $ {" t) j% A$ F. l' P. D) |15
    ; O( F- u4 \1 y$ ^2 P8 h16
    $ ]+ ^  E6 B, I! d' e) e! e17
    $ r' k; [1 t& ~( j& ]: s( L181 m0 c# {0 H' P" Z" a# I
    19
    * v3 o8 J% P/ H: L: G! N& {& d20  A5 T3 b$ Z# h% w3 z) R+ [
      由于序的建立,因此就可以进行比较操作,方便后续索引操作。分类变量的比较操作分为两类:
    / U0 c; D: r3 w0 L9 Y
    - q4 ]' g+ o4 J7 Y8 f==或!=关系的比较,比较的对象可以是标量或者同长度的Series(或list)。(无序时也可以比较)& o- u# P' _6 H: q6 P. J
    >,>=,<,<=四类大小关系的比较,比较的对象和第一种类似,但是所有参与比较的元素必须属于原序列的categories,同时要和原序列具有相同的索引。2 D1 G4 t/ f$ A! O4 P& f
    res1 = df.Grade == 'Sophomore'+ u/ a  m" j* x* l

    7 v& E$ a- R9 ], c. Rres1.head()( r6 U/ G9 h) i# o/ a$ u6 C6 e
    Out[31]:
    0 q* v. }6 v) v4 W7 V* t% M4 b9 d0    False* Y+ r- M7 V( ?
    1    False+ B: a: Y& y" p+ o$ \6 {
    2    False
    - t0 i' T( x- X1 ?* ~3     True, t: D3 b, ?4 z. K. A6 H* Z
    4     True1 H% ?( x$ F* P3 z% e
    Name: Grade, dtype: bool$ W, \8 w# l6 v& S

    % F! S" e* P' y2 J& ^! m& Lres2 = df.Grade == ['PhD']*df.shape[0]
    ' h% H4 q* j3 l9 ], c9 r0 g" d, V
    " S* b' ]7 Q1 U9 S9 Bres2.head()
    , v% ]. O6 V# V- `0 k% j7 UOut[33]:
    ! q( d: B6 L6 g$ `' F" s0    False, w2 [: J' Z2 G% s
    1    False0 f/ W. C5 {8 o* a. Y
    2    False, k% U7 P) c; j. P
    3    False' u3 u* f$ O& D) q' b
    4    False$ N/ G- c$ y6 G$ K/ Y
    Name: Grade, dtype: bool
    - b0 a( l; l6 m
    0 i) o+ R3 L$ j( nres3 = df.Grade <= 'Sophomore'
    " z& B% x) A! J; a5 R$ s$ }$ q- a
    res3.head()2 c1 v5 z( I! C8 y/ n: D1 H8 k
    Out[35]:
    6 Q$ @1 U" u5 D1 E+ u0     True
    ( r& d8 b8 a2 B+ W1     True1 X# y$ q& \) h+ z2 X" G6 ]
    2    False) T( |* m$ v( k7 ^* p2 q
    3     True
    9 k( S' t7 }/ h; f0 T0 a4     True# n, J& ~$ T% v1 N1 s
    Name: Grade, dtype: bool6 M5 J# I& l2 g% {7 K

    ( ^0 o# s- b3 `* H- U) Y$ C5 x# sample(frac=1)表示将序列随机打乱。打乱之后索引也是乱序的,直接比较会出错,必须重置索引。
    . I- [+ H  q4 M: \9 b; dres4 = df.Grade <= df.Grade.sample(frac=1).reset_index(drop=True) $ j  r, R# Y* c2 k
    ' C: l8 R; C7 p1 T4 i
    res4.head()
    ; F5 ^, @% n! m! v6 d+ lOut[37]: 3 z9 h  I; s  i5 v3 T$ e* u3 O
    0     True
    : j, q+ ]* G9 c% u, @* o2 Y1     True& }8 j# f$ a. \9 h
    2    False
    * J( S1 n1 f  Q' x" Y9 k3     True
    % ^. k* q. T  l: m& l4     True, h& K  w4 h4 o9 ^6 T) V
    Name: Grade, dtype: bool4 R) N% B, ?) }" p" s

    # d, O6 z! Z5 ~3 R1# n) g4 h8 Z( W: B- q0 v
    27 X4 \9 [$ S3 T: b! M# ^
    3  q4 c1 Y, V% s* H" i( H# m
    4
    , {7 g8 l0 d( c7 m: e5+ Q( X! P: x3 E0 T
    68 w( f, y9 H! u9 g7 P
    7) Z# d: f" e7 u. {0 m! Q/ W
    8
    , K  }- k3 w# V- N, t9: v& x: }% X* U
    107 C6 @1 p) X: z3 K. N6 B
    11
    & E5 W/ Z+ l; J% \- O& h4 ~8 n  ~0 f: f12
    2 H8 \7 k% Y$ w1 O. R13$ Q0 G0 K# k( |, b5 z. C
    14
    5 R% A' J6 w; H+ g  I4 u15  b& R0 Z- o% C, c
    16
    4 g& B( T# v- Y17
    7 W1 {. x$ ~5 K5 `: @1 }" X, n18
    , ^- S) i5 k1 ~  `( E197 S4 {, ?* X0 g; |6 Y7 I" [
    20
    $ [0 I5 J  f, u1 T: D  i21
    ' w6 H5 l( h0 @- G229 w, a+ C2 y  [$ Z# g4 ]- Y' B, p
    23
    ( l2 k+ I7 M6 X$ }4 u1 w% Z24) L- G6 H$ t% r6 N2 Q) t' A
    25: {  U" j1 _) \' U9 i( y
    26
    : D: y, w4 B: ?' a7 B7 }2 d27
    # Q7 t5 j8 t  N286 `# v. H2 e- q" d$ P3 g& r  x/ `, d
    29
    5 b# ]1 _3 `5 v% F+ w30% M! b- s2 K& W; ]% P6 [3 e9 p
    31/ K! I+ V8 K' z# I, U
    326 q7 v; @# Q: O  J) B
    33
    & A8 Z/ `" P# i& W0 B34
    ; @! y2 \- c" ~$ s! P+ V35* Z* |2 j( _. Q0 g9 t: B7 s
    36
    - A: h" x" o& g3 T& J# x4 s37
    ' s0 W6 O+ J1 z' D6 y38
    ) l: w- ^0 ^$ U. u3 X39) L# ?# j7 O0 x1 C- _9 G& k5 I
    40
    ) Z  l* ]1 q! n1 S412 S9 [% y& n% m' L
    42; j8 {' q* n+ b* L# W* B
    43
    1 j7 m+ ^# B5 p7 S5 ^- g443 b! G1 i4 q% W, B
    9.3 区间类别
    1 R; V7 t! t# q, v9 G1 o9.3.1 利用cut和qcut进行区间构造
    & t! Y& O$ @9 ?/ O$ m  区间是一种特殊的类别,在实际数据分析中,区间序列往往是通过cut和qcut方法进行构造的,这两个函数能够把原序列的数值特征进行装箱,即用区间位置来代替原来的具体数值。! Q1 p2 L! F& N% {  Y, h/ p
    # \+ d& g/ ^8 y
    cut函数常用参数有:4 T" l8 T6 k% l' `" B6 @" T8 g
    bins:最重要的参数。2 K, {# B- D0 [' W* p. O
    如果传入整数n,则表示把整个传入数组按照最大和最小值等间距地分为n段。默认right=True,即区间是左开右闭,需要在调整时把最小值包含进去。(在pandas中的解决方案是在值最小的区间左端点再减去0.001*(max-min)。)
    ( k/ ?" P0 t) P+ {! s也可以传入列表,表示按指定区间分割点分割。! k% p  S# F5 \
      如果对序列[1,2]划分为2个箱子时,第一个箱子的范围(0.999,1.5],第二个箱子的范围是(1.5,2]。
    2 ?) Z, O- l  ~; D6 E; v9 c- h  如果需要指定区间为左闭右开,需要把right参数设置为False,相应的区间调整方法是在值最大的区间右端点再加上0.001*(max-min)。
    ; t  ?- v+ x1 C% i3 Q, O
    7 q( S1 P' i+ ~6 b) l! Js = pd.Series([1,2]): N6 _2 g8 o" Q; w. R( z! i
    # bin传入整数! P; c4 `& o/ i
    ! j% y7 ]' G' p0 L( ]4 M" @
    pd.cut(s, bins=2)/ x! H7 m1 K6 c9 F' G
    Out[39]: - q& E' t1 k( d# k
    0    (0.999, 1.5]8 Q0 `) Q3 X; [# a6 y7 w" E
    1      (1.5, 2.0]% {4 T0 @$ t! k- w; E/ H
    dtype: category
    $ _: x% C9 S! W0 K( p" YCategories (2, interval[float64]): [(0.999, 1.5] < (1.5, 2.0]]
    ! ]4 ?0 P7 N  e$ y# A# i0 X: q# o' q7 B3 K% U% ]
    pd.cut(s, bins=2, right=False)
    9 K; G) C8 c: COut[40]:
    9 O4 C4 ~9 q; N' d& b. t7 j0      [1.0, 1.5)
    ' p8 J  y9 B7 y/ T: i1    [1.5, 2.001)" h0 k0 w' S# |. Y, m$ V- f: W% n
    dtype: category$ a% N/ i  g! s6 E) p1 j
    Categories (2, interval[float64]): [[1.0, 1.5) < [1.5, 2.001)]
    " T6 J/ t( `, l! z) W
    # p6 O: L3 j: [  m% L
      P- ^8 A+ k8 J0 l/ y- e3 j5 z1 a# bin传入分割点列表(使用`np.infty`可以表示无穷大):( {/ t+ v8 p7 S2 V& H% w6 J2 B
    pd.cut(s, bins=[-np.infty, 1.2, 1.8, 2.2, np.infty]), \; E- g' u4 b$ I% q& v
    Out[41]: / U+ G# d; }) q4 P# C
    0    (-inf, 1.2]
    " ^1 x3 G1 e9 O6 b9 d5 p) t) R/ I1     (1.8, 2.2]
    2 I3 Z+ V7 O0 K3 H" Tdtype: category: o! E- U6 L  n/ L
    Categories (4, interval[float64]): [(-inf, 1.2] < (1.2, 1.8] < (1.8, 2.2] < (2.2, inf]]; r( `. c) ]8 q) F+ c# k* _. ?% Z
    9 Z; f, m" |. `6 E! c4 |" `5 E
    1# L- c$ |/ q+ m  i
    2
    & j- M. C- a: S( Z, U, H# y3 P! `2 M3
    ) ?9 Q2 \; I: J9 z5 W6 k46 D# F  d2 m- o; y5 k3 Y
    5" u# Z) a; V( [/ B" s$ P( g1 X0 f
    6
    ) |/ g! B" c7 z6 K7. L7 X% s7 u2 b6 J8 s
    8
    0 L4 m# p; `8 u8 h9
    1 ~! ^. q  _# f/ }" }5 v" Q10
    # c: [5 {# {4 k. x- V' [11! m, H" P' v2 p) B
    12
    0 ^6 J& k6 I1 h- p( B! ^13
    ( J0 W/ b: D% q; t  T145 n2 A* J" z4 c4 N! M% V
    15
    ( ^3 t* S: C% z/ i# f16# t& i' f9 k+ m% ?3 y
    17
    & ~2 R1 x( T) h+ U% d3 @18
    # [( D7 q$ ^) L: L8 s19( q; [8 J# s- D$ ?8 u! ^9 f9 P
    20/ r- c) \1 @4 L4 p+ Q2 O* E
    21' ~/ `+ G& Q( ~4 j) b4 o, U
    22/ _$ \+ B" x) n- x. Q. B
    23
    4 J1 h7 a" E6 @1 Q0 S24+ n) N3 u; ^7 n: O4 p0 W1 j! K  f" B
    25
    ( _# e+ B! H1 j5 [. `labels:区间的名字
    - _: `8 Z# L8 I4 B, jretbins:是否返回分割点(默认不返回)0 ~! r; }0 O1 D: S
    默认retbins=Flase时,返回每个元素所属区间的列表/ A4 Y7 c% x- x  Y& _: Y; I/ T
    retbins=True时,返回的是元组,两个元素分别是元素所属区间和分割点。所属区间可再次用索引取值7 J! ?+ l" ~! H* [8 G$ r$ o% h
    . }+ g0 e; Q8 P$ x4 X
    s = df.Weight
    8 T  R$ q: Q4 `res = pd.cut(s, bins=3, labels=['small', 'mid','big'],retbins=True)
    7 G; s0 i7 n; t  Nres[0][:2]
    6 [' @. i9 X7 y  t; Q8 I% Q9 m1 R5 f" T" b7 m, d
    Out[44]:
    1 w- `$ i+ t1 p* N* W2 K/ d% H0    small
    9 [( w" f2 j7 L  {- {6 i! P8 R; j6 x1      big
    ! s& p8 R5 \: P7 F/ fdtype: category
    - C1 W" z% v* QCategories (2, object): ['small' < 'big']8 c- }4 A7 K% X
    2 j! ?4 K; M' M$ G. C: D* R5 \8 P" C
    res[1] # 该元素为返回的分割点
    - ~+ p- f: @4 t* H5 n: WOut[45]: array([0.999, 1.5  , 2.   ])
    1 c+ d; q. G$ u( E1 `1
    & b* Y; f6 z9 V. s. ]/ n2) o1 k* f% e/ P$ L8 D& I
    36 Z+ U& P1 h: O- O) E( K* g
    4+ v1 g' t1 X8 [$ P' E3 ]$ h
    5$ b/ U: _2 M8 ?$ f: G' v# d
    6
    $ T, ^$ ?6 P) A( ?; m) b7
    8 P# h# u" ^5 T$ f% c8% J* _* E3 `% S+ L( r: M" ?
    9( b/ P# Y7 d+ v' }) y4 T& _
    10
    4 P8 v) s7 o4 b# H! }11
    " O# e% {' Y" h% o5 I/ o5 W12; o- b; }1 S0 E+ v! k- [+ o$ G
    qcut函数。其用法cut几乎没有差别,只是把bins参数变成q参数(quantile)。
    : o+ i. `9 v$ @& Wq为整数n时,指按照n等分位数把数据分箱
    6 R1 O- ?# t' ~9 [q为浮点列表时,表示相应的分位数分割点。
    0 j. Y! ?  S. }& z. M* U, j- s# b& is = df.Weight3 t+ r1 h* L' {- @* A2 R6 g

    . {& F6 L3 W7 a: z2 mpd.qcut(s, q=3).head()
    . D  G- {( L4 u4 r2 hOut[47]: 3 T/ {  c$ G  I$ ~2 J% a
    0    (33.999, 48.0]
      n9 l+ h5 a1 _1      (55.0, 89.0]
    ' {4 z* |  d4 n2 v* Y" e2      (55.0, 89.0]
    ! q! D% Q$ L- _4 y8 V1 l3    (33.999, 48.0]% e) E4 [4 U* r" A
    4      (55.0, 89.0]3 o' K) {2 `" R; g
    Name: Weight, dtype: category
    $ U" Y! K6 ~& N) zCategories (3, interval[float64]): [(33.999, 48.0] < (48.0, 55.0] < (55.0, 89.0]]4 |4 b( h/ t: P# W) L& ]- Y

    6 y. I: R% M: F9 U  A3 I4 \6 Xpd.qcut(s, q=[0,0.2,0.8,1]).head()
    ' \: J% J; J' L; L3 }( Z# ROut[48]: 2 P5 p: |: X, C
    0      (44.0, 69.4]- s, @! V4 ~4 o! s( L
    1      (69.4, 89.0]4 v' a! a3 d  |* [) f5 e
    2      (69.4, 89.0]3 D6 r* c( v9 K, u- W4 E$ {, F
    3    (33.999, 44.0]
    / U+ W! Q" w$ [- y5 h. d4 r4      (69.4, 89.0]3 d- G( ^: ^6 ^; M3 \" H: S
    Name: Weight, dtype: category* M% B2 u; L1 Q+ G/ f
    Categories (3, interval[float64]): [(33.999, 44.0] < (44.0, 69.4] < (69.4, 89.0]]( Q4 @" M- ~4 D4 `

    - D1 \8 b8 l2 B- w2 r, x+ A1
    , _" n, T# P* F" z# }, ~( C2
    ' B  o: e6 D* M  H+ I3
    3 n: T4 x3 S) a, ~- [% c4
    4 o9 B7 o) e1 P! b' e5
    * D; x" r1 ~9 e5 M6
    ( J( P5 j' z3 ]. t4 _7& R! D7 b9 o# _$ q5 _2 a
    8
    : A* @6 i* A$ X0 o& a+ Y" u! O9
    - x8 Z! h: Z  l' j$ x8 ^7 e10
    1 `! b  M  C# F0 F9 O' b11: t3 g5 @3 e" J1 |. Y% P' p
    12
    , U2 N& A! A8 d2 |+ `' e" y  J133 O* _: O! B, q2 Q
    14; m* [" b9 ^' W. z
    15
    9 u4 d" d, F+ k" j$ }+ C) o% J16+ z9 z# Z) c( ~( k, ^) C- e/ w
    17
    ) K6 y$ M% I4 J4 g& x  n184 I8 L; e& X7 C% V+ p) M3 G
    19
    2 U# L- Q7 w0 J! c$ W+ O' T4 y20' \8 K+ p% W/ \5 Y
    21
    ' R) ^! e- `. \  Y% c9 _, v9.3.2 一般区间的构造& s: h8 X. A" M1 D
      pandas的单个区间用Interval表示,对于某一个具体的区间而言,其具备三个要素,即左端点、右端点和端点的开闭状态。5 K  A/ H5 w: ?0 V; d) F
    6 Y, T# T( k. M; R4 Q/ k: _2 @
    开闭状态:包含四种,即right(左开右闭), left(左闭右开), both(两边都闭), neither(两边都开)。
    ) p% O. F) X* |, i( Smy_interval = pd.Interval(0, 1, 'right')
    " ^7 E* _' z7 Z: F. Z, P9 {! q; V; ^+ ~# b" v  b1 v+ s1 j3 S/ V
    my_interval- [! f- Q2 S, P/ H" _
    Out[50]: Interval(0, 1, closed='right')
    9 z) e1 I0 ]$ }1
    : h+ s1 |( b3 o0 r$ ?2" u7 _& A; A$ R, Q
    3
    1 |! v& k/ G1 E7 Z9 d2 R4: A1 |  R7 ?& T7 O% @; m
    区间属性:包含left,mid,right,length,closed,,分别表示左中右端点、长度和开闭状态。
    5 p+ T- Y( L) B8 c$ M使用in可以判断元素是否属于区间% S0 u: [: J7 Q* V# A
    用overlaps可以判断两个区间是否有交集:: R% H& A" ?" |/ j2 k
    0.5 in my_interval* S3 @# c0 q2 z9 f* [2 ~1 h

    # i# A/ n7 h. u* |( @True
    8 P$ y5 ?- F* a8 u& o7 x( d1+ B- S% [1 ~( c. e1 L
    2
    0 a- ~& U9 D6 n9 s% R2 Q  X3
    * Y& E( b. c) }* q" l' nmy_interval_2 = pd.Interval(0.5, 1.5, 'left')
    * ]2 X2 o6 a$ l$ \) Tmy_interval.overlaps(my_interval_2)2 O  t$ T& R/ ^% X% c7 q
    $ k  O& R1 }. l# P8 ?
    True
    " A: W6 T. g/ n12 G1 |' p$ ], H
    2
    , h9 f9 V8 f. e& O37 B6 [6 K8 I& H
    4
    : s' k  k& B8 U  pd.IntervalIndex对象有四类方法生成,分别是from_breaks, from_arrays, from_tuples, interval_range,它们分别应用于不同的情况:
    & R$ g: E/ S" w% ]! O9 g1 X- n
    * T- y0 V& K5 ?from_breaks:类似于cut或qcut函数,只不过后两个是通过计算得到的分割点,而前者是直接传入自定义的分割点:! q) r( k  V. P& x" r/ i
    pd.IntervalIndex.from_breaks([1,3,6,10], closed='both')
    ; X( ~* x5 ]* I5 _5 m+ l: d; ~4 G) G1 K' v+ o4 _+ z8 v
    IntervalIndex([[1, 3], [3, 6], [6, 10]],/ o( r5 H  [% C/ N, F0 x  M
                   closed='both',
    4 b# P$ q; O; |6 ^3 y               dtype='interval[int64]')4 I1 N: h3 ?  {4 E) M. h
    1# u) B/ l% w# e, _
    2+ L+ y% _+ `9 i3 C) H
    3
    5 z! v" y9 b6 i1 J9 X$ O4
    ! N5 X6 T1 n" V5/ u$ r3 A& Z  r/ a8 G
    from_arrays:分别传入左端点和右端点的列表,适用于有交集并且知道起点和终点的情况:7 [' s5 P- ~9 ]$ d
    pd.IntervalIndex.from_arrays(left = [1,3,6,10], right = [5,4,9,11], closed = 'neither')
    & H& ?" s3 K2 t" ^- {2 P& P6 |# `* Y! C1 L
    IntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    0 v7 F/ n" i8 |( z, k8 k2 C% L! ~, a                  closed='neither',) ^1 b* a: N( H  |9 r) E
                      dtype='interval[int64]')4 e5 I$ I' W" O) W$ T5 [% v
    1
    ) F. X0 u, U; A25 q  c! A. k  h2 {  S
    3
    4 X) @, D. N9 S4 V# x4; W/ I& t1 q# o9 w  V, i6 F
    5: U# G# H. i0 C* L
    from_tuples:传入起点和终点元组构成的列表:
    & {# [  ~' N9 s# D- p$ fpd.IntervalIndex.from_tuples([(1,5),(3,4),(6,9),(10,11)], closed='neither'): d8 N6 ~( t. L

    # K0 G/ W) A4 W9 RIntervalIndex([(1, 5), (3, 4), (6, 9), (10, 11)],
    % `% W( k) n; T! E              closed='neither',6 G; X" v( P9 A* o
                  dtype='interval[int64]')
    / J$ y- u( C: _0 W1 u0 b& e17 B8 W( H( m$ C: q
    2
    / d( l' a  v  _# u% [3/ \) Q+ u1 |) S; C& u2 `; X
    4
    % T1 i1 A  I0 i4 S8 j5) ^# }/ @, r% Q7 u# g; K9 z
    interval_range:生成等差区间。其参数有四个:start, end, periods, freq。分别表示等差区间的起点、终点、区间个数和区间长度。其中三个量确定的情况下,剩下一个量就确定了,从而就能构造出相应的区间:: M* [4 w! [+ _; J
    pd.interval_range(start=1,end=5,periods=8) # 启起点终点和区间个数+ c* b0 j) y+ u
    Out[57]: 5 P. o  d& D/ _- W2 O+ C/ s
    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]],1 _& v) F& b6 Y5 x* A% |* C3 {
                  closed='right',
    . B3 D6 _/ t) E3 T: o$ A- T" Q              dtype='interval[float64]')
    9 S! V/ G% L9 v
    , `+ u( L- R0 O" ~, I; Wpd.interval_range(end=5,periods=8,freq=0.5) # 启起点终点和区间长度. q3 Z; w5 }$ ?2 t
    Out[58]: " w( A6 {! f5 W  z
    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]],, ^4 H( q5 H* M0 A* L- v  f
                  closed='right',
    ( ?& q- q( E$ C7 A7 K9 E- T2 r              dtype='interval[float64]')
    7 b  O  B; d' [- {1+ j8 Z! i3 }' }
    2! o7 x0 g" z% g3 j8 J! S/ C
    30 x1 P+ V. a( _' M" Z4 T
    44 I7 c! g( T/ O' \4 G4 x) R
    5
      z% t" H5 U, }+ x3 o* G6
    " l* q! P( `8 ^5 }7
    " C; z$ Q) d  b6 b: y4 {8
    0 U7 p6 [: D* z; t( s7 Q9( U9 d. z# ]( s+ p. j3 D9 g4 I
    10# G9 h* U, v1 d  F  @
    11+ ]1 d) r# G5 h; X! o4 ^3 Y  b
    【练一练】
    # ?$ X3 o8 V5 D  无论是interval_range还是下一章时间序列中的date_range都是给定了等差序列中四要素中的三个,从而确定整个序列。请回顾等差数列中的首项、末项、项数和公差的联系,写出interval_range中四个参数之间的恒等关系。% \& K9 R% t5 n/ i7 a1 e

    7 @; o# a- I; {4 J  除此之外,如果直接使用pd.IntervalIndex([...], closed=...),把Interval类型的列表组成传入其中转为区间索引,那么所有的区间会被强制转为指定的closed类型,因为pd.IntervalIndex只允许存放同一种开闭区间的Interval对象。
    4 P; `! W8 o5 U1 Z- @/ b3 E; Z
    3 D: z: \* i6 `2 E4 [& Amy_interval
    - |4 o- I& y% B* r/ t, }Out[59]: Interval(0, 1, closed='right'), A- f/ Z% g( _1 R8 r& Y/ p2 j
    : @. Z, W% D+ g! U. p& f# I
    my_interval_2
    " k, C: w) d9 B9 cOut[60]: Interval(0.5, 1.5, closed='left')
    * e* x6 r2 a  q# R) L% {0 s1 r' V, |8 R0 h3 L% a" C: k
    pd.IntervalIndex([my_interval, my_interval_2], closed='left')7 D. W9 ^8 ~( e
    Out[61]:
    / ]4 U. g4 o5 b4 G& r! EIntervalIndex([[0.0, 1.0), [0.5, 1.5)],
    - f- n3 s9 N1 q. E( O              closed='left',
    ; ?/ t% i, g" Y& g- f              dtype='interval[float64]'), F0 _, ^) v; o9 Y4 t  [
    1
    4 X& c2 E  E4 J3 ?: l8 C9 u2. ]+ t- m  G5 j8 m0 k/ y
    33 O" v9 Z' D7 B' l7 X* p
    4  s9 f& V" J0 ?8 L
    5
    % N) ]  n+ m6 X6
    + j8 n9 Y; q+ K) Y70 S5 O$ l5 Q1 ?5 G( ?; a& h# ~% [8 t
    8% v, T( ^4 O7 k$ @/ ~3 o
    9& I  M6 I! k2 s  g* z5 |
    10
    + y5 i6 e3 R6 r+ m# E2 ^5 [11! z/ f# U" o9 _
    9.3.3 区间的属性与方法9 n1 ~! s5 x+ n, G+ d# d
      IntervalIndex上也定义了一些有用的属性和方法。同时,如果想要具体利用cut或者qcut的结果进行分析,那么需要先将其转为该种索引类型:) Z' z3 p' V# t! m/ K1 b6 j

    ; l  i( E1 S, g) q' C: rs=df.Weight
    , T) W" x' [) \' Z- kid_interval = pd.IntervalIndex(pd.cut(s, 3)) # 返回的是每个元素所属区间,用具体数值(x,y]表示
    % T4 K$ \7 v$ o* N. aid_interval[:3]
    6 ^/ I! g; C# @" X6 c
    ; P" K" M" M9 V6 BIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0]],0 c% B* }% l( c1 d) u
                     closed='right',
    / p; _- y0 E4 y                 name='Weight',* m1 J3 c" d- d+ Z& w& }
                     dtype='interval[float64]')
    1 P% k4 g* `/ P+ w% M/ W1
    , O% |+ ~) A3 x2+ m& j2 H+ n9 z! R$ Z) P; k& m# l
    3
    6 q. d7 s- y! s4 K" p4
    . o" y- T( P3 B6 K5
    4 [; Y; o. s+ W9 Q6; K! W4 J# K2 B2 P
    7% S! o% P" ?! w. q, q. N
    84 a! _6 q* q* Z
    与单个Interval类型相似,IntervalIndex有若干常用属性:left, right, mid, length,分别表示左右端点、两 点均值和区间长度。
    6 v/ S& ?& C6 O$ j4 gid_demo = id_interval[:5] # 选出前5个展示* w, w1 @7 z4 R. b

    / A& N4 a2 H/ a: D( O! wid_demo' z5 V1 n" a% ]  Y
    Out[64]:
    5 q, f. u7 J% O* u; f3 zIntervalIndex([(33.945, 52.333], (52.333, 70.667], (70.667, 89.0], (33.945, 52.333], (70.667, 89.0]],
    - @/ M5 l6 ^; I# ?5 T7 e5 ]% i* k  ^              closed='right',
    2 \" M6 Z0 E+ w0 J  M! S              name='Weight',7 h9 c! _- l8 }$ X4 m/ X1 C
                  dtype='interval[float64]')) V4 n. Q) z  K7 G0 C2 `, ]; a, G

    1 @6 o* z4 D' L5 E6 @9 X% t! `id_demo.left # 获取这五个区间的左端点& g+ w* i% B$ ~" ~+ V
    Out[65]: Float64Index([33.945, 52.333, 70.667, 33.945, 70.667], dtype='float64')
    4 L! r3 N! F" I% r, ^7 E' @$ J$ x  H- p$ c/ z0 `/ b
    id_demo.right # 获取这五个区间的右端点; G/ p, F% |8 v! f* F9 m
    Out[66]: Float64Index([52.333, 70.667, 89.0, 52.333, 89.0], dtype='float64')
    1 u. O5 m3 l0 N9 o7 ^' \9 ?
    $ ]% x" }. m; a9 v: }* k3 {id_demo.mid, c9 z7 s4 M; ?0 d% G2 f
    Out[67]: Float64Index([43.138999999999996, 61.5, 79.8335, 43.138999999999996, 79.8335], dtype='float64')5 ]8 h4 s+ b- B: [. H; @

    $ n4 T4 `0 p3 L1 o" p6 h4 c! rid_demo.length
    ' |/ y6 X+ Q0 ?Out[68]: 7 `: g+ B# R9 ~" J% r8 o0 V
    Float64Index([18.387999999999998, 18.334000000000003, 18.333,' j) d! g9 O- t3 [6 \
                  18.387999999999998, 18.333],
    ; D' I* i8 K0 m) v6 q' p" z: p6 Z0 S* A             dtype='float64')6 {9 c/ a7 {" Z: X( Y
    , f4 T0 n  J. I3 C- K
    1
    1 y$ \: t$ [/ e: |8 u; C29 h0 v4 [: M6 S0 v4 D: a& k/ D
    3
    4 P2 @4 g- |6 v( j2 n4. r6 `) l% t4 W4 n8 z, X5 D
    5, ]# j  g) Y! r0 O
    6+ S& q% w# Z5 @! C6 |2 C
    7
    & K( w. [. h' k% Y8 o8
    3 v* q8 ~" k* {( I% i& @6 o" i) g7 v/ k9/ F6 {/ M' X7 ], E
    10
    ( y, X6 ^$ r  ~$ z1 }8 X+ z119 P5 c8 J: @. I3 B) C
    12. ?. q9 L$ B% d2 `3 ^2 ^; R
    13
    ; g9 I8 W, r0 ?14
    % b$ K- B' L7 Y158 t9 K$ i& {4 z! r& S, f
    16
    % L( q" g- v( |( \0 E17
    " i; e% G+ w$ }# x18- I: A: ]+ s. a
    19
    # q- l7 T2 p( ]/ E8 c$ E4 c20
    0 F* ?6 F3 M0 q210 e& ~5 r& K( C7 N: ~
    22
    $ u9 ]2 t7 F' I( `0 X8 Q23* t: Q* T+ _* i1 m5 y7 ]3 J! }
    IntervalIndex还有两个常用方法:
    ' E: `8 S) P, q& ocontains:逐个判断每个区间是否包含某元素
    7 J" W( M$ J" ^3 R: S' A3 ^/ c2 qoverlaps:是否和一个pd.Interval对象有交集。" x) ]/ d* O/ E+ G! i$ F
    id_demo.contains(50)3 ]) f, H6 e) r+ \( L+ A
    Out[69]: array([ True, False, False,  True, False])2 H9 C- W" d1 u$ w8 o9 D
    6 G9 [9 \7 g9 f4 @5 g5 f  X! O
    id_demo.overlaps(pd.Interval(40,60))
    / o. w7 j* Z9 G8 a/ E' mOut[70]: array([ True,  True, False,  True, False])$ K& I2 @- ?0 @
    1- e! ~% D4 Y' X" e9 I
    2
    $ R* L  `7 Y& I' S3 X* F( {- R3) N! M& z2 \9 F$ M3 k# b' s1 [+ }
    4
    * A8 P0 R' S( Y) [5* B0 s+ t, t( ?: ~2 \
    9.4 练习+ d* k% [% Q# {( i4 y- F/ K( t
    Ex1: 统计未出现的类别, k" b8 T# n% |" c+ E, p, X
      在第五章中介绍了crosstab函数,在默认参数下它能够对两个列的组合出现的频数进行统计汇总:
    % ^& @* o+ t: F( e8 h5 {$ E1 N6 H# L0 L& L
    df = pd.DataFrame({'A':['a','b','c','a'], 'B':['cat','cat','dog','cat']})  o- ?' w; s6 h9 O# D1 |
    pd.crosstab(df.A, df.B)
    - t" t  j" h+ W0 V1 L4 Z  S$ t: k: V/ o% J/ s, J) }( ~
    Out[72]:
    $ b- F7 n" u$ L5 T+ o. xB  cat  dog3 e: a0 b( b7 I3 R
    A          , w4 y* l# D4 v) ^. L9 j2 S
    a    2    0% w$ c8 n; t+ n: f! K
    b    1    0
    - B! g7 b2 k# C. w7 N. X( ^c    0    15 A+ j$ c+ w  \/ g5 G. `" ~
    1. F, ~9 W7 _, O, Q3 t7 @4 }' ?
    24 Z8 m3 O9 ?1 j1 \( M
    3
    2 ~: K! X1 a  ~, h/ f4
    7 t' d- r4 A. T% i  U8 D55 S# S" b5 S  L) H% R5 v* Z" z& e
    6; S+ L( E" r1 k
    7# l  ?" K0 \3 g! D- U  q8 b  a
    8+ S7 L& y6 |+ z# a( }, I  L
    91 U- h) {9 R6 a5 k
      但事实上有些列存储的是分类变量,列中并不一定包含所有的类别,此时如果想要对这些未出现的类别在crosstab结果中也进行汇总,则可以指定dropna参数为False:
    & U# n$ A; \* e8 F" W
    # ]; I' Q! ?$ i9 }6 o: xdf.B = df.B.astype('category').cat.add_categories('sheep')
    8 _' y8 p$ u7 U9 Rpd.crosstab(df.A, df.B, dropna=False)
    , q) o# u. `- `, t+ J' I# z* x" ^' V* R
    Out[74]:
    " l# S+ v, \5 N/ ~B  cat  dog  sheep9 F+ n. }; G! G7 b9 E
    A                 $ Z2 u. ~0 R, G$ [2 J0 d& V: C
    a    2    0      06 y$ U/ T! W/ Q8 Y1 K  o' J, ?
    b    1    0      0
    1 y3 }2 H8 J5 \+ P* @" Vc    0    1      0
    ; U/ l- ~3 B3 |1 N, ]1
    % ]0 q4 `2 X8 h/ @! s6 ]  A! z7 ^' t24 l* |3 x  d. \3 q. g9 W; D( r
    3! M! k& F) v" R+ H( E
    46 A6 u( B' \0 ?) L. h+ a
    53 T- x% Z: R8 f& f, P" Z7 l  W
    67 p. h7 i+ a1 @- B/ `0 w
    7
    1 G1 w( S+ v% y# f8% t0 C% ^% e3 n
    9
    3 ?% [9 S: T! s# s1 I' D% l- [请实现一个带有dropna参数的my_crosstab函数来完成上面的功能。7 u) }  k2 L- H8 m' b  Q% p0 ?

    # `5 X3 O% z* h! a  mEx2: 钻石数据集8 M8 L+ g4 m5 m: v4 g
      现有一份关于钻石的数据集,其中carat, cut, clarity, price分别表示克拉重量、切割质量、纯净度和价格,样例如下:3 E* @+ `& H4 @
    ) C. I# w6 c1 d& c# E
    df = pd.read_csv('../data/diamonds.csv')
    ' ^' U# f0 d+ Q  h5 Y8 `+ ?& `df.head(3)
    5 c" V- \/ x* W: g  H+ q. U( l
    9 u* B  K3 F/ d5 COut[76]:
    ! s# U9 A# ~* M- c: v  L- g   carat      cut    clarity  price
    4 |# J: k8 m% U- K+ V0   0.23     Ideal     SI2     326
    - G% \" W: W3 r. y% _1   0.21    Premium    SI1     3269 ^' b/ l# r8 v) ~% G& V! ?6 W
    2   0.23     Good      VS1     3270 T  [& R1 v9 f  _! \
    1
    / g1 A( y4 A# K% m2# E) O) p$ j' `
    3
    3 P8 A& m7 `4 H' H- s" e4
    : q7 f$ r; r# K7 w3 |. [& V55 Y" j4 d9 S6 J6 n; S6 r% r" ]
    6
    & y9 t- b8 o" C3 S, m: t+ Q7
    0 A  ~: J6 h& ^8# i6 j0 P6 I. `+ ^  Q& _
    分别对df.cut在object类型和category类型下使用nunique函数,并比较它们的性能。
    7 v! i! y4 L) O) g钻石的切割质量可以分为五个等级,由次到好分别是Fair, Good, Very Good, Premium, Ideal,纯净度有八个等级,由次到好分别是I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF,请对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。: C  _9 B; }9 M, E5 a% G1 a# I5 B8 [
    分别采用两种不同的方法,把cut, clarity这两列按照由好到次的顺序,映射到从0到n-1的整数,其中n表示类别的个数。
    % L1 K4 d9 B; R& W4 s1 v对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。
    ! x3 E' T6 \4 @+ h7 Z0 ?7 c, i! `$ {+ b第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。+ h$ d( q$ t5 e
    对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。/ P. O; Y& f3 `7 N, B( k
    先看看数据结构:
    $ @, t. Y8 J$ W. c( e, m! q, P0 s& x" g+ c3 I/ C
    df.info()0 Y: H, I# S9 l7 Q0 t1 ~0 i6 `" q
    Data columns (total 4 columns):! t0 H% e! n5 p( h5 {
    #   Column   Non-Null Count  Dtype  
    + l3 f; B, f" ~" T4 S" ^---  ------   --------------  -----  ; Q1 X8 O8 u+ M& u/ r
    0   carat    53940 non-null  float64
    # b5 @! R! J1 }' D 1   cut      53940 non-null  object
    * t. s3 B; d. [8 d* V 2   clarity  53940 non-null  object
    & K% `1 g, M& ?- ]' p4 ?+ y) `& j 3   price    53940 non-null  int64  
    8 k6 n/ h" M. Z$ O9 \, wdtypes: float64(1), int64(1), object(2), G. O6 F+ Q1 e6 l0 _
    1
    9 z. x. O. Z9 o2
    ) D: K( j3 [8 J5 o6 V& B4 F; T3, V1 U, T" n( B* D, w5 ?) Q
    4
    . C  I6 \7 o9 W5
    5 Q+ ^4 j8 P" l% t/ Q6: L& n( {6 E4 c# u$ V
    76 k5 ~) L6 V+ s, L; f; E8 G9 Y( G1 t
    8; O( j! K- l) F
    9: ^9 N6 a9 y% `+ m( ^
    比较两种操作的性能
    7 P( ]( h. [  a% q7 @9 A' b%time df.cut.unique()0 ]! f6 h2 s' i

    7 B5 b* k8 j' Z7 N- UWall time: 5.98 ms/ W1 i: {& M! m1 M
    array(['Ideal', 'Premium', 'Good', 'Very Good', 'Fair'], dtype=object)3 r( e2 P& c" |: r9 `
    1/ |3 t" {1 ]# J0 B
    2
    $ I. F- B! [: W& f% Q" Z31 I3 ~. F  V0 |3 ]9 _7 S
    4
    2 f2 b2 B" k) I% `# q, @: ~%time df.cut.astype('category').unique()
    3 W. S2 v6 ^% k; B3 X! z8 r" l% Q) G) J
    Wall time: 8.01 ms  # 转换类型加统计类别,一共8ms; G$ q% E2 f! V8 ]& j: w
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    " g2 z$ o" U9 a, J7 `/ E5 ~Categories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']' _  V8 v6 a- l( e
    1! A/ Q7 Z, c/ X9 `2 P4 H: Q
    2
    5 S5 S; t9 g- ^  _3
    5 n7 M; F' n7 @4
    : t" a6 v* }% K$ ^0 l3 z5
    9 |: v9 `- S' i1 }! ?9 ]df.cut=df.cut.astype('category')
    . x# y$ P! ^0 r; I%time df.cut.unique() # 类别属性统计,2ms$ ]2 D0 f! }; d( h' G2 M3 K6 U
    ! g; F# w) |" y$ M" _7 y4 e
    Wall time: 2 ms) e. y' V. g; a6 T5 ^
    ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
    ) L0 A4 L6 q% wCategories (5, object): ['Ideal', 'Premium', 'Good', 'Very Good', 'Fair']
      A+ k3 n! Q. Q19 `+ a6 u% i, C" ]
    2) g, S1 M5 G; e, `  h8 Z4 z. p
    3' ]) w. j; Z! t+ w% O8 o8 i
    4
    $ H1 ^) k  W- J& I" f/ G) d4 Y51 J$ v4 ]( e3 n) E5 [
    6- @' ^; f/ Z2 R: G$ P
    对切割质量按照由好到次的顺序排序,相同切割质量的钻石,按照纯净度进行由次到好的排序。' E  [! n5 w1 D
    ls_cut=['Fair', 'Good', 'Very Good', 'Premium', 'Ideal']5 v2 q* k% T# L6 A
    ls_clarity=['I1','SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF']' ^0 ]1 X" T9 ?9 j
    df.cut=df.cut.astype('category').cat.reorder_categories(ls_cut,ordered=True)  # 转换后还是得进行替换
    - \) N; d7 M% u# s. gdf.clarity=df.clarity.astype('category').cat.reorder_categories(ls_clarity,ordered=True)
    0 C, e7 Z. ~) w* s* L% I! k% P; D9 L; V) f1 S
    df.sort_values(['cut','clarity'],ascending=[False,True]).head(3)
    ) j  t7 _$ `5 R) \: |3 b" y/ _$ d( ^8 z: c* s9 A
            carat         cut        clarity        price- Z* u$ C( R0 {5 U  \
    315        0.96        Ideal          I1        2801
    ' I! E) e; R( p! S* b- H535        0.96        Ideal          I1        2826) ?5 K* L& G% y; Z0 b0 N" e
    551        0.97        Ideal          I1        2830
    2 A. U! t" Y: z1
    * X* y1 H8 a- u# a; a, M! d  T2
    + g8 p0 o8 R0 u$ W/ a( Y  A+ H3& ^' \- U8 \: Y7 F6 b4 i
    4
    * s7 q+ N2 _! b* @5$ m! K1 g& Y2 A( e5 r1 r  f
    63 u- f0 P/ u7 z$ o3 r$ v
    7
    9 H0 H8 O3 S2 r7 `& c2 C8
    ) o* S' d8 k% ~2 X. y# [9( N* x. B( X8 G$ V
    10
    1 v9 i4 o" {/ U" ^8 v, @3 z2 F11
    7 P  O: [' m7 y/ g# N% ~分别采用两种不同的方法,把 cut, clarity 这两列按照 由好到次 的顺序,映射到从0到n-1的整数,其中n表示类别的个数。, W6 j8 J' _5 v* F3 p. @& r& i. L( b
    # 第一种是将类别重命名为整数; M# Z" v0 H  d2 S
    dict1=dict(zip(ls_cut,[x for x in range (4,-1,-1)]))2 t$ F2 k! c: x; e
    dict2=dict(zip(ls_clarity,[x for x in range (7,-1,-1)]))
    7 b( S6 M" O! {+ I, Y! F
    $ r  w! x7 P  h8 m: `" o" Y% Ydf.cut=df.cut.cat.rename_categories(dict1)
    5 q' F5 d" p8 S4 x* Fdf.clarity=df.clarity.cat.rename_categories(dict2)0 ?5 `7 k% Y$ k% M
    df.head(3)
    ( G, H4 \9 |8 H
    * V0 ?2 I& v% C! J- p        carat        cut        clarity        price
    ' b' q) S2 N+ w8 i0        0.23        0          6                326
    " D! b" o" A4 n* G: f3 @1        0.21        1          5                3263 j8 N# A; x% `# ^0 O; m
    2        0.23        3          3                327( q7 _; a3 V3 s# a+ ]8 Q, r( S2 V
    1
    - k7 X( O2 g8 R; i2 w2
    6 N- h1 _0 w8 q. N# ?' A3 D3
    : l. B8 G' Q5 v; Z) t4
    2 }6 h% C$ q: X. d5: ]7 H+ ^: ?  \0 O. c$ S# s
    6$ N3 k* L9 ^5 T( g( O5 t7 D
    7
    , `5 {2 R6 a4 P' B8
    ; j! s! w. c, ~3 F4 ]9# s4 v7 j4 a2 f
    10
    9 J) J' i" T( t' k$ G* ~- x11
    0 V: e: D- m8 |- ~12" ^" T0 _# ~4 x+ l& A4 `( N0 x
    # 第二种应该是报错object属性,然后直接进行替换% k+ ~' }, X& R/ R
    df = pd.read_csv('data/diamonds.csv')
    " N6 O, x7 R+ c5 Cfor i,j in enumerate(ls_cut[::-1]):
    9 A! S' Q/ R4 G2 T* n    df.loc[df.cut==j,'cut']=i
    0 Y% `2 y5 V' x. m+ D
    7 ?+ r8 `# s0 T: H: l$ T5 v5 u& L/ j, wfor k,l in enumerate(ls_clarity[::-1]):
    3 z7 p( Q& Q/ h% m7 Z* _    df.loc[df.clarity==l,'clarity']=k
    # V! @4 K  g) x( f8 P7 D4 Ydf.head(3)
    ! F- Y6 C) q5 @
    ' Y' ]8 N' d# I: Z: J9 i        carat        cut        clarity        price
    4 ]) f5 c8 c2 S2 u0        0.23        0          6                326
      k; ]3 I8 \  T/ y! p, ]% [1        0.21        1          5                326
    $ R6 }4 I: `, Q$ g; u2        0.23        3          3                327- v% ~7 x$ M8 m5 Z6 ?( ^' f
    1+ X; b: C6 j8 _4 D) m' {
    2' v8 Q7 V+ V% ]& F. Q
    3
    ) ]- M( n! {' O1 G' K3 K# F9 e. f4  a) w! V9 r! X9 _: E! i
    5
    / z7 ?3 T1 M% U) `+ @! A6, h( t) o+ P* }$ ]% l* s) v
    7! K/ [/ P& ^' s+ L' q; @8 k1 S8 a
    82 n8 v8 U0 m3 _' E8 H% E
    9" Y' [5 A" d7 R) r, h
    10/ b- [2 e+ g: L0 B; W& b
    11
    , w$ }& d4 o+ n* l* L9 j120 Y  W2 X+ t/ v: V. T
    13
    6 K. H3 _0 l# x$ q+ l- Y% ^7 w* ]对每克拉的价格分别按照分位数(q=[0.2, 0.4, 0.6, 0.8])与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别Very Low, Low, Mid, High, Very High,并把按这两种分箱方法得到的category序列依次添加到原表中。  R. h/ g- `5 B8 B* p
    # retbins=True返回的是元组,第一个才是要的序列,第二个元素是分割点: l7 U6 [4 l* E4 N1 a8 R
    avg=df.price/df.carat1 ~+ D5 z' K; k
    ( Q6 u  w7 `- U
    df['price_quantile']=pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],# S. g# E4 G* H$ V
                                  labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]  I. p. ^/ h* |6 p. I/ ^

    . A' Q# q1 o6 f) U2 j" O" N0 S& J; B5 Idf['price_list']=pd.cut(avg, bins=[-np.infty,1000, 3500, 5500, 18000,np.infty],
    " I6 b8 C! ]7 Y1 s" F                              labels=['Very Low', 'Low', 'Mid', 'High', 'Very High'],retbins=True)[0]
    / D9 d# B5 G! X) ^5 O# }0 G; a' Zdf.head()
    ; W5 u3 F' n1 _! z; l3 Z, V/ c/ w1 m! L# z% d" D- r4 v# G5 {- ~5 \
            carat        cut         clarity        price        price_quantile        price_list+ w" @- b6 }. \# W2 x- L
    0        0.23        0                6                326                        Very Low                Low; T# y4 T) W( j! i! i
    1        0.21        1                5                326                        Very Low                Low
    * J- y' {! v  N- y2        0.23        3                3                327                        Very Low                Low( H9 ?3 i2 }. |
    3        0.29        1                4                334                        Very Low                Low' _0 h' s0 U3 g' W% _
    4        0.31        3                6                335                        Very Low                Low                                       . j- |* c8 ]5 J) I0 r8 U% y& r

    ) i2 ]; n* g- T2 _& x) {1& e+ G0 Y3 E& f% i
    2
    2 u: M  D$ o) I; B* `* x$ g37 ]1 m( M8 R: Y6 G& w8 P- D
    4
    0 v& [" _; Y) Y2 E$ j9 e5
    - a. ], q3 |1 V# E; }: ]6
    3 l1 h5 O: ^9 M, v7 D7
    / J1 d8 P$ a1 {+ f1 p% n( H8! r4 V# F6 P: ]
    9
    ( [0 Q4 d7 G0 e10
    - {1 X/ G( J/ K# r11
    2 T; X% o4 A* t  G' d2 l12
    # J% e1 b8 o5 x) i3 i7 H' A13+ c9 u4 G$ J3 l" O
    14
    + P2 Y# x0 q- g  X15
    & a1 O! I  g( ^/ [2 E, i: v16
    , B0 n* u! {4 I* V% O& I$ J1 b. `  W分割点分别是:- Y" W2 S4 V5 R; ]% f7 a3 f
    3 y) e4 I1 n7 r
    array([ 1051.16 , 2295. ,  3073.29,  4031.68, 5456.34, 17828.84])- K6 V  ~" ?+ V8 v! Y
    array([  -inf,   1000.,    3500.,    5500.,   18000.,    inf])9 n0 X9 n% ~, F8 g
    1
    ; X& p# y0 s# H: b3 s: h20 g; j( _( Y$ r7 T; d2 O
    第4问中按照整数分箱得到的序列中,是否出现了所有的类别?如果存在没有出现的类别请把该类别删除。
    * ^% _) M# I1 F7 \8 V" i  Udf['price_list'].cat.categories # 原先设定的类别数1 |& c7 e! I; O! s' d3 q0 i8 m
    Index(['Very Low', 'Low', 'Mid', 'High', 'Very High'], dtype='object')& {" ~4 l! Y* i1 W) E

    ' D7 W7 m" ?. _) D% o% k# R9 p5 Ddf['price_list'].cat.remove_unused_categories().cat.categories  # 移除未出现的类别
    9 F" G6 w& U; s/ h# XIndex(['Low', 'Mid', 'High'], dtype='object')  # 首尾两个类别未出现
    2 U3 }9 _) G3 S" a1 E4 {1
    # e0 \7 V# Y5 z3 P2
      O" j8 h1 l2 X+ ?6 z" |1 y3
    4 }( F9 H1 ?0 Z1 m* |4
    1 k0 L9 E  {6 E! R. L% k. Y5
    ; F+ S( G& a# ^7 |: r  yavg.sort_values() # 可见首尾区间确实是没有的
    + h/ `7 y2 f# o31962     1051.162791
    8 n+ g+ m1 `' u  t& q6 H: T15        1078.125000: E2 }  C5 P0 `6 n; V
    4         1080.6451614 ~1 u5 O% u* A
    28285     1109.090909
    ; c$ O. k3 H  M) N) [13        1109.677419, p7 |8 w! B) T4 k2 z: I: ?. V
                 ...     5 ?* r, n$ ]- m0 _
    26998    16764.705882. n3 [& M9 P5 d  T/ o+ b
    27457    16928.971963
    # d; N; t4 q1 g9 J! _27226    17077.669903
    ! @3 o& f( n: v4 R. @27530    17083.177570
    , E7 [. k, v& h+ r1 q* i: E" j27635    17828.846154
    , w8 Z4 J% ~+ A8 b. O1
    3 z$ W+ e' V1 s. k, E: z6 _2
    ( w7 X( t- Z& b2 n" b+ ?8 o2 i35 i* o% I6 q( Y, }* a3 ~
    49 F# R. H* W( w' R
    5. {8 V2 e( C6 f) k' _; u) P
    69 j" u6 a6 @+ \8 V7 x5 k  G
    7
    " i  |" J" ~6 R- O8
    2 w/ c2 F  Z0 p) `9
    2 t& }3 h, i9 G# y4 @2 H" b10# R0 x5 Z9 k; s) o' B
    11. d, t7 \/ h  p; Q
    12
    9 P7 ]) I. o  U7 r; S; n6 n对第4问中按照分位数分箱得到的序列,求每个样本对应所在区间的左右端点值和长度。9 ]: [* ~/ D5 C" z+ T
    # 分割时区间不能有命名,否则字符串传入错误。
    / H  }- j$ Y5 j. j% ?id_interval=pd.IntervalIndex(
    , X+ |* y9 ]) ]/ g    pd.qcut(avg, q=[0,0.2,0.4,0.6,0.8,1],retbins=True)[0]! P* s# I: c! s4 w8 y* ?) C0 o
                                )
    , F. g6 N* h; g/ ?! `id_interval.left% \2 f9 K9 e; C- y( c
    id_interval.right
    $ r3 T0 e$ S" j! R$ P4 z8 `- {/ uid_interval.length                            # H& ~1 w6 s( G  V. L: H( |
    1  @7 m+ K* k  M" ?4 S/ f0 o3 X8 [4 w
    28 f- v% s+ {1 D: [
    3
    8 M. e" H4 w9 e+ z2 E4* ~6 d* R; J8 ]8 u( |
    5
    ! J! c& V0 v- Y7 I, @6
    - H/ S: K) d' V" i/ _! C( ~7
    0 i$ _$ ~  n8 B- w  l+ i第十章 时序数据  A5 L# q6 R8 B* z# l& t+ y
    import numpy as np
    " e2 R& ~( D  `" ^) ~5 @import pandas as pd
    8 o" x" q; |0 d8 u9 |5 R9 i12 N9 g# ~: o$ ]
    2
    & F$ F# C( j# S  }) @
    3 B# n+ ], X- x% P; s
    " m' M- m; l7 L10.1 时序中的基本对象
    % `0 p! G7 O+ P  时间序列的概念在日常生活中十分常见,但对于一个具体的时序事件而言,可以从多个时间对象的角度来描述。例如2020年9月7日周一早上8点整需要到教室上课,这个课会在当天早上10点结束,其中包含了哪些时间概念?- @6 f4 Q& V* Q

    1 ]- k9 j2 Q4 B1 {' \0 y会出现时间戳(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的简写。" ?: o! R" J' h1 l
    4 q8 K0 M: q6 @1 @% H
    会出现时间差(Time deltas)的概念,即上课需要的时间,两个Timestamp做差就得到了时间差,pandas中利用Timedelta来表示。类似的,一系列的时间差就组成了TimedeltaIndex, 而将它放到Series中后,Series的类型就变为了timedelta64[ns]。7 o& L( n7 F. ?+ w0 C  V4 K
    7 M4 y' z% w7 f4 [
    会出现时间段(Time spans)的概念,即在8点到10点这个区间都会持续地在上课,在pandas利用Period来表示。类似的,一系列的时间段就组成了PeriodIndex, 而将它放到Series中后,Series的类型就变为了Period。
    " s3 V% T; q# Q: G( _1 [; r
    # Q4 ?! A- H  a7 g6 g会出现日期偏置(Date offsets)的概念,假设你只知道9月的第一个周一早上8点要去上课,但不知道具体的日期,那么就需要一个类型来处理此类需求。再例如,想要知道2020年9月7日后的第30个工作日是哪一天,那么时间差就解决不了你的问题,从而pandas中的DateOffset就出现了。同时,pandas中没有为一列时间偏置专门设计存储类型,理由也很简单,因为需求比较奇怪,一般来说我们只需要对一批时间特征做一个统一的特殊日期偏置。- b: E5 X: r1 h" |

    ' |3 E- k0 Y! u  通过这个简单的例子,就能够容易地总结出官方文档中的这个表格:  G1 F0 [6 c- _; m  x: I

    - E- |9 I& m$ q) u  Z+ A概念        单元素类型        数组类型        pandas数据类型5 y# E" \1 U( s
    Date times        Timestamp        DatetimeIndex        datetime64[ns]: Z! G7 V! @6 q. ^; f7 n( D: N
    Time deltas        Timedelta        TimedeltaIndex        timedelta64[ns]5 C$ F* z2 h8 T9 T. `* B( r
    Time spans        Period        PeriodIndex        period[freq]& d# }4 m  o+ g% x! D' u9 ]
    Date offsets        DateOffset        None        None# ]3 p% s( n* z: a* \
      由于时间段对象Period/PeriodIndex的使用频率并不高,因此将不进行讲解,而只涉及时间戳序列、时间差序列和日期偏置的相关内容。: H5 o$ n6 Y2 o! c: _, S) k- x

    : Q0 e6 ?- w& E, L$ ^) e10.2 时间戳
    9 T2 X& @" o# o. u/ {10.2.1 Timestamp的构造与属性6 L3 Q4 _" O; |' e( Z
    单个时间戳的生成利用pd.Timestamp实现,一般而言的常见日期格式都能被成功地转换:$ S# S& S& ]* e1 P7 B) e& w" t! W+ p
    ' O  V5 K% C5 b, ?! D' ~
    ts = pd.Timestamp('2020/1/1')- E& ~; }- y- R: J

    3 W4 G4 I, m/ ], ~+ Q, y/ Mts
    : r9 O+ w0 _6 T6 @Out[4]: Timestamp('2020-01-01 00:00:00')" N1 }5 n& e: J+ n% F

    0 e0 }2 i  I5 {0 f; I, bts = pd.Timestamp('2020-1-1 08:10:30')* q' o& j9 N" |  l& }, Z

      ^7 X# K' f' c; D1 r) j6 _- Zts* j& X5 m: {+ Q6 {+ Y
    Out[6]: Timestamp('2020-01-01 08:10:30'); z& E( h8 D0 V3 o5 c# S
    1
    " U% T0 M& v! K0 Z5 {2 R0 O2
      G6 i5 J/ w/ Q# K8 p$ G4 m3
    : R8 B0 z2 N8 o( E: U* ]2 Q3 I4& K8 V5 R; V1 L6 g6 O6 {
    59 i2 Y$ ^. _4 w. O) E6 d
    6% Y6 c8 r' `1 M1 x! F
    7
    3 |; K. t+ W7 \6 `: b) o8 Y8
    " k) C) ]0 }- x7 J9
    , u, `% n" o' a% \  K9 `通过year, month, day, hour, min, second可以获取具体的数值:
    . H6 ~, n- r8 W; m* ^- s1 J3 B' \! d" I% b+ ]: `
    ts.year
    3 a  m: H2 D# c+ s3 FOut[7]: 2020# j! S. Z9 d5 \) D' {" M( @, z
    % @6 ^. \  L# ^
    ts.month- F) z0 B( @5 f' B
    Out[8]: 1: ^1 s8 a, |4 D: a4 o
    2 a" C+ |/ o! ^
    ts.day. Q1 x- @; v0 }' A
    Out[9]: 1) d( e8 R1 k* I8 u* X  r5 E

    % N$ ^" b) B) Q% m1 J) Kts.hour
      B3 \" V; P: t: d  B- cOut[10]: 8& N1 S; }. Z: q) k" r4 g8 [

    8 C" U4 x6 Z+ X, ]8 ]$ }$ s% p6 sts.minute& A! I; J& @/ N! V6 `6 o
    Out[11]: 10
    , l3 h0 ]- u/ h
    4 k) o4 _1 X4 u- a2 j  Nts.second
      T3 [/ a" N$ K* v1 b* NOut[12]: 30; _3 ^% G; X) F- |: `* A* b

    : o0 i* E% g- N! Q" I5 X$ R' o1" O* L4 [* C  r$ K) Q% x8 G8 V4 y* Q
    2
    & i- r2 q8 Z9 F4 B+ W) L33 O* `' Z' ?6 _1 g' A/ `
    4
    # {+ E5 p7 w' t7 @# ^5; j3 k# I% X2 F1 b3 e) v3 P
    6/ T# z+ [& R5 w5 E* x& O8 J
    7% Y# v( T+ |- z- u3 O
    8
    . [! l0 D0 ^. o4 r3 Z. S8 R! Z& p9, X+ s( N8 Q" J: {
    10
    3 V/ l6 C( u' s! P  w2 s6 v11/ L$ B1 O$ Z% D' W; H
    12
    ) I. }( L1 w% D. ?3 C) c& M13
    , i7 P4 I7 O4 [4 i5 [140 D3 u$ a- J! [3 i/ b
    153 N" N' \, w: ]
    16
    0 j2 Z. W% ?; e8 s17) m/ D7 b/ |6 N$ x
    # 获取当前时间
    . D1 `6 e( x9 G1 gnow=pd.Timestamp.now()
    % g1 p1 B  M( y/ s; \* C1
    . K: M' j5 C* [. _2+ k5 z$ u. J# J9 U- u/ A
    在pandas中,时间戳的最小精度为纳秒ns,由于使用了64位存储,可以表示的时间范围大约可以如下计算:' ~" V( a# X  N( x) g1 k+ l) a
    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). o& N3 z5 M# ?6 o! {8 h2 l
    TimeRange=
    4 J6 p( X& N, i% I- u10 ( K/ B( G' o. o
    9- _. n, K( N( l: y5 \% g' ^
    ×60×60×24×365& u( q- N' G* b$ E* U! \4 Z4 m
    2 / P( ~1 l7 N6 J! G
    64
    5 c/ S& w9 C$ p6 R
    ( Z$ B8 C8 k* s# D; j; D% W% J3 Z6 s* ?
    ≈585(Years)% ?4 {% a& @8 i, J+ V& y4 g. b

    6 {3 h+ e: Q! C5 \- y. \5 N通过pd.Timestamp.max和pd.Timestamp.min可以获取时间戳表示的范围,可以看到确实表示的区间年数大小正如上述计算结果:
    ( J8 E) G" {# U, R. i0 D( h7 p; d( @$ A
    pd.Timestamp.max
    ! J" I  x& [1 V9 pOut[13]: Timestamp('2262-04-11 23:47:16.854775807')! ]9 i5 {) B2 i1 [% e( x4 F

    # u1 r1 ?) t, P1 a; `- ~. ?1 t( ]pd.Timestamp.min
    6 z' C' {  W2 V" \4 FOut[14]: Timestamp('1677-09-21 00:12:43.145225')
    $ N4 ^  ^, @* \
    % ?, q, T+ m: B" ^pd.Timestamp.max.year - pd.Timestamp.min.year
    * F4 r# t4 }4 I# YOut[15]: 5853 g5 R* Z+ ^, T) h8 S  s
    1% Q! m3 ]) |6 L2 f' c; I$ w
    2, L  ~7 c, R2 M8 t$ g2 |8 [4 X
    3
    , X' Q  y$ L  |8 q' \0 I3 O4- G0 Q6 D9 s: p; j
    54 o" v& I# C+ w) x6 S
    6; E0 y" G( `, B
    7
    % o  D% k  x9 L; {6 M! j8& p6 w' w2 f* b2 b# a
    10.2.2 Datetime序列的生成
    + |+ J3 Z' m' ?1 j* T0 @3 b" Dpandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None,) s* |( u" V" A
                                      exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)2 o3 J$ Y- g3 C- I) [& d
    11 x# f6 @' I- ?/ t5 H
    2
    4 a3 u- h% p1 h% S- y+ R( }pandas.to_datetime将arg转换为日期时间。8 O! {  J% a/ d  Y" F
    / Z. N/ F; }  m
    arg:可以是argint、float、str、datetime、list、tuple、一维数组、Series、DataFrame/dict-like等要转换为日期时间的对象。如果提供了 DataFrame,则该方法至少需要以下列:“年”、“月”、“日”。& f; y% x; H' H: [# I9 V' b
    errors:
    & e1 P5 X, {8 h- ‘raise’:默认值,无效解析将引发异常
    * G& t* }2 {. ?4 s" O- }; b- ‘raise’:无效解析将返回输入, ~. @2 q0 }* X* a
    - ‘coerce’:无效解析将被设置为NaT6 v0 z  f5 f5 e: G! U
    dayfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析日期,例如“10/11/12”被解析为 2012-11-10。如果无法根据给定的 dayfirst 选项解析分隔日期字符串,会显示警告。% ?0 G2 I0 U' L3 q' n
    yearfirst:如果 arg 是 str 或类似列表时使用,表示日期解析顺序,默认为 False。如果为 True,则首先解析年份,例如“10/11/12”被解析为2010-11-12。无法正确解析时会显示警告。(如果 dayfirst 和 yearfirst 都为 True,则 yearfirst 优先(与 dateutil 相同)。)
    " t4 s: b+ X( p4 R2 e- H. \7 y& Futcbool:默认None,控制时区相关的解析、本地化和转换。请参阅:pandas 有关时区转换和本地化的一般文档' g# ^# \" A5 m' T# j
    format:str格式,默认None。时间戳的格式不满足转换时,可以强制使用format进行匹配。
    + ~1 |& R0 f# g: ]  x2 v) j) Dunitstr:默认“ns”。它是arg (D,s,ms,us,ns) 的表示单位,可以是整数或浮点数。这将基于原点。例如,使用 unit=‘ms’ 和 origin=‘unix’ (默认值),这将计算到 unix 开始的毫秒数。
    ; Q& d& w  B" \4 {7 k' Q" s  Ato_datetime能够把一列时间戳格式的对象转换成为datetime64[ns]类型的时间序列:( _- c9 B& E) I, r' o
    pd.to_datetime(['2020-1-1', '2020-1-3', '2020-1-6'])
    0 }8 W/ W3 Z2 m  x: c' A
    8 D* K  A- l0 e4 Y9 v& JDatetimeIndex(['2020-01-01', '2020-01-03', '2020-01-06'], dtype='datetime64[ns]', freq=None)! ~  a% v( r) D2 Z
    16 X# H4 m$ h) T. d, e8 {5 w
    2) }6 R( [) Z5 o! f8 [# I
    36 v2 m" E2 i/ k7 C9 C1 o
    在极少数情况,时间戳的格式不满足转换时,可以强制使用format进行匹配:
    * n- \( Q! x" ^- e# X% h) V9 ^6 Q) A) C
    ' r9 v3 h* [) ?( v  e' Stemp = pd.to_datetime(['2020\\1\\1','2020\\1\\3'],format='%Y\\%m\\%d')
      C2 K/ P' s$ S/ \" etemp
    $ o9 s1 _: D, u, ]/ W# P- f% y; P: |
    DatetimeIndex(['2020-01-01', '2020-01-03'], dtype='datetime64[ns]', freq=None). e$ {) o; u; _$ E8 W% K% K
    1
    ) L2 c4 D& H3 {9 T7 `- ~; H2% |" I3 S& T/ i# `# |
    3  D$ i+ k8 a4 `" N
    4
    * V. T& C( S6 v  注意上面由于传入的是列表,而非pandas内部的Series,因此返回的是DatetimeIndex,如果想要转为datetime64[ns]的序列,需要显式用Series转化:4 R4 r; z" |2 C9 C3 Y* c; b

    $ b) \1 G9 [% p  H2 C. Cpd.Series(temp).head()
    / `1 C/ ^. P) ~5 Z4 U% i& h0 T; F9 R8 W; E4 u& N
    0   2020-01-01
    4 j8 i" S& P4 {5 A" b; H; |( ^1   2020-01-03: _) u& E! l; Z8 V1 E( T
    dtype: datetime64[ns]' r' |/ r/ o7 X7 y
    1: D* Q: p  n' N: H. C
    2
      }3 _+ Y- u6 b1 P4 ?3/ @5 R  t0 K4 t! o! D
    4% i* V7 H) u+ ^. `- E
    5
    * n! {9 I* n! D; P/ d. t4 Y下面的序列本身就是Series,所以不需要再转化。: N1 ]8 s2 K/ b# B6 S
    & e0 U' |8 j6 j% z4 h
    df = pd.read_csv('../data/learn_pandas.csv')+ {5 ?" _* _! Q# B; R* v
    s = pd.to_datetime(df.Test_Date)4 F: t6 I' G2 l) l' x( c. h$ [1 z9 N
    s.head()
    ( L0 Y( n  `+ S/ F: c! ]% m' ?0 d- ~
    0   2019-10-05
    ) j) w, |/ |1 n' R& H" O1   2019-09-04
    # X/ ?. c) f9 v2   2019-09-12, ], [( _8 `0 S3 ]9 [
    3   2020-01-032 M: @7 H- O  _' N
    4   2019-11-068 a0 Z# \; R, E$ w/ \
    Name: Test_Date, dtype: datetime64[ns]6 j0 ?! ?: V6 S) Z4 }
    1
    5 r; \0 O7 X4 {. K23 M3 D7 h8 d, O/ W
    30 e& v4 }9 M/ ~! ~5 ^
    4
    - D/ J" k; i# l5 ^4 Q! {5  C0 m( a; {1 |# g8 b! Q& B
    68 J# p3 y5 c2 p6 A: }% |/ ^- Q; O5 @
    7. X8 w  H/ {8 ~) _; P; r. }- x
    8) b7 N! {9 @4 e$ j
    97 d: E5 L( Z0 a( R: Q
    10
    0 r1 z0 w4 H3 y8 h把表的多列时间属性拼接转为时间序列的to_datetime,此时的列名必须和以下给定的时间关键词列名一致:0 `( g; S' C; s! Y3 H. G& J2 g
    df_date_cols = pd.DataFrame({'year': [2020, 2020],
    + M9 G8 q4 c- z; [- z( B                             'month': [1, 1],, p5 U" w6 B' t0 A) d# b3 Q
                                 'day': [1, 2],
    / P. }) t6 s: C1 u7 k& x# ~7 e                             'hour': [10, 20],4 p% v. r0 {5 ^+ `  X6 i
                                 'minute': [30, 50],
    8 f3 U5 A  X' [                             'second': [20, 40]})
    * z4 e- j' v- \6 y# Cpd.to_datetime(df_date_cols)
    1 d4 O% W5 ?  Z7 \$ g" r( Z( V. ?, q1 ~' ?
    0   2020-01-01 10:30:200 y2 _* W1 t6 h/ w6 P  c& L
    1   2020-01-02 20:50:40
    3 X4 R0 U# L# M, C7 ?: gdtype: datetime64[ns]
    0 ]8 o6 F7 ~: a6 D2 q1
    + {6 i# f$ C; P- T1 [& }. p% g2
    ' d$ B8 L& @4 `$ E. v0 ]4 |3* R7 {+ j( C9 [
    4/ n2 x* a. l( X! n  V8 A* a
    5
    7 k2 A  r; O" W* x- _* ?6. |% ~% \  z# ?& l; n
    7
    ! w0 D8 Z* G! c. H, o8* V1 g* H$ M+ d2 {! I7 _
    95 r: B" i* L$ o$ @1 j% I2 Q
    10
    / Q, Y9 k2 J4 B0 B11( I$ C8 w3 F, T0 U! e
    date_range是一种生成连续间隔时间的一种方法,其重要的参数为start, end, freq, periods,它们分别表示开始时间,结束时间,时间间隔,时间戳个数。其中,四个中的三个参数决定了,那么剩下的一个就随之确定了。这里要注意,开始或结束日期如果作为端点则它会被包含:
    1 g' @& v  w- T) {) T+ X3 z+ wpd.date_range('2020-1-1','2020-1-21', freq='10D') # 包含
    ! p) l. z3 ?2 i$ ^/ ^% NOut[25]: DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21'], dtype='datetime64[ns]', freq='10D')
    # }: e! |+ K. o- e3 R
    # r' H, \" w( Z9 Q+ e! {$ Tpd.date_range('2020-1-1','2020-2-28', freq='10D')7 |% E( l3 f2 S9 p. Y3 d& @: f( a2 i
    Out[26]: 4 t% m; R$ H5 E  C
    DatetimeIndex(['2020-01-01', '2020-01-11', '2020-01-21', '2020-01-31',4 l3 x1 l# n, f
                   '2020-02-10', '2020-02-20'],
    ) t, I+ s4 ]/ S, @; u              dtype='datetime64[ns]', freq='10D')# C! D0 X! ?) I; |, P& R, W) J
    8 P& i+ J: J) C2 ~) [; s9 p! g: k
    pd.date_range('2020-1-1',0 u9 `& L5 Y: l" e2 a2 Z
                  '2020-2-28', periods=6) # 由于结束日期无法取到,freq不为10天  q4 U; y+ V  ~' j/ e# Q
    - P5 l/ q+ T: M9 H6 g( @
    Out[27]:
    - R0 e: ?# S/ ^8 N+ Q. u( JDatetimeIndex(['2020-01-01 00:00:00', '2020-01-12 14:24:00',
    ( S1 b# K8 p! x. M/ d7 Z               '2020-01-24 04:48:00', '2020-02-04 19:12:00',
    3 `9 ~) Q* z* G: B               '2020-02-16 09:36:00', '2020-02-28 00:00:00'],1 n# L/ d6 }/ R" f
                  dtype='datetime64[ns]', freq=None)
    ( L& y. D2 z/ v1 R9 [7 z
    ' U3 m0 D, @# x4 D+ K1
    ) C& W( h, D9 N2
    5 ~8 y0 h( N  h& x5 U4 C3/ g& ^* p/ L& T" ^+ |0 ?
    40 Q& L4 R, Z2 x. Q, s
    5
    5 D5 l+ r. H8 @. ?) z4 j6
      \, m4 }$ Z8 P7 f. U! j78 J, |4 l3 r1 o9 K
    8. J% N0 S/ J* x; n8 X! R. n
    9
    5 m' l( N% o1 v" F- a  o10
    3 o6 U2 y$ {* m+ F2 y* u2 ~. {115 d* Z- \3 q3 C7 u; W7 l, S
    12
    ! `% g' K$ {  i0 ^4 T/ B135 p) p" I/ ?  r
    14' P1 W. [3 l( b* w
    15
    & q4 @! H4 M* e16
    $ a3 _  p7 T2 Z17
    * H5 u3 Z; q0 V1 q" \这里的freq参数与DateOffset对象紧密相关,将在第四节介绍其具体的用法。
    4 x. ?5 |+ p. Q  Y6 g0 d1 u
    . e# r  ]1 G- N/ G! y【练一练】
    7 ]: G+ s- H1 z9 i" cTimestamp上定义了一个value属性,其返回的整数值代表了从1970年1月1日零点到给定时间戳相差的纳秒数,请利用这个属性构造一个随机生成给定日期区间内日期序列的函数。
    ' ^  G" m3 U4 B( s' I0 r, U2 {$ D) v; b, |
    ls=['2020-01-01','2020-02-20']
    2 ^! Q2 c+ L1 g% ^+ U# H- ]! F+ m+ idef dates(ls,n):* B! g; l  Y: G3 E1 [+ N" v" f* _
        min=pd.Timestamp(ls[0]).value/10**9
    " g& u. N! X8 v" Y. a    max=pd.Timestamp(ls[1]).value/10**9& }* H' f+ O. v3 r9 I
        times=np.random.randint(min,max+1,n)! n, T4 ]0 f4 u
        return  pd.to_datetime(times,unit='s')
    9 V6 B7 k9 D. x7 K0 I7 [! c# qdates(ls,10)
    ; U/ W( {2 m/ F+ A
    0 C5 H+ ^- T; {$ Z1 ^. P- ODatetimeIndex(['2020-02-16 09:25:30', '2020-01-29 07:00:04',1 r" m3 S1 J4 `- u; Y5 `
                   '2020-01-21 12:26:02', '2020-02-08 20:34:08',6 {  h0 o5 t+ w
                   '2020-02-15 00:18:33', '2020-02-11 02:18:07',# _5 r  w8 m- s+ r7 s
                   '2020-01-12 21:48:59', '2020-01-12 00:39:24',
      Z+ m2 s% l0 K6 G! b6 I: G               '2020-02-14 20:55:20', '2020-01-26 15:44:13'],4 z$ Q1 l8 Y2 h, M1 e. g& Y: j& U8 n
                  dtype='datetime64[ns]', freq=None)
    $ x1 W& m  f- ~! z; f1& m  H; G/ K5 a
    2: o# T0 w2 F0 B5 c
    32 R5 F3 U4 {8 J
    4
    " `( ?* W* n( q" e+ [5 G5
    5 Q' l" C9 g: ^& a+ B; @4 u$ q6
    6 k$ l5 w2 c! D7# i) ^+ |+ ?5 [' i9 S, m
    8% E' c) U& x$ Z3 j7 w, X0 T
    9
      \7 l' I$ }7 C2 w: F* a10
    ! ~6 C7 `6 y7 u8 E: n! Q% Z' y" j9 N110 K0 b6 P. z) W' z2 d0 y0 Y6 S
    12
    # p! T1 f3 i6 q13
    1 R" ]  C' X4 m  ?# ]" S- n! v4 d14; ~6 X- w( u# t" l4 P8 d. H$ n
    asfreq:改变序列采样频率的方法,能够根据给定的freq对序列进行类似于reindex的操作:
    % g3 s$ p# ~. b' z  Ws = pd.Series(np.random.rand(5),
    & A4 x+ ^$ {6 S            index=pd.to_datetime([
    + j" [7 T- m# p4 E) J6 l                '2020-1-%d'%i for i in range(1,10,2)]))
    5 O5 M) Y2 S- p8 s" h& a% G7 W0 g7 A/ i; F3 s  Z) j8 U
    6 B6 X+ B, L/ U
    s.head()" V; I2 p) ?. @; S
    Out[29]:
      m% Z# j0 `* v0 w* |) I8 s2020-01-01    0.836578& w1 @! s! z, }$ s# S
    2020-01-03    0.678419
    + m& o7 |& X: G% w% L3 a$ ~2 }2020-01-05    0.711897, i  d# s) D  G% Z3 D' {: H- C
    2020-01-07    0.4874290 K9 x6 c) C9 R9 [- o- L+ |
    2020-01-09    0.604705' Q& A4 F3 X$ u; j" r5 U3 F: Z
    dtype: float64# T, r& W! J9 H1 p; M
    - K/ S" g- Q; c0 a8 t/ ]
    s.asfreq('D').head()
    ' E8 ]" o6 i' IOut[30]: % f' V. y) B" J6 w
    2020-01-01    0.8365789 T3 F1 c* T' b4 F  w; O" V, T
    2020-01-02         NaN6 _- H% V6 {: G, S: Z! P5 w
    2020-01-03    0.678419
    8 W! g4 P* U' o. w7 b) `+ T: Z" M2020-01-04         NaN( N6 c- S" r# X  x5 w2 t$ u
    2020-01-05    0.711897
    ' C- `" R3 d, K, f6 GFreq: D, dtype: float643 |6 ~$ t& r5 j: G% X3 ^5 S
    ! P4 m" Q- x( Q& ?+ R  j' N
    s.asfreq('12H').head()3 K. Y' t) w  Q5 p2 x5 F, l: Y
    Out[31]: # |  v. ?7 z# g7 `
    2020-01-01 00:00:00    0.836578% i- g! H' O7 B, A( B2 O( @" ^
    2020-01-01 12:00:00         NaN+ B6 H$ X# K! T
    2020-01-02 00:00:00         NaN
    ) f, n) d1 X# z  g2020-01-02 12:00:00         NaN
    4 D  A6 P, D) t- i4 H2020-01-03 00:00:00    0.678419
    * w' E* p  p3 D' EFreq: 12H, dtype: float64
    : [9 }# o: P) K6 k
    0 @$ n+ N! h& ^9 s: O- @0 T2 e1! p3 c- b% B5 F) l& g; L- Q8 a
    2# Q" X2 |5 S  d6 t8 Q; X
    32 h" k' `+ I$ c- u/ v4 m
    4
    + [: u9 L8 L* U  i+ y5' {/ S0 [$ z  s6 y6 `
    6* L+ ^4 L* h/ o) a
    7. N1 e2 W$ B, E9 E
    8
    6 m! c: [4 V! Q4 W% o+ P3 Y9
    # i# `% l# d; u4 v( b; `6 I7 }10
    6 h7 x" O( G7 _( \11
    # K9 Z+ S3 V/ r1 c! W& Q% y12
    * ?, o, y. m$ S- w- S& N13$ I; I' i' h2 y) o; `
    14
    " l5 E  K) ^" Q3 `  b15
    # g: ]1 s! W- M" `9 `& D16
    8 J3 M, s: }; g  Z17
    # e/ g2 B1 N7 X6 _9 w18
    ) s2 j9 z+ @! ^* T, O199 T* d3 @5 x+ x, o% ^
    20' ?- Q$ u2 x3 B
    21
    0 t3 K+ Y8 F1 u+ T2 u; n/ [' o222 y, W% y2 R1 {" o
    23
    - l: I  ^9 n7 p; a4 m24
    / m" W, U# v/ p$ @* h9 E25
    8 C+ Z$ I4 L- Y8 x- |26% W5 l* Y: j( Z# p8 Y* N
    27
    1 G/ C5 T% b8 m; ^; p7 C28
    ' j0 s: P$ a3 z- I8 ^29" B( @  N$ {' X0 V9 U
    302 x" D* G, g& a0 w8 O% K0 l
    31
    ) n9 P* U$ a, B! Q0 R& q【NOTE】datetime64[ns] 序列的极值与均值
    " c8 `, c7 r3 z* M: i# B/ a  前面提到了datetime64[ns]本质上可以理解为一个整数,即从1970年1月1日零点到给定时间戳相差的纳秒数。所以对于一个datetime64[ns]序列,可以使用max, min, mean,来取得最大时间戳、最小时间戳和“平均”时间戳。
    3 `# l& r- Q# R$ r+ b: d8 B1 |; T% c$ b' N- f
    10.2.3 dt对象
    $ V7 u5 s6 {" N0 |  如同category, string的序列上定义了cat, str来完成分类数据和文本数据的操作,在时序类型的序列上定义了dt对象来完成许多时间序列的相关操作。这里对于datetime64[ns]类型而言,可以大致分为三类操作:取出时间相关的属性、判断时间戳是否满足条件、取整操作。+ t+ C+ {/ @; K1 Z3 ~
    " Q2 `0 V: E- W  g
    第一类操作的常用属性包括:date, time, year, month, day, hour, minute, second, microsecond, nanosecond, dayofweek, dayofyear, weekofyear, daysinmonth, quarter,其中daysinmonth, quarter分别表示该月一共有几天和季度。9 y# [$ a* T: {6 Z. Z
    s = pd.Series(pd.date_range('2020-1-1','2020-1-3', freq='D')). _( _& x- v+ i& b: o' i5 L

    * }0 s( i; Z5 ~3 N* @s.dt.date, L. S2 F) ]4 C, W% N  M" _, o
    Out[33]: 6 b/ j3 j% U/ D& S
    0    2020-01-01
    5 i& M3 N1 D& r# q8 i9 x: S' ?1    2020-01-02
    ( `9 e& T1 z- R% w; j: Y, }2    2020-01-03
    ' c4 |" T* c' o' Q( y; pdtype: object8 x& X- ^2 o2 Z; ?: m  i
    9 {2 y7 E( {' }$ q: o
    s.dt.time" [! |. O9 b# l% q4 N
    Out[34]:
    1 n5 P3 |2 r9 q! l3 Y5 K0    00:00:00
    - p: o2 F8 E" U% c, z1    00:00:00
    5 l- z/ F4 i8 f2    00:00:00! q' w0 }: A' \, Z
    dtype: object
    9 X0 }8 q. p  q& x, t1 K; s& ]3 a$ J/ G7 Q
    s.dt.day
    + y+ ?" F( R; A0 L; BOut[35]:
    " i' D* Q% W2 m, I0 c) _% @9 i0    1
    " d" F, V5 E% a" s% O1    25 N6 k  [  p$ P
    2    3
    , s6 Z9 J' ]' R& C( u9 Wdtype: int64  C! F7 o6 F$ r# \

    + O/ p8 p4 ^7 n! vs.dt.daysinmonth- O* l, s* F/ A) N% _8 S, D
    Out[36]:
    / w. z9 K8 J3 }: _* C! M0    31
    : ~7 ~5 J) I5 V5 |1 {1    31! g: N/ Y4 M, L! u( w
    2    31. \( Y0 e$ |0 q4 O3 ?
    dtype: int64
    ' t( S9 y" O) r% i
    ' B) F2 O- h1 `# y1
    % p  T# P  z6 Z  c) {2
    9 e( Y- u% Q0 R! v! n% o3  e- c2 E+ L+ {! ?3 D& F
    4. d& o8 n9 w- J. Q9 w- i
    5
    + U  O5 Q, D7 f. Y6 `; N8 b6
    2 C3 A! D0 N0 M" ]$ w  o8 F0 I70 r: t* L6 X* S) a5 Y# s. f
    8
    " C# \3 c9 N: _' P9
    3 G8 {% Z+ q1 g5 Y  \* N; y10
    , U! j4 m& w3 G6 E4 s9 x9 c% h11
    * k( f* y. y+ d; `  B( I: N12- y: W* y9 y; r3 l% l
    13- f8 a1 ~2 ~( L6 P- m
    14
    & ~. i$ U  E5 F0 O7 t152 v" p' t/ h' D3 b7 }% h' g. T
    16
    % b  r( ]" m' m- n" J# K177 L& j: m8 `* n  x: ?) a0 S
    187 \: s% L, x- ?5 W
    19
    # J  V, {1 U' I( [) {20! a- @+ f1 K! u
    21
    $ p, C* ]5 N* i1 `, |" I! L22
    " B# P( J  c5 t; @- ~  }23
    - D5 v% a9 x$ O1 a" \2 N4 |24
    ( d% O! C5 h; J2 `# g- L25
    $ v  X" v3 G/ v  H26: J7 C' h9 W1 v+ @
    27
    * s8 w! R' G& s! z4 R6 C6 ]28# ?- e* d/ c8 n1 B9 z) q, O( ]1 R0 {
    29
    5 U9 w7 W' k2 c8 \* s" g- A  在这些属性中,经常使用的是dayofweek,它返回了周中的星期情况,周一为0、周二为1,以此类推。此外,还可以通过month_name, day_name返回英文的月名和星期名,注意它们是方法而不是属性:% U; d/ C- B% L' n2 A' {

    6 v* |, w5 T/ c; j8 h4 u0 cs.dt.dayofweek. ]6 `7 l( `7 e
    Out[37]:   N) M6 C6 q, c0 e5 v' i" W* ~" B. ^
    0    2% S' q- {* A; W  E( c/ r9 d7 X- H
    1    3! x% g& x/ U5 k9 d
    2    44 l5 I1 V5 P7 [* N
    dtype: int64
    # k/ Q# z$ y  `: s) T2 w# g8 U  `
    * T" t/ l' S2 m3 d. O1 V4 l7 D. i" Bs.dt.month_name(), X7 B! f3 x* f: H+ d; J- G$ ?
    Out[38]: / A1 \# {9 g. w1 [% ~" R
    0    January
    $ @* d' _% |7 ]8 S0 [, F4 G$ j1    January( G$ B# L6 _7 s; c( m
    2    January
    7 o8 g% {1 n; B9 edtype: object
      E' B9 c9 B. M& P) ~
    ( D. a6 X( R2 T( c$ Z$ O9 Zs.dt.day_name()
    / x0 k# g1 e. X* d& hOut[39]: ( q+ {& @# t/ F  h" v1 C+ ]
    0    Wednesday3 _* x1 c" T8 g( @4 u% T0 t- o
    1     Thursday; w( j6 O) l$ a5 U
    2       Friday# S: y0 H8 S1 [
    dtype: object- ^5 h8 S# {' j- ~& ~: [7 ?
    ; I. [( }2 P4 z% @
    1
    % R, s$ o' m( O) }/ O9 Q21 P' A* t2 b9 w2 v# ]. P; p
    3
      h  Y9 `2 |0 |7 [4 K  `/ r4
    ( }, P6 J5 M# c# |+ q50 t$ u- x! x  ^
    6. f8 [- d& F: m1 m+ \9 u
    7! \( A+ `0 p7 U% C8 C
    8
    , u. y4 n$ w0 A+ h1 O7 b, m& \9
    & w* g* E' G# k3 t0 `104 K, a$ p/ o4 i+ P9 K5 u2 r
    11
    ( L3 m* w/ p( }8 K) O" S12, |6 }0 o! G1 B( r
    13* X$ n3 Y0 h! v; ]- w; J0 o
    14
    ; Q+ o0 f: X7 Y9 v, y7 n15! m* Q6 h. b$ a  H' Y2 E
    167 j9 ?3 ^; u# t4 L8 {
    17
    4 k2 m9 O& M3 _& h2 F  D18
      `: _5 M" `& F1 t1 K9 k$ I199 }) {# T5 Z5 h. k* L( r- v
    20
    1 _  t0 K6 |4 Z9 m第二类判断操作主要用于测试是否为月/季/年的第一天或者最后一天:% Y) \& f: I6 Z' w) C9 I
    s.dt.is_year_start # 还可选 is_quarter/month_start% l$ T1 e# _8 X3 ]) L: f/ L8 E8 X
    Out[40]:
    & P& L  J# z4 W% @0     True
    / ^6 n/ J* C4 L2 A" l+ Q1    False/ ~) j% |$ p1 B# x: Q# k
    2    False
    ) M1 y* c# F1 odtype: bool$ s+ W) v8 U! g/ ?% t

    3 l+ g9 S9 X" [- ~2 n% \2 Rs.dt.is_year_end # 还可选 is_quarter/month_end2 t" V+ a  i0 ~9 g# _1 x" U
    Out[41]:
    2 S2 y/ q" g; o: ]% H2 |0    False
    9 `" F% \. |' o1    False
    * l% r6 Z5 n7 j" _2 x; G& h' ?: x2    False: v' z$ p* y/ G
    dtype: bool" `. z: \% s5 X) i4 ]
    1
    ! s" h9 o9 p3 ~) R' z! d2
    . `0 F" K, q+ i. I+ E: T* I3 s3
    7 C5 u; H1 r2 g; y8 c# E8 C$ @: r4
    # Z' H6 R$ ~6 w( S) X# ]5' V& ]# a  t* E' Z8 I' e
    6& p) k; y5 {' Z: L
    7/ `& s( Q4 @5 T* G2 C/ m, {
    8. o! A- ~" N/ t
    9
    4 Q1 y3 _, I7 p3 R% i! B10, _0 k. s1 s3 ]2 ]. m: r+ j
    11. E# @5 a+ ?. t$ W
    12! h* C* L+ {1 I" n, ]: A
    13
    - r9 ^1 n' a; ~6 w第三类的取整操作包含round, ceil, floor,它们的公共参数为freq,常用的包括H, min, S(小时、分钟、秒),所有可选的freq可参考此处。
    & m0 n0 b9 E3 X3 o  vs = pd.Series(pd.date_range('2020-1-1 20:35:00',
    " Y5 O1 Q& q& o# g% {                            '2020-1-1 22:35:00',
    / P# C6 j" b1 H8 a  R                            freq='45min'))
    ( O* Z; a; o5 w2 _% I6 [
    8 O# K9 v# U, c1 W- N& ^5 F
    " H; n; l7 a) G) qs
    . V$ k0 r/ z+ MOut[43]: $ m/ {8 B% ?2 m8 r
    0   2020-01-01 20:35:00) }9 x. \/ }8 p# Y5 _& N$ E
    1   2020-01-01 21:20:00" z/ h( Y, ^9 k* M
    2   2020-01-01 22:05:00
    $ g5 U% [) n0 z* {4 p/ b3 Ldtype: datetime64[ns]
    ) X2 u: R% m3 w, K7 D! |# x: H2 I) ]0 a  P2 Y1 @3 C
    s.dt.round('1H')2 v- P% p3 @) r0 `' q% T
    Out[44]: 9 J. g, }4 i0 `; V+ [- D! [' W, t
    0   2020-01-01 21:00:004 G* I! O' h7 J+ w
    1   2020-01-01 21:00:00; H/ {. w& F# e
    2   2020-01-01 22:00:00
    # G" V* r3 J0 Z8 o( Z3 P% N( g) Ldtype: datetime64[ns]5 S" I% n: M; h8 B* n
    ( j$ k4 }- H" J& s- h, k) k/ L' d
    s.dt.ceil('1H')" m5 l* g4 @! @. F5 [5 b! Z" p" D# z7 N
    Out[45]:
    ( A+ R5 J$ ?+ w2 A, I$ ~9 g0   2020-01-01 21:00:00
    , e5 y1 U* ^1 B) V; O# x) r1   2020-01-01 22:00:00' W- }/ R: p% K' o9 @) G
    2   2020-01-01 23:00:00' F, C. i. V% z  G
    dtype: datetime64[ns]+ T0 M5 e: P" S; N: ?3 U  F

    , h6 Z' i; y* [+ N9 Qs.dt.floor('1H')
    3 F: G5 Z3 I# mOut[46]: $ |! {8 T! g  l. L* k
    0   2020-01-01 20:00:00, E5 b1 }6 [& t) W/ G( E' a
    1   2020-01-01 21:00:00
    ' U% J; g# ]7 O9 i" m2   2020-01-01 22:00:003 S2 z! Q% x# z' i) o- {) F
    dtype: datetime64[ns]
    $ v# \- o- u+ n' i: ~6 u2 t1 x; F0 x, s: s' N) y7 u
    1
    ! D+ A6 }9 N9 ~, g  z8 I28 n1 @" {% q* s* o/ p; E5 r8 G4 v! t
    3
    * I9 F) j* {2 u) X, r4
    " L: d" ?% w9 N, u& H* K5! K8 x. N6 b# c/ {/ N
    6
    8 ~: O: ^% _# Q- v4 t! s7$ O7 z- n" g) ^" \
    88 `: N4 g* Y  m. M
    96 u5 |5 S: s, [* a; z5 B) S6 o
    10
    $ b) l3 I9 r! \8 {111 T1 t1 w' K6 o
    12
    * A! d. }# w0 _- t# C13) \, h8 i' ^- P2 Q" x
    14& |5 M: K' S% S) Y
    15, k% Q4 n+ Z, ?/ F! m! h
    16/ C5 E1 x0 g9 n/ J0 r% h6 w
    17
    4 l5 S9 m6 `3 R# s18* _, F  Y' }  s" \% X, U3 {
    19
    " y; ^6 O, G: I# w  K20
    % v9 u" K5 Z3 `. Z' ~% m213 w) a4 o7 w4 X* ~4 W/ b
    224 c, }2 _" I) y3 ^( G
    23# N, b" F) M; E/ y) `$ p# }6 z
    24
    " E6 r# f' Y0 f% M+ v4 p25
    3 t1 V) k5 q1 Q- P& U26
    # B- T8 {1 m! m! F' ~27& H. j* w! S3 x- H% H6 J; V' h
    28
    9 d% N7 J) ^0 Z$ m8 ]$ J5 ]29
    & W" R+ G2 A0 r, Z( U+ v307 {  Q. D# O; d  b7 V
    31( N0 n- [9 F! M- b8 {9 L
    32# {- p6 ^; k$ v; @# y
    10.2.4 时间戳的切片与索引& T! O4 }) L9 l+ y8 _4 b
      一般而言,时间戳序列作为索引使用。如果想要选出某个子时间戳序列,有两种方法:( c& g2 u! V5 H4 v  i1 ~+ ]

    ) R+ }; w& k5 `$ w* u! S3 X利用dt对象和布尔条件联合使用
    : k; [$ `- D1 e" M. U- T利用切片,后者常用于连续时间戳。  {4 J6 S. f$ O( e
    s = pd.Series(np.random.randint(2,size=366), index=pd.date_range('2020-01-01','2020-12-31'))7 a6 D) Q# [% k/ i3 i6 }  `
    idx = pd.Series(s.index).dt
    3 ?; n6 y3 h# Q4 E# V9 f4 ^) S2 ps.head()
    % [, ~1 m0 _' \2 f% y! h5 n
    / U) X8 C, n1 R" G2020-01-01    01 m* C0 D* c9 l8 l. u% w
    2020-01-02    15 W% X, N7 o9 i, l& W
    2020-01-03    15 E+ Y$ v" O4 B0 X- f
    2020-01-04    0; V1 ~' I- N$ m9 c. w8 b1 O9 o2 z
    2020-01-05    0) b0 V8 G$ L# l. S, T& W6 Y
    Freq: D, dtype: int32
    6 \; l! i/ X: n& K& S/ k+ g13 h( J! @( [$ k" w4 ]: f. Y
    2
    - o) x) c4 o5 C, [33 g: y4 T1 d: t' y9 \. ~3 _
    46 }$ F/ r2 P/ j6 R2 _
    5. x, l  [+ X( c
    6; y! [% I; U' T0 P& L$ v3 f0 b
    7
      }$ r# R  _$ Y: M, t6 ?6 O8 O% M$ h8
    / d. h1 r3 [  ~+ m" K. y; D9% Q; ?# Z3 ]& ~* G' }
    10+ G! ]: Q/ T" x# l6 e
    Example1:每月的第一天或者最后一天
    . E0 O8 _5 V2 N& s
    , i9 I: A- f' W8 t" T/ q( G* l2 Ls[(idx.is_month_start|idx.is_month_end).values].head() # 必须要写.values
    : F7 o0 ?+ e/ e& XOut[50]: $ H# o" D# a& `& l% y
    2020-01-01    1/ t) {! d, _$ B
    2020-01-31    06 T. v* N& h1 I) c; N
    2020-02-01    1
    3 W) G  L) k+ j; E2020-02-29    1- o. |# _' f8 n1 M5 [
    2020-03-01    0
    * _3 G0 X; V8 n: vdtype: int32
    * N# G6 o& }2 o8 C0 d& u1& {# C/ t) ^& N3 E6 E( |& G8 `! r
    20 X8 l# w* Q1 P2 a8 E
    39 t2 p; k& }8 f+ i! B& e2 N
    4
    ; H1 Z5 }/ b1 m5
    $ }: S" Z" P% ~9 ~9 V! _6, x1 \2 `7 @: J$ D1 l
    7
    ( }8 g0 d. l! W* r8
    ) v! r' D8 Q, f- L* zExample2:双休日
    ; i& h/ s! _' X  V: _# [. a% S( _
    : G8 C  ?; N, ]s[idx.dayofweek.isin([5,6]).values].head()
    / s  P: p' o% j& O+ k3 |Out[51]: ' n: s7 j& S7 E1 l# }" C& o
    2020-01-04    1$ U, f) n$ [8 {' l; C1 m" C
    2020-01-05    0
    4 H' [4 E7 u" o" `/ G% R2 ^$ x2020-01-11    08 T5 D4 c# ?8 z
    2020-01-12    1# L& |7 ~7 g1 U: O( F
    2020-01-18    1
    / T- B7 E" v  m. M4 ]* b# sdtype: int32/ v# @- T; l, l4 p3 o8 E' b
    1
    6 f. x( z+ _& C# U0 N: v& y23 H; D+ C5 r8 s% c, L8 I" a; o
    3
    : S; |, k; y4 y5 R  w. k) S$ |40 f: f, Q" x+ T9 J
    5
    + T( I! I% v; h  y- Q! S, w64 Z7 B( p1 |' k+ W) P. E+ a) K) S
    7
    5 A& I/ l8 T* n) I1 z2 f) v( y# {. e8
    9 Y! g  B+ N' x0 }- ]Example3:取出单日值4 _7 V3 r8 s( |4 c& d
    6 q( W8 N* l& a. Z
    s['2020-01-01']
    9 W2 I1 y3 a1 q7 tOut[52]: 1
    ! ?1 H, ?) E, T4 G, H3 N$ f8 R, I
    1 m6 \% ^5 ^9 J& _9 ns['20200101'] # 自动转换标准格式. x) ~9 D! W3 C$ C2 Q! y. G
    Out[53]: 1! C! w, r$ ?3 }
    1' c+ ~1 V3 ]9 ~9 |, Q
    2
    ' s" ?% S& J+ q7 x7 R& c36 G$ B4 K: m& m# o0 l$ f
    4
    / U# m' k7 y: v! H' H1 }% K& t/ i5
    $ K4 w0 Z) \, g( o$ ~1 i3 V0 @Example4:取出七月' u* g4 F. R7 N; _% }

    7 O: b$ b* _7 ^% Ds['2020-07'].head()
    ; i/ L+ \, o# y7 O* G( C1 B6 V2 cOut[54]: , v& u: |9 Z: o
    2020-07-01    0
    8 I+ a6 {5 z9 \9 J( @+ Q0 m) ^2020-07-02    11 C# l" q! L, L6 q8 M; G
    2020-07-03    0  k: O+ l! W: F0 \
    2020-07-04    07 c. v0 }) _2 O" h8 @' J
    2020-07-05    0) f; f& f! I# o6 r& I) \
    Freq: D, dtype: int32
    * m+ q: O) w  @/ E" r8 X5 F1
    : o/ t" S( Q2 g+ S2
    # i. X3 d( \: T% y4 L35 ~: B7 c  K' H& Q
    4
    0 l' z1 f( \( R5 K1 Y5
    # M+ C8 X- }9 N) b" T# G) g: d6
    1 w4 G$ }% j# p  r0 M4 K. [7
    9 J. v8 O& u( r8% {+ T+ d6 r, x
    Example5:取出5月初至7月15日* T7 O( ]3 G9 G  f# [

    4 p! k1 I  x& ]- F3 }4 js['2020-05':'2020-7-15'].head(): A& q5 `$ O, G$ w/ a( l* D
    Out[55]: 2 P5 v/ r- c4 i$ i; h1 K' M* m
    2020-05-01    07 S& W$ o' S! x. y' t9 r2 J
    2020-05-02    19 w9 y7 L. \' N, @$ t6 f
    2020-05-03    0
    - A+ q  _% p) g& A2020-05-04    15 ?1 Q6 s! A0 n) F. i! O
    2020-05-05    1
    5 t) Z" h& n' u. i8 {: g7 ?( CFreq: D, dtype: int32
    ! {* p& f4 N+ o8 b+ A! V: z  i4 V5 y0 D2 v
    s['2020-05':'2020-7-15'].tail()  R" |3 u  T9 M$ n6 ]7 {. F% Q
    Out[56]:
    0 j, }2 ~. g; w% w0 o2020-07-11    0
    + ]5 `& |" C& j; I  {2020-07-12    0$ B1 w3 h) Y9 `5 H4 z5 u3 V
    2020-07-13    1
    6 G$ F5 V! T0 z% O6 Y4 q2020-07-14    0& f4 i" c+ x1 e/ C+ ]. C; J
    2020-07-15    1/ h( J) V  ?3 F. s3 ~4 r
    Freq: D, dtype: int327 ^; f3 ?& ]; E5 v7 ~/ E0 o
    , b0 f  D3 C$ N) Q1 |
    1
    8 o7 {, W# @& x) S' Y2
    5 V5 b: `0 ~& m1 t0 @% [5 f3
    - ^, ?. j, H9 g5 ^5 ^% d/ _, f- {4
    / q5 E  A) t; C# D- Q# Y55 D8 p2 X  x& R' m  ~, w& }( C3 Z4 [
    6; v, e& p* N8 T" d7 y
    7
    0 f; n/ ~/ s4 e- J1 ]( A8! B+ u4 G+ c9 b/ @6 M
    9! i0 I3 {( X" o2 J% d$ o+ V
    10
      V% e* w3 O' Z5 Z8 @/ @  x11! ]- S; g, }5 b& J# J* R
    12* o7 h' _2 s' z
    131 P4 x9 \7 l# |8 H. I& }, S: I
    14  Q* g3 _* I, }, Q0 G. ^; C1 a
    15
    ) g% [7 c- q: ]3 _1 T7 l16
    . P) w7 D* D3 p17
    5 t  p; ^) ?8 C: J8 }& e10.3 时间差
    ! K% Z& N$ w5 l: W10.3.1 Timedelta的生成
    $ }, l3 r# ]1 w2 z' V/ |' {pandas.Timedelta(value=<object object>, unit=None, **kwargs)6 K3 `+ }7 P! F" D
      unit:字符串格式,默认 ‘ns’。如果输入是整数,则表示输入的单位。  K( ?9 n7 l2 W9 ^. O( M+ E/ L
      可能的值有:
    ' \$ A' \1 O& a+ q$ d0 @1 W( t. f  b, i9 X! E
    ‘W’, ‘D’, ‘T’, ‘S’, ‘L’, ‘U’, or ‘N’
    * b6 I0 K6 m% a# J# [‘days’ or ‘day’4 s. ?0 \3 X/ [4 U
    ‘hours’, ‘hour’, ‘hr’, or ‘h’
    8 b5 W( |# D; r, r5 d3 G  t5 ?' P‘minutes’, ‘minute’, ‘min’, or ‘m’! `0 o3 ~# w5 k" j& j6 J
    ‘seconds’, ‘second’, or ‘sec’
    - T' u( ]5 j6 R/ p- u1 l& k( D5 }/ @毫秒‘milliseconds’, ‘millisecond’, ‘millis’, or ‘milli’' y9 t) c# ^  ?
    微秒‘microseconds’, ‘microsecond’, ‘micros’, or ‘micro’
    : P- K6 r1 I3 H纳秒 ‘nanoseconds’, ‘nanosecond’, ‘nanos’, ‘nano’, or ‘ns’.
    & |+ H& y- i  x( f时间差可以理解为两个时间戳的差,可以通过pd.Timedelta来构造:6 ~, U5 q& Z, ?  k, Y. {- s# K
    pd.Timestamp('20200102 08:00:00')-pd.Timestamp('20200101 07:35:00'): w+ G3 n# v. a% e1 I' R9 x
    Out[57]: Timedelta('1 days 00:25:00')  l( D2 D0 T3 G+ v
    , O# d( v8 F% a  Y: m
    pd.Timedelta(days=1, minutes=25) # 需要注意加s
    2 Z! v1 o5 s( i, k  iOut[58]: Timedelta('1 days 00:25:00')
    5 W, `! }$ D- i. I  Z: u* b0 ~2 f7 l- [# X
    pd.Timedelta('1 days 25 minutes') # 字符串生成
    ' y0 ~4 A3 g& U/ VOut[59]: Timedelta('1 days 00:25:00')- o. d- W8 l. e; y* x

    - Y/ \1 B. D" ]4 \- ~pd.Timedelta(1, "d")- i5 x- ~9 _" `1 r' I0 I0 s6 K
    Out[58]: Timedelta('1 days 00:00:00'), U* {2 M. e4 c( X) F
    1
    / ~/ u4 O; B7 {8 L) x5 Z2
    0 b/ c2 n! I# w: q7 r% e0 ]7 S3
    / p2 k! z( O& h( s- e2 {8 X4  j: `  i$ k6 |- i$ \
    5/ A& A0 V0 C8 I. o/ t% Z: n6 p: g- C
    6* `! K2 D  ^2 \# I) u8 d2 q9 v
    7- W, r; y$ p; w: g
    8
    + r$ m8 f9 N! }4 T3 _93 Z) G' |9 e  B
    10$ f4 G& p- }* @% Z% H7 d
    11& W" I6 k2 C; A7 e* M$ v
    生成时间差序列的主要方式是 pd.to_timedelta ,其类型为 timedelta64[ns] :+ D6 A5 i8 g5 x$ V9 [. V
    s = pd.to_timedelta(df.Time_Record)
    ) @& c) i* Z! g" Y6 ~- Z0 g9 P
    6 v# s. v9 w4 y0 N, D. rs.head()
    % v! v3 }- Y6 ~Out[61]:
    % E7 I+ W% x! J* u1 \0   0 days 00:04:34' N0 z' r! U8 M; K$ o% d
    1   0 days 00:04:20
    4 b4 w- e4 K/ x% ?. ?6 ?+ ]2   0 days 00:05:22% U8 H0 ]( C# E( d) O9 _
    3   0 days 00:04:08
    & K  Q+ p# ]% [1 }$ N* H7 m4   0 days 00:05:22
    / D4 ?. c, i+ {" dName: Time_Record, dtype: timedelta64[ns]2 W7 g; ?# H) l7 l3 n, s, e8 X4 Y
    1! g0 B0 l$ I; v, f/ ]
    2! \! D! d) V8 n* R! j( D
    3
    1 _9 D( e; h4 r6 X/ c+ |  D4$ l0 P) K, D+ W
    5
    & M5 B' I+ Y; R0 b- s6) K& ]2 g9 c2 s8 D8 D, P+ {: k
    7" T3 @6 ?: {. `5 E  t+ i/ d/ v8 x
    8
    ) h$ v& j$ N, ^$ [$ K5 F95 o6 _" b8 \% V" N9 t7 h& Z
    10
    : }, T* e9 N# k( C+ M与date_range一样,时间差序列也可以用timedelta_range来生成,它们两者具有一致的参数:
    4 u/ H- i% U; A' T. ~pd.timedelta_range('0s', '1000s', freq='6min')
    2 B8 d. A6 w+ `$ cOut[62]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:06:00', '0 days 00:12:00'], dtype='timedelta64[ns]', freq='6T')
    7 D( z( o: A; u3 I+ R' H' K1 p0 Q+ a" T3 z3 ~+ o- P5 j+ f' q
    pd.timedelta_range('0s', '1000s', periods=3)# _# G6 k+ [3 [& q3 |
    Out[63]: TimedeltaIndex(['0 days 00:00:00', '0 days 00:08:20', '0 days 00:16:40'], dtype='timedelta64[ns]', freq=None)# M5 ~5 ^' A. l% V$ W. n/ G' a
    1
    8 m$ c0 j9 u  q  M' r2
    2 i! r1 E! S- ^& D3
    5 Q, X3 B0 c- w8 P9 p4
    " E5 h( _' |6 q5 T55 S; Q( R& c- U
    对于Timedelta序列,同样也定义了dt对象,上面主要定义了的属性包括days, seconds, mircroseconds(毫秒), nanoseconds(纳秒),它们分别返回了对应的时间差特征。需要注意的是,这里的seconds不是指单纯的秒,而是对天数取余后剩余的秒数:' l+ k# I2 L% n, m/ T6 z
    s.dt.seconds.head()* o9 y2 f$ o, W4 S
    Out[64]:
    6 `% h! T5 q. n3 O8 h  O0    274$ A; }/ ]0 u. c
    1    260, `  \' e- }5 e% k; D
    2    3228 c% f% W$ `9 a0 s
    3    2486 m1 Y6 L8 M6 |+ P
    4    322% ?% X' [# D% q* w
    Name: Time_Record, dtype: int64
    $ F) Q' A& g4 l3 r2 L9 c- B3 _18 D4 t+ K2 ?7 ^/ y7 u" @. c: |
    2
    : h6 t8 ^; @, u9 j! Q3 e3
    & m  _1 y* ?+ x" l1 I. `4
    ! m: I+ Q3 W" R0 N' R( G5
    + E: @/ p) O  n- C' B2 T" Z+ ?6
      x# ^2 q8 H* e7
    ( k* }# p3 z9 Z  n/ z, E8  Q5 [! ]: E, ~3 q7 e& w) Q0 Y* w9 W
    如果不想对天数取余而直接对应秒数,可以使用total_seconds
    # i4 S& l- k; z4 _  c$ t1 `$ A( e3 L  U* ~: `
    s.dt.total_seconds().head()
    ( d3 P; W$ c! Z1 pOut[65]:
    % r/ F8 T' W0 v* a3 N  M2 f7 L0    274.0
    4 e- }4 C, F3 y- O3 V0 a1    260.0
    . N, [$ z  J0 _+ ~+ h2    322.0
    7 d2 m8 ]/ Y  `- [3    248.0
    / [  O' Q6 G0 g" p4    322.0& c' Q& i$ A+ Q; c' P
    Name: Time_Record, dtype: float64
    2 c+ B1 p" G& C1 d% w  Y! d; l1
    2 W+ ~: V( u: C) E2
    : H% n! J1 m9 Q" L8 I. @0 J3
    ; o4 {3 q: ?: O  }* r4
    ' B7 j- t7 k# @# y. T6 ]5; ~" u: k/ Q  D3 G; @/ l; h7 B
    62 P7 v; x: G1 k6 L7 m
    7
    0 L2 C* K: v9 y  K  c& q8
    : j1 n! W$ v! J与时间戳序列类似,取整函数也是可以在dt对象上使用的:+ I* p/ A" e* N

    " f' g! [2 `) M5 p1 Apd.to_timedelta(df.Time_Record).dt.round('min').head()
    / }  h, Y$ y7 O/ J% o4 z/ w9 h: SOut[66]:
      G$ ?$ o' S- _; C0   0 days 00:05:00
    8 n8 K  ]$ [' f+ G, z- r1   0 days 00:04:00  \" n+ }4 ^9 e
    2   0 days 00:05:00% J' c* E6 P9 R, L+ D/ W2 p3 S
    3   0 days 00:04:00' }9 k3 I- K' x  Q8 [
    4   0 days 00:05:000 v0 G; ?3 P6 Q" Y: N; R
    Name: Time_Record, dtype: timedelta64[ns]
    1 f! j8 H  m' K+ D) c( ^1
    . ?8 ?% \3 O( c6 h3 s8 t: F2
    6 I6 Q4 ^' G6 c: T# m( ?8 o1 \35 r9 T0 W6 H8 H8 \; T, A; w
    4+ h* b( E/ T. R; o9 N4 @6 @- D# }% s
    5
    : t: I; Z0 X0 }) ?' `0 P* {1 n6/ I5 ^4 h( r, d7 }
    73 X% v# C. e7 A; G5 k; v4 c1 @
    8
    . v- B% ]  Y; p10.2.2 Timedelta的运算; X7 U6 m& i3 |- k0 t
    单个时间差的常用运算,有三类:与标量的乘法运算、与时间戳的加减法运算、与时间差的加减法与除法运算:
    7 O' C; C+ G$ j" i' B* r9 @td1 = pd.Timedelta(days=1)
    7 Q5 q. K" ?7 E  k/ Jtd2 = pd.Timedelta(days=3)
    7 {0 v6 O7 S* K2 Z  |ts = pd.Timestamp('20200101')
    * C! o, j/ G; T/ s* n6 x
    ' b7 V( l, C# ~4 Y4 ^& _# W7 Atd1 * 29 K! W# }! N1 m$ L
    Out[70]: Timedelta('2 days 00:00:00')' x' l  \1 M% {  C3 O4 l
    : L5 Y% _7 a2 G, H- t$ l9 t4 E
    td2 - td1- K6 [& E- c$ q: Q" l. S0 ^
    Out[71]: Timedelta('2 days 00:00:00'). e1 i  L) z1 q. s+ s2 k
    & Q! o4 W1 M& T8 D
    ts + td1
    5 ^) c; T3 z- M3 VOut[72]: Timestamp('2020-01-02 00:00:00')
    " ~7 A# J; a: r! Y9 w* Q$ L& L0 g/ w* I8 K9 Z
    ts - td17 P% E0 m( V3 e$ J
    Out[73]: Timestamp('2019-12-31 00:00:00')
    6 W# u! D: f" d6 B, U: B1
    ( e4 X2 Q% h( K2 a" `! w% r2: x* T+ z9 U4 {  G$ x1 L
    3
    6 h0 `8 A; \+ a5 a# a: I4/ O2 u# W6 R0 }$ N$ v% a1 m
    52 t( k/ a( m  k% i* P1 N" ^; }' a
    6
    ! M1 J3 A( O  @7# }0 Y- W2 V0 K' F, i
    8
    . _. g. Q7 V6 g. |9
    ( p5 L$ [; Z- E# _, F10) V/ {1 z6 D: J0 O
    11
    - Y1 K4 L1 R4 R' Y, W12
    / |; Q7 @7 B$ `# j# l13( H; I( T: P6 @
    14
    - ~  u9 c/ V) x/ {- P$ q  D) q15
    # o4 M* T8 u% b: J4 e/ q& `! p* d时间差的序列的运算,和上面方法相同:9 C1 S: a" Y/ G0 M& h5 x" [- O$ x
    td1 = pd.timedelta_range(start='1 days', periods=5)
    " ~! W5 V/ p, M, M3 X& y7 `4 H2 Ptd2 = pd.timedelta_range(start='12 hours',* X! Z5 u& i5 V/ ^9 p
                             freq='2H',% N- Q( c/ Q2 S3 T; c
                             periods=5)
    6 g( y" ^- u. k+ Ots = pd.date_range('20200101', '20200105')
    + Z- F$ N7 I& R" W& g' \2 ptd1,td2,ts! C2 k' o4 I" d; {
    7 U1 q; `0 @, W( z; H
    TimedeltaIndex(['1 days', '2 days', '3 days', '4 days', '5 days'], dtype='timedelta64[ns]', freq='D')2 n* Q. J) i8 n( w: t' V' s* T3 y( l0 B
    TimedeltaIndex(['0 days 12:00:00', '0 days 14:00:00', '0 days 16:00:00',* _" {7 @8 ?( n9 X$ {
                    '0 days 18:00:00', '0 days 20:00:00'], dtype='timedelta64[ns]', freq='2H'), `) k$ H1 l9 @1 O, t' J0 ?
    DatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-04',4 j( ~, H- u/ F7 t/ E5 i/ T# c
                   '2020-01-05'],3 M/ j( ]6 U$ U6 j0 l
                  dtype='datetime64[ns]', freq='D')( ]& f7 a6 @* M7 B& c
    1
    * N5 W: m( Y# ]$ N4 D2. K6 V1 t# u5 \3 U$ q0 p
    3
    * e' i) o7 k. G1 Z5 h4 y6 Z9 ~4
    0 Y5 |! t3 `1 \9 j! w1 K5
    " ^* T2 t" C+ b6
    $ T& @9 v) w& _; h% q" z4 B% ?6 x7: {: G& d6 k1 ]! U, P1 I
    8
    7 g' V  ?( O9 K7 I9
    7 P' T+ S. N! I" F) m+ e5 ~104 \2 J( h  f7 N. K: J5 ^
    114 P" m/ M3 {1 C( q! h/ Z% u# I
    12
    4 n: a+ t) E2 a- K13
    & A/ ?4 c. W  M- T" \0 L; f  S( Q7 btd1 * 5- _' e2 a6 W+ ~* b3 H
    Out[77]: TimedeltaIndex(['5 days', '10 days', '15 days', '20 days', '25 days'], dtype='timedelta64[ns]', freq='5D')
    ! R( R9 ]2 ~) T: p- F; c' G1 X6 b! O9 f% T% X' l* C7 D
    td1 * pd.Series(list(range(5))) # 逐个相乘
    , }3 l0 m9 H1 u3 G# }Out[78]:
    / {3 t+ L0 n8 X' a% Q0    0 days& U0 C' ^2 v' N4 r
    1    2 days3 v: e# o$ I$ h0 l! h# V
    2    6 days
    . l& y6 J1 b( ?5 [& t3   12 days& l) d; @+ s& p5 F) k6 a1 Q: A; d
    4   20 days
    4 R: j3 m, R$ M; u4 y5 c5 n/ p; odtype: timedelta64[ns]2 G9 }5 C* s7 y/ c& I

    $ ?# D* ]; }5 I: w0 mtd1 - td25 W: }  U$ c1 x# r" c# @
    Out[79]:
    - w, ?* g- R! g$ z- iTimedeltaIndex(['0 days 12:00:00', '1 days 10:00:00', '2 days 08:00:00',/ g' ^8 d+ V4 U. B0 w: a. ?
                    '3 days 06:00:00', '4 days 04:00:00'],
    6 }6 }( ]$ D9 \# _               dtype='timedelta64[ns]', freq=None)
    " @5 b4 ~5 b1 _6 U5 H. I
    ( r$ x, S! o. K6 {* }2 Jtd1 + pd.Timestamp('20200101')1 V3 t- E, ^+ P8 `% w& z" f% V
    Out[80]: 4 ^8 E( t( ~! `! u
    DatetimeIndex(['2020-01-02', '2020-01-03', '2020-01-04', '2020-01-05',
    " z' `/ b1 U* T+ g" r7 I               '2020-01-06'],dtype='datetime64[ns]', freq='D')# a7 z" ^/ _8 o# }4 r9 Z% R
    ) w7 Q) e  N: h4 z0 }( J/ z) Z; ~! P
    td1 + ts # 逐个相加
      @! x- _- s, X- |Out[81]:
    $ T; Q0 _. {3 t9 D4 f- t  o$ oDatetimeIndex(['2020-01-02', '2020-01-04', '2020-01-06', '2020-01-08',/ {7 j# P" W, G6 U! f: y
                   '2020-01-10'],
    2 s6 P% n# @+ m& u5 P1 ^! J              dtype='datetime64[ns]', freq=None)
    " v5 R; e- H1 z) z. g- m0 k& ^) ~: K" Q- H9 l
    1
    8 g# i' v! P( M( @* ?! b! @# S, G% t/ P2
    " h: n& `' k- ]7 M0 r; B. M# [) _3
    ; V3 i" J' E: C1 {3 M8 v4' j8 E3 `7 x% O6 J4 ]
    5
    9 k5 y' t3 j7 L/ {0 S2 t6
    " S  u9 Z' r% ^7
    . @1 b+ W' l. N$ m1 K$ K; {8% r( _# w  {7 m
    9
    ! O( `) Q# `9 l. }- D10
    % x0 x' G; Z, H, Y0 y% s/ v% N113 a+ m9 J7 z1 N) i/ c
    122 e) Q) I# f6 I% B$ k
    13/ l- o7 U0 u- M
    14
    ; Y* f! |5 _" _4 P15
    ; g: @5 Z$ w: H; p) _) L16' ]" X$ k9 Y% s+ u8 L
    17
    2 Y" K0 t5 R4 p2 D185 l5 q9 l: L2 u+ v8 S. J
    19* R: l% c3 v# e7 W
    208 o% u: ^8 H$ m5 \; f, [, x8 e
    210 t" q0 e; R5 u) s( D7 v+ `9 O- K
    229 f6 V1 ~0 X( H$ a: }5 @
    23
    " e( S5 a+ e. [8 i& b24
    # c* l, e' D% A' X3 b% @5 a) X9 o25
    7 B, c3 v! l2 K  z5 _, M3 c) r26
    ' n. c9 t0 I# ?' H/ @27
    9 c; v" r, R/ a6 R8 U0 M# b28
    . L/ I% t3 H" y6 q10.4 日期偏置
    # ]  R4 I$ ~  U7 n" S10.4.1 Offset对象" @  Y* H  f  t  F( b
      日期偏置是一种和日历相关的特殊时间差,例如回到第一节中的两个问题:如何求2020年9月第一个周一的日期,以及如何求2020年9月7日后的第30个工作日是哪一天。* n+ w4 ~; A, _- w3 N0 y9 ?8 {0 p: O

    8 D, n0 C: B. I/ Y' X( h/ s# ]DateOffset 类有10个属性,假设s=pd.offsets.WeekOfMonth(week=0,weekday=0),则:  X' {) g+ l: y
    " v3 o0 V% ?$ v
    s.base:<WeekOfMonth: week=0, weekday=0>,返回 n=1 且所有其他属性一样的副本
    5 `9 f# M4 T8 D; a9 Ws.kwds:{‘week’: 0, ‘weekday’: 0}
    / H+ J/ ^: b* j( Qs.wek/s.weekday:顾名思义' j* i" |% f5 U( J. |! y! h- s& r
    有14个方法,包括:
    0 {( Q1 ~, @7 r- |% Y9 R7 ?% u4 b
    % I4 C7 M; D. \+ w7 s2 H+ w/ kDateOffset.is_month_start、DateOffset.is_month_end、DateOffset.is_quarter_start、DateOffset.is_quarter_end、DateOffset.is_year_start、DateOffset.is_year_end等等。
    ) u9 r4 G8 l8 M# k# M: Z$ z  n7 ]pandas.tseries.offsets.WeekOfMonth(week,weekday):描述每月的日期,例如“每月第二周的星期二”。" W5 u# y8 q' K* w1 A

    1 ~$ e) X5 I5 h! D有两个参数:
    9 P7 |( m# [( [week:整型,表示一个月的第几周。例如 0 是一个月的第 1 周,1 是第 2 周,以此类推。. e( [# J: p1 d2 Q' q, z6 \
    weekday:整型,取值为[0,1,…6],表示周一到周日,默认取值为0(星期一)# }+ Z) w- T% _5 i9 |
    pandas.tseries.offsets.BusinessDay(n):相当于pd.offsets.BDay(n),DateOffset 子类,表示可能的 n 个工作日。. J# X6 X; @6 A7 Z2 j) X7 }# t
    7 r0 M1 l) \: S* c* V2 W$ {
    pd.Timestamp('20200831') + pd.offsets.WeekOfMonth(week=0,weekday=0)5 Z2 M8 {0 f& m0 M% F0 @) |- r2 B1 W' x
    Out[82]: Timestamp('2020-09-07 00:00:00')
    1 w# D, t$ Z% D, Z/ }7 W
      e1 _- F0 D6 }9 m. L+ O) Q% G7 Gpd.Timestamp('20200907') + pd.offsets.BDay(30)
    0 G4 |0 d0 r. \- w+ C: {) X. y& C* wOut[83]: Timestamp('2020-10-19 00:00:00')$ n+ J: _# i, Z( A" r' z
    1
    ! U3 k/ a( N, I# j/ X$ p; _) @2  a1 ?- {* L3 m& J
    3
    ) J- R' `, V9 |' J& }4 X3 r, n1 J- t4
    7 {' v/ O* ?$ s9 [5- X) G& H7 B% K  Y1 u0 G# D
      从上面的例子中可以看到,Offset对象在pd.offsets中被定义。当使用+时获取离其最近的下一个日期,当使用-时获取离其最近的上一个日期:
    $ e2 K4 J- m+ U; W/ K) H' e  l* D  M0 ]! }& H# E
    pd.Timestamp('20200831') - pd.offsets.WeekOfMonth(week=0,weekday=0)
    ' }$ ~9 x5 A( J& t% U  UOut[84]: Timestamp('2020-08-03 00:00:00')* b" V9 t0 `1 u5 e1 J2 R& N0 M0 C

    7 ?+ f. z! o6 h! h! V9 Vpd.Timestamp('20200907') - pd.offsets.BDay(30)
    . d4 \" Z: ]! ROut[85]: Timestamp('2020-07-27 00:00:00')1 ~- T) _% f0 A6 [1 E' f0 h

    + z2 x0 Z; f) ]; wpd.Timestamp('20200907') + pd.offsets.MonthEnd()
    # [# r" |0 U9 _4 T6 fOut[86]: Timestamp('2020-09-30 00:00:00')
    5 u7 s  L; Y4 `: M& |0 {1% \% H$ J$ L$ L7 o* P! G6 {* c
    2$ f4 D, d; z1 c# H: u7 f# g/ O6 n
    3
    ) D# |) x% D0 {& ^/ g0 O5 p3 D4
    # o8 G3 a; Y+ _: Q# |5
    % F+ `* j$ ?8 L0 B8 i# c% f) o6
    ) ~& ~# ]8 A% Y* n$ s: u7
    # j8 T- d  b9 \# _' y8
    5 o7 ^6 }$ n# a  i( [( g4 z  常用的日期偏置如下可以查阅这里的DateOffset 文档描述。在文档罗列的Offset中,需要介绍一个特殊的Offset对象CDay。CDay 或 CustomBusinessDay 类提供了一个参数化的 BusinessDay 类,可用于创建自定义的工作日日历,该日历说明当地假期和当地周末惯例。
    : H7 X( e8 P( i5 U! T  其中的holidays, weekmask参数能够分别对自定义的日期和星期进行过滤,前者传入了需要过滤的日期列表,后者传入的是三个字母的星期缩写构成的星期字符串,其作用是只保留字符串中出现的星期:
    ( D  f4 h/ o8 i! L, X- y! Y9 |2 d/ t7 c) Z1 v
    my_filter = pd.offsets.CDay(n=1,weekmask='Wed Fri',holidays=['20200109'])
    * q/ V  x3 ?4 Cdr = pd.date_range('20200108', '20200111')
    $ i  \. [- E- e- k% X( P4 s
    / p8 g/ d; q7 ]dr.to_series().dt.dayofweek
    4 L& H; `2 V. K% ~% V# s8 kOut[89]: 3 J; K6 u. Z6 G3 G7 \
    2020-01-08    2
    3 a, o, a" X( p+ W2 }7 R5 A2020-01-09    3
    $ U4 T7 n: I( F; N/ D; J& Q2020-01-10    4, ~6 m  V# n1 W! K
    2020-01-11    5
    & ~, M( ^/ R/ bFreq: D, dtype: int64  @4 r( X. `' d4 D& i3 q6 G

    ; n( u7 z9 a. Z1 G8 ]& p[i + my_filter for i in dr]
    8 U$ `/ N0 u/ a3 Y* D( U/ u1 hOut[90]: / L- Z, d+ I! D# Q2 g+ F3 e0 F; K0 I  s
    [Timestamp('2020-01-10 00:00:00'),
    + a. m  Q3 i0 c/ A Timestamp('2020-01-10 00:00:00'),
    ; P8 F* |2 R& p) T  N/ O Timestamp('2020-01-15 00:00:00'),
    ) a! X' l/ g; V  {( Y) V$ m1 H; s Timestamp('2020-01-15 00:00:00')]
    1 ?- D- c% M' V# X6 f
    # V3 l+ q) I& ^6 F0 _5 b& u  m13 T3 }% J+ j- ]6 }+ w) j* K* F
    22 ~* C0 O. S1 F( L; r6 j
    3  ]  }0 m5 T" B8 G0 w; T/ _' S# V
    4
    / N9 Y" z6 Y+ M- I; B) `9 Q5" z" F" [/ n( C/ Y( e: |- ~+ p, Y
    68 v8 w6 Q; l; m- z9 R, O7 X) b& G
    7
    % Q) a( K6 b: \# T+ Z% M5 y8" W8 ^2 F8 [9 u' S7 T/ e+ Q
    9
    3 @7 I/ C3 e+ ]10$ F! ]. e. L% U# f: o* o: A5 l
    11
    5 l: |# c  m$ e% a12
    / `) d9 k$ R/ D# X+ h  W1 c% w13
    & Z3 Q6 j* P! k2 _9 I2 W# M; z14% B! K# E3 F& n0 N& f+ V
    154 `$ Q5 j" H" Y# E) V. C
    16! [5 V, C: p. W9 A% d: d
    17; @" d1 Z+ ]. n& W" \/ K
      上面的例子中,n表示增加一天CDay,dr中的第一天为20200108,但由于下一天20200109被排除了,并且20200110是合法的周五,因此转为20200110,其他后面的日期处理类似。
    $ c3 N' W) I! v+ m& E
    ; `2 l" K: Y5 C【CAUTION】不要使用部分Offset
    & F) d+ P+ _! Q4 b& @" |在当前版本下由于一些 bug ,不要使用 Day 级别以下的 Offset 对象,比如 Hour, Second 等,请使用对应的 Timedelta 对象来代替。
    4 [* V4 w. L2 J4 v
    * ]& W% ?0 s1 b- r: z2 S10.4.2 偏置字符串
    / \& O: Z, D9 |5 J. T6 v  前面提到了关于date_range的freq取值可用Offset对象,同时在pandas中几乎每一个Offset对象绑定了日期偏置字符串(frequencies strings/offset aliases),可以指定Offset对应的字符串来替代使用。下面举一些常见的例子。! F1 g+ Z+ a5 |, E/ q
    7 [- c2 D8 d0 ^$ [2 \
      Offset aliases:pd.date_range函数中的freq参数,为常见时间序列频率提供了许多字符串别名。 也称为偏移别名Offset aliases。偏移别名列表点此参看(大概27个)。
    2 a4 E0 g. G0 H% `6 l4 K/ U+ ~( Q1 T- ~& T" G, a: E! y
    pd.date_range('20200101','20200331', freq='MS') # 月初
    / @  V3 z9 g* DOut[91]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS')
    1 K# j% ?" e) f- D# j5 n
    ! ]/ K# O! w; s9 d7 }8 Bpd.date_range('20200101','20200331', freq='M') # 月末: m, \. g7 C7 Y/ n" I$ I
    Out[92]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')
    % Q& e0 [% E1 ~! b, X& d. [/ q1 T0 {$ Q. [; \1 w, T1 [) n0 i
    pd.date_range('20200101','20200110', freq='B') # 工作日
    + I9 Q% i4 j) C& ~( g% ~& KOut[93]:
    + m" F8 N/ D! n/ T4 P# Z3 J% @, BDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',4 j- w# A( m9 `
                   '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10'],
      Z! C5 E) s( ?1 M# g2 V2 o& S              dtype='datetime64[ns]', freq='B')+ b; j7 M! i% I1 y
    & @7 {5 k: j4 j& r
    pd.date_range('20200101','20200201', freq='W-MON') # 周一+ J) o' b, W8 h1 b9 B- Y5 B
    Out[94]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='W-MON')
    6 k( t- [2 B( j$ W0 S. R! Z4 I: O% M# s2 y, E  k% M/ F
    pd.date_range('20200101','20200201',( R  w. d8 }' m/ X7 c9 S
                  freq='WOM-1MON') # 每月第一个周一
    1 ^+ I' J5 g9 M8 z4 A+ e
    9 f" @$ h# L- M6 H, HOut[95]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    ( _* P2 T: N6 B5 {) e+ Y* W+ g  y7 e0 N
    1/ o5 ~5 d/ G1 e* t4 ]$ ]9 z6 N: K
    2* g2 V# [( E) D4 s
    34 C- |# U& L1 S4 y( s& |  ~& b
    43 m. h- U0 ^/ G- p+ t
    54 M4 B( S0 g7 T+ m
    6( y  Y5 r4 D: S4 k& l
    7$ Q) [, R% j; E; N: W! p3 X
    8
    % O3 x/ ^; j. {% @8 m- O9
    & _' y, Y- p# |' O% }& h3 b1 ]5 V$ l10) p0 j' E( Y6 Z. w( `
    11
    - k' e* N9 {9 M& C5 L5 x120 E7 T3 o; ]8 }9 ]& Z  X$ s" E
    131 Z$ Q( Z& P8 N" f. w9 i
    14
    9 T" Q- ?! C7 T$ [; ]15
    3 |3 S. j% F+ X0 O$ ]* z6 Y16. a9 x, Z9 L5 i5 m* k9 G
    17
      u" p, b  k) r2 Q: m4 B) u' a18
    ) _1 N1 D9 m, _. R19) i8 K' S$ Y3 x/ `$ }4 P# l
    上面的这些字符串,等价于使用如下的 Offset 对象:
    ! u7 Y1 D8 ~6 T. j
    6 |( f  G3 _% r) S0 O* s2 Ypd.date_range('20200101','20200331',7 w% d6 {1 J1 U& ~
                  freq=pd.offsets.MonthBegin())
    + m+ K$ i8 R& {/ R
    , u1 N9 n  j% [  y( [/ y* ~$ xOut[96]: DatetimeIndex(['2020-01-01', '2020-02-01', '2020-03-01'], dtype='datetime64[ns]', freq='MS'). {* q$ I# ?. X9 p' g" `/ _

    3 E7 q- ?) C2 @9 R9 v1 h1 ypd.date_range('20200101','20200331',+ X" s5 G) \3 g) F
                  freq=pd.offsets.MonthEnd())& a/ d6 z+ V8 W5 ]% v

    ) X9 B, x( [3 B! a' s, KOut[97]: DatetimeIndex(['2020-01-31', '2020-02-29', '2020-03-31'], dtype='datetime64[ns]', freq='M')/ m$ ~& U# w9 y
    , H3 ?( U2 N( k
    pd.date_range('20200101','20200110', freq=pd.offsets.BDay())
    - F. A: w& F$ AOut[98]:
    ; b5 D; k" \+ h* X! U2 }1 W. x) x* W+ iDatetimeIndex(['2020-01-01', '2020-01-02', '2020-01-03', '2020-01-06',
    # A/ k0 r+ t1 ~6 T' n               '2020-01-07', '2020-01-08', '2020-01-09', '2020-01-10']," H. c5 h& r0 ~3 L% M( o% I! X
                  dtype='datetime64[ns]', freq='B')
    : X4 A0 @! Y+ U2 V; E$ D. C3 z0 N9 e
    . o2 J+ t; \- \0 g& d; F" wpd.date_range('20200101','20200201',
    9 S3 T, g2 ], H              freq=pd.offsets.CDay(weekmask='Mon'))
    " l) ^1 \: b+ Z, f: u0 Z, J/ `$ d; c! r
    Out[99]: DatetimeIndex(['2020-01-06', '2020-01-13', '2020-01-20', '2020-01-27'], dtype='datetime64[ns]', freq='C')
    6 X( i9 D1 z) E. b5 J$ W: G- m6 d' W: x1 w: I7 P9 C
    pd.date_range('20200101','20200201',
    ! y" G# e+ i8 W5 o5 a* g2 Z              freq=pd.offsets.WeekOfMonth(week=0,weekday=0))
    5 W1 l, P1 Z. Z& l; z# |4 }( Z) q& w0 D* Q
    Out[100]: DatetimeIndex(['2020-01-06'], dtype='datetime64[ns]', freq='WOM-1MON')
    # w1 `) R0 E5 W" G  {5 ~
    4 Z8 U9 z8 W0 H& j18 s2 f: t0 S. v% e% G
    24 S  _& l. \( ?( L
    3
      ~' V9 c% S% t8 [% u0 K/ v4
    % J/ ?3 o5 F6 O/ B1 Y- H; |5, N7 {1 e% L1 [/ J4 `
    6
    4 d) ?6 N% n* d72 q# _' o, o1 @; D; y
    8" t8 B6 l! e/ Z6 H2 ]/ ~
    9
    3 C3 |+ D/ ]( @4 n: C2 F$ W10* ~* s' h- d) @. X
    11
    2 G) f6 O7 _1 \; B1 R8 t. e12- U- i! Q& R8 q3 z0 I" A" h
    137 T; E9 s2 E; Q) [' t
    14
    ' m1 D5 }- r6 U/ V15% T8 ]) Z# U1 T/ _) s
    160 `: S( G/ s% T! H, h0 k/ A* @
    175 m" o6 X& Z% B+ k7 `% w
    18
    ! v8 h# m0 S' I19
    + W+ t% n; ~4 d; x' ^5 |20
    , [2 N9 R( P: a  W( l; H+ i218 ]/ |# d  C4 r6 M  Q
    22& _8 v, Z" y+ w' E
    23
    5 T; f. K4 w. c246 H3 O  h% \( H! p1 Z
    25; w$ J2 U8 f' E% x
    【CAUTION】关于时区问题的说明
    # Q, Q7 a; U9 }3 p9 X! m% B) E  各类时间对象的开发,除了使用python内置的datetime模块,pandas还利用了dateutil模块,很大一部分是为了处理时区问题。总所周知,我国是没有夏令时调整时间一说的,但有些国家会有这种做法,导致了相对而言一天里可能会有23/24/25个小时,也就是relativedelta,这使得Offset对象和Timedelta对象有了对同一问题处理产生不同结果的现象,其中的规则也较为复杂,官方文档的写法存在部分描述错误,并且难以对描述做出统一修正,因为牵涉到了Offset相关的很多组件。因此,本教程完全不考虑时区处理,如果对时区处理的时间偏置有兴趣了解讨论,可以联系我或者参见这里的讨论。& X! R, l9 \) v2 {( G9 J

    ; m/ ^! S0 a0 l$ ~9 U$ {10.5、时序中的滑窗与分组5 @3 P( f! W& B; R# f6 J
    10.5.1 滑动窗口
    . r& u8 C5 r% _& z" z0 c# C3 `1 V  所谓时序的滑窗函数,即把滑动窗口windows用freq关键词代替,下面给出一个具体的应用案例:在股票市场中有一个指标为BOLL指标,它由中轨线、上轨线、下轨线这三根线构成,具体的计算方法分别是N日均值线、N日均值加两倍N日标准差线、N日均值减两倍N日标准差线。利用rolling对象计算N=30的BOLL指标可以如下写出:: U. C5 G0 ]5 n9 t/ m2 e
    - ^: Z2 ]: q, ^4 z% F: H& U4 M$ g
    import matplotlib.pyplot as plt4 k9 k5 T  s1 Y% W
    idx = pd.date_range('20200101', '20201231', freq='B')7 |( K% \" ^/ g( R) g; M
    np.random.seed(2020)
    5 O5 a4 E, ^* O. \$ v: y% Z
    * T/ z6 G& {# D' @data = np.random.randint(-1,2,len(idx)).cumsum() # 随机游动构造模拟序列,cumsum表示累加( Z. F: d$ |' v
    s = pd.Series(data,index=idx)
    + O, y0 }& o1 K! P8 Vs.head()
    , Y0 s( q: W$ ^- [( ~6 sOut[106]: ; l& w- ]0 D6 W& l: X1 [8 R3 C
    2020-01-01   -1+ i& |* J" K8 K
    2020-01-02   -2
    / f. j8 \+ q, k2020-01-03   -1
    # u+ X" x" E, `' Y9 [8 v, A5 t2020-01-06   -1: `* _: s# F! f7 |. b& ~
    2020-01-07   -2
    & M/ R) Z/ R1 c& }  G  ?/ PFreq: B, dtype: int32% D; z+ [7 B* I
    r = s.rolling('30D')# rolling可以指定freq或者offset对象6 `# e- s( J9 r; t9 `) d

    . H. o3 L& W# V" @+ r$ Lplt.plot(s) # 蓝色线
    - l$ }4 I) u8 dOut[108]: [<matplotlib.lines.Line2D at 0x2116d887eb0>]# ]3 \' s% b+ l# C$ R- F# }
    plt.title('BOLL LINES')
    . K& k/ h6 A: _3 T+ j( p" xOut[109]: Text(0.5, 1.0, 'BOLL LINES')
    * ]4 k  J! x( j/ ]1 A  n& d5 H7 Z  F2 P7 ]7 {. @/ U
    plt.plot(r.mean()) #橙色线7 A2 T6 t% Z: Y
    Out[110]: [<matplotlib.lines.Line2D at 0x2116d8eeb80>]
    $ `% R" Z5 A( t8 b) H; h! y* \3 e7 Q! S$ p; e7 f. a* o
    plt.plot(r.mean()+r.std()*2) # 绿色线2 X, e1 A; P3 s5 C! V
    Out[111]: [<matplotlib.lines.Line2D at 0x2116d87efa0>]" i( d! z+ A& S4 _% ^/ X6 ^

    * ]% B: C0 v0 B2 Pplt.plot(r.mean()-r.std()*2) # 红色线
    ; q5 n3 q# S* ^/ `Out[112]: [<matplotlib.lines.Line2D at 0x2116d90d2e0>]
    ( F0 X+ d5 d, z* l# }
    1 J& b9 F  G9 S( |: |12 S% l  y, W/ a( s8 J; A* Y
    2
    ! U1 C+ o% A$ V4 J6 x9 X* {3
    2 }& A+ k! F( l8 o; k9 y2 M4
    " Q9 ]" _; S% ^, G% s( E5- {' T) n0 v6 G$ J$ x' o
    6
    5 l$ \% W1 ?$ w% J' Z; ?5 b& ]7
    * k) l" [1 I& ~8+ }4 d% _1 O- j& o6 R# j
    9( d& W1 q4 ^# U
    10: Y! p  B* @7 ^9 k1 p/ m7 u  I
    112 n+ c7 P0 r  b( y8 o
    123 A# Z; ~* }: ^
    13
    + N. f" l0 U: t% M* P  B, K141 o* ~4 z* ]; Z5 K9 [5 Z' C
    15
    " M+ p/ j. X  u16
    9 }% S0 e/ ^& r! ?% |17) j, G% _9 X. A3 z
    189 Y) i6 Q3 c6 o% S- R
    19
    3 t8 I2 Y0 ^' N4 w4 s20
    # S# X% x- ]0 s# [$ y9 Y: Q4 g210 s) ?9 C2 c& ?/ A! F# l2 ?
    22' H9 z0 ~2 \% @' g% d. `
    231 d  I* ?0 m! Q0 V9 O: `! E
    24& f. o1 |' g, o8 Q& \. `* Q
    25( o8 l" s$ n* I1 a( }5 x7 F
    26
    3 F1 m. {$ |, R- O5 O( h. x# p272 u1 P9 ], L' u3 A; {. V
    28
    ; }; E" J, E& [* }/ N29
    # M: q; k# x, k: f+ e$ l, @0 T/ l! W8 {% T  a+ @+ M$ g9 G
       这里需要注意的是,pandas没有实现非固定采样频率的时间序列滑窗,及此时无法通过传入freq字段来得到滑窗结果。例如统计近7个工作日的交易总额。此时可以通过传入多个函数的组合来实现此功能。1 h, T/ b+ p, }  t# y8 M
       首先选出所有工作日,接着用普通滑窗进行7日滑窗加和,最后用reindex()恢复索引,对于双休日使用前一个工作日的结果进行填充。
    6 I9 E; Y9 x/ h6 a6 ?* ?+ ^! A) t1 O1 h, D  H1 O6 B1 K! k8 l  _
    select_bday=s[~s.index.to_series().dt.dayofweek.isin([5,6])]' @9 m$ }* M0 q7 L
    bday_sum=select_bday.rolling(7,min_periods=1).sum()5 h/ U( x9 L8 S1 @3 }; q
    result=bday_sum.reindex().ffill()7 y  \  ^0 d& `1 S5 E
    result
    2 ^$ P0 `; V) ?2 w' S
    1 I7 @; S) j& q# W2020-01-01     -1.0* b  J. |! c4 e
    2020-01-02     -3.0# {0 Q; G6 w0 p' b: |5 @5 ?
    2020-01-03     -4.00 C. U2 [+ Z8 M
    2020-01-06     -5.0# q; L4 r0 {. v0 W
    2020-01-07     -7.0) }4 N. d" {) m! ~# M, V
                  ...  $ R) I. b8 |2 A0 `1 \) _* x
    2020-12-25    136.0
    1 }" A  [' E& Q2020-12-28    133.0( v7 Y- h! S+ L. U0 w- `
    2020-12-29    131.09 O5 q2 a) T. G% w9 U5 i
    2020-12-30    130.0
    ) |$ M$ w) ~7 `0 {  o6 D2020-12-31    128.0& R) }& p  ~+ E8 _3 V2 m' I
    Freq: B, Length: 262, dtype: float64
    : u6 `4 @! P# P8 V' y* F9 f% @" z: d4 d% C1 c5 ^& ]
    1( r% T. D0 d6 b# k$ Z/ e& i+ |
    2
    0 A/ |7 S5 ?. A* R6 r/ n3
    % V  B4 C3 r6 k8 E0 o: g0 S2 U4
    * E  ^/ G' H" d2 j! f) q5! b8 w5 e6 f" n2 z9 r1 b/ ^8 o
    62 ~3 s+ u* Z4 d2 ^& A: m
    7) c, ?9 P  u+ W" b* _# z, k
    8
    ) s0 k3 O2 W5 {  R9
    ; W9 K1 z) z; Y$ Z8 B$ v103 d# q# ~( K/ I( Z7 x9 x
    11
    7 i3 L% f9 M: U& h( K12
    ; k4 h- F" j- [$ V5 p0 I+ u13+ j3 Q+ n5 G0 d* c' M/ |
    14
    # @2 V% ]8 P, B" \$ Z* h15  X( [( v1 N: U# I
    16' J  P: l, t3 X5 [6 z4 u7 S
    17
    ( b# ~, f. t( j% J- a- W3 p  shift, diff, pct_change 是一组类滑窗函数,它们的公共参数为 periods=n ,默认为1,分别表示取向前第 n 个元素的值、与向前第 n 个元素做差(与 Numpy 中不同,后者表示 n 阶差分)、与向前第 n 个元素相比计算增长率。这里的 n 可以为负,表示反方向的类似操作。
    , y" u! p! |- O& r/ n- g
    5 y) P9 ^3 e, i2 Y- M  对于shift函数而言,作用在datetime64为索引(不是value)的序列上时,可以指定freq单位进行滑动:
    % c; n' a) T/ W% S* m7 K* J+ n* u
    s.shift(freq='50D').head()
    7 r% i% S+ k$ a( E0 JOut[113]:
    % b  L4 L3 R0 i+ J7 n2020-02-20   -19 P& q, k; {7 X# a( I0 }/ w+ U; Q
    2020-02-21   -2
    / [, r  {. \6 X( L& K, r2020-02-22   -1
    . ?9 I2 B, p& Z6 q& O6 \" T2020-02-25   -12 k/ L' N) @; d
    2020-02-26   -2
    + l; ?: Y' a3 J  u9 i( z( Adtype: int32$ W4 ]6 |( ~* T. v' C6 c8 t* U9 E
    1
    * D8 S/ w8 h* t4 O2
    $ N2 L) Y1 v+ R3
    2 E$ N/ N! }) \, s, F4
    + x8 s7 T7 d# U. p! u5
    ( o# A- Y# x4 h; r4 X65 [* z+ k  A. L
    7
    . O: j! w! g2 I8
    0 A6 y. T- @! }$ y8 ~9 u  s  另外,datetime64[ns]的序列进行diff(前后做差)后就能够得到timedelta64[ns]的序列,这能够使用户方便地观察有序时间序列的间隔:& p# s% V3 e% i+ \
    0 |8 u; l7 ^( p
    my_series = pd.Series(s.index)
    7 R3 S' r! i  O5 _my_series.head()3 }4 \* H  V; R8 ^
    Out[115]: & y2 F- F4 D" S
    0   2020-01-01) E9 d4 c, K% r/ M! h- |: I# g
    1   2020-01-02
    % Q( N, e8 w4 G$ X/ g; e2   2020-01-03$ Y: _9 |5 Q  X4 P, I
    3   2020-01-06% l0 Z- K' K+ _' P- e
    4   2020-01-071 ]3 q/ ]) {4 O  D9 }- L5 s
    dtype: datetime64[ns]+ @- T( y  P8 u: x# h7 m! u
    % m9 A* ]4 u( ^. \% ?% R% O# }9 E
    my_series.diff(1).head()
    3 T$ E! ~7 c6 D/ ]' Z2 x1 aOut[116]:
      z9 W2 R) q! g0      NaT5 w: u, v7 f) y! f; ?- L2 K' v
    1   1 days% U+ D/ [/ o5 Q3 O
    2   1 days5 }7 l9 E, Q0 ~/ M, z
    3   3 days9 ?$ w' j( d3 @! u7 f% K( C$ q
    4   1 days* w2 H/ A  Z3 V4 n% `0 q0 ~
    dtype: timedelta64[ns]( {8 t: l/ R7 U

    + {% g$ l5 @8 N; R$ A5 l1
    5 H8 [9 L% d# i: v2
    ! ]; @% r) `" q2 M" O3 a! V3
    2 Z2 r* S% Y5 o6 }4
    ; Z  K3 L8 c! ^8 J5 |9 [3 D/ c* ~" J5
    ; J6 ^# \& n9 e& S" S% T$ e; k& e6
    " i% g5 _9 e6 A* A& k/ `' s+ U7- }1 _0 E) S; k: {5 a
    86 Q* |9 ~. N  b, N& l
    9
    ( Y# v( N9 G4 j3 |& X0 k; p101 k0 k6 B4 j" e' c: E, g, h
    11! y4 ?8 A9 A7 \4 b
    127 R9 t+ N# }- m( _8 M, H3 p
    13
    $ s( y0 ^1 H0 z2 V14
    # l: ^4 E! g7 r6 A; ?152 ^( L/ \5 _" e- p) i" {# u* ?
    16
    # Q; _6 {9 r  u4 h( ?. B0 s- y172 k' p# e+ E, v: W
    18
    0 A4 m" A: i% Q  t% @10.5.2 重采样# ~1 g: ?5 D" f1 y, f' }% V. S
      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)
    / g# p# @% k9 `% e常用参数有:" x% u9 l6 t7 G. O$ @- o. U
    " P, x& T% ?3 c+ \. b3 ]" U
    rule:DateOffset, Timedelta or str类型。表示偏移量字符串或对象. Y; f+ e$ P. Y! ^1 g  J% g
    axis:{0 or ‘index’, 1 or ‘columns’}, default 0。使用哪个轴进行上采样或下采样6 A$ y# W/ l9 E" a3 s/ U
    closed:{‘right’, ‘left’},默认None。表示bin 区间的哪一侧是闭合的。所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    6 ~4 a  e" \9 K* D  e" jlabel:{‘right’, ‘left’}, 默认 None。hich bin edge label to label bucket with,所有offsets的默认值为“left”,但“M”、“A”、“Q”、“BM”、“BA”、“BQ”和“W”均默认为“right”。
    + m: n% V& f% e2 K6 {convention{:‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’。仅针对 PeriodIndex,控制是使用rule的开始还是结尾。/ Z( {! A5 r" ^
    on:字符串类型,可选。对于 DataFrame,使用列而不是索引进行重采样。列必须类似于日期时间。& P8 k% K2 B$ M
    level:str 或 int,可选表示多重索引MultiIndex的级别,这个级别的索引必须类似于日期时间。) R0 O. c( t2 U' @3 G+ Q
    origin参数有5种取值:
    + Y9 n5 z9 M6 B6 N( F‘epoch’:从 1970-01-01开始算起% w+ y6 `: j, B1 B4 _
    ‘start’:原点是时间序列的第一个值8 G( Q3 j; z, g! x% s) B
    ‘start_day’:默认值,表示原点是时间序列第一天的午夜。* g# z0 y: W* Y: X4 [' G. P
    'end':原点是时间序列的最后一个值(1.3.0版本才有), ?- w+ ~: h6 W% j1 C+ V
    ‘end_day’:原点是序列最后一天的午夜(1.3.0版本才有)9 M$ d+ x' v/ U6 p  b
    offset:Timedelta 或 str,默认为 None,表示对时间原点的偏移量,很有用。3 C& V6 e7 Y/ S, Y' d. j  ]
      closed和计算有关,label和显示有关,closed才有开闭。
    ( m* N' A/ ~; K/ G, @  label指这个区间值算出来了,索引放区间的左端点还是右端点,closed是指算的时候左端点或右端点是不是包含。
    ; \3 U- }$ x. G8 s  G! W4 ?/ O  r+ k) n# e5 D
    重采样对象resample和第四章中分组对象groupby的用法类似,resample是针对时间序列的分组计算而设计的分组对象。例如,对上面的序列计算每10天的均值:8 m/ r4 {$ A# ?+ x+ d
    s.resample('10D').mean().head()
    4 `$ K/ [' ]& ~5 C7 \7 AOut[117]: % F! F2 }# j- Q# \1 K  Y
    2020-01-01   -2.000000
    ; s8 r0 |3 c  U+ W2020-01-11   -3.1666676 D1 @! u' _  `6 N/ ~& U& L
    2020-01-21   -3.6250009 q& w4 i7 K/ f! i8 o. K9 I
    2020-01-31   -4.000000+ ]+ C5 L% _" `* x+ X* [
    2020-02-10   -0.375000, l+ @% i% K0 Q5 O& C
    Freq: 10D, dtype: float64, D* `& K, ]/ J) \
    1
    % X( h; _9 D# ]. B2
    * I+ s9 m, v% [: c5 G% K3& X# K8 @5 p6 o0 I
    4: B4 y8 C* h4 x
    59 Z3 `6 C5 s! H$ {4 r7 _
    6
    $ y  A6 L. o& Z9 k& u; b2 m75 ?$ X! r, L7 H3 Z+ s. N2 |6 H3 L
    8: O5 t- C; J( D/ h  q2 A1 |/ H/ f
    可以通过apply方法自定义处理函数:' T/ @; m, R) {7 |/ ~% D
    s.resample('10D').apply(lambda x:x.max()-x.min()).head() # 极差' D" E9 O* H0 @. o. P, R
    9 b3 B9 `' c" T. G
    Out[118]:
      V9 I4 o* v' ]. |# t2 w2020-01-01    39 R: s9 R* U3 b! j7 Q
    2020-01-11    4$ L% y6 z9 I0 T. x$ B* |
    2020-01-21    4+ M* S, G& y" \9 N8 L
    2020-01-31    23 a9 a( q9 C9 g/ N; X9 f8 I& M: e
    2020-02-10    4% C+ n8 ~0 v; G$ S* V, L
    Freq: 10D, dtype: int327 e+ ~; @+ G3 u3 R5 M2 Q' j5 h
    10 Q1 ~' m, R' z* n' u7 `: u6 {
    2
    $ e. c# V* `# [3 O. v) p6 l35 _7 M- `# q! Y4 T( N
    4
    " {$ m4 U% B& ]$ s55 |, a* K0 y9 W  D, _
    6& |. _' f8 I8 E" S0 C1 Q
    7
    ; U  o, t5 P/ A5 `! K81 S* H. f# @& D! g! T6 \: t
    9( c% R( J  N8 e& F. r3 A+ k- g9 l
      在resample中要特别注意组边界值的处理情况,默认情况下起始值的计算方法是从最小值时间戳对应日期的午夜00:00:00开始增加freq,直到不超过该最小时间戳的最大时间戳,由此对应的时间戳为起始值,然后每次累加freq参数作为分割结点进行分组,区间情况为左闭右开。下面构造一个不均匀的例子:8 r* a- G) e# t' j% }' t
    ; I, F5 F$ u, d/ y2 V
    idx = pd.date_range('20200101 8:26:35', '20200101 9:31:58', freq='77s')
    6 y1 a8 Q0 d" a5 Sdata = np.random.randint(-1,2,len(idx)).cumsum()/ p1 \6 U# E* N0 v1 P" y
    s = pd.Series(data,index=idx)
    " n$ w; U: `9 ls.head()
    . f( d) S' k+ t6 `/ @8 {8 U9 ~$ t  K1 a  a5 \7 D+ {  x! y8 L3 m
    Out[122]: / r* M( r9 c% b* C3 m
    2020-01-01 08:26:35   -1
    ) l6 q; i7 [0 y/ L! i' f7 o0 S8 W2020-01-01 08:27:52   -13 @5 g" y" i$ ?* ^3 `" J  Z
    2020-01-01 08:29:09   -2
    % B0 Z- O  {' F9 f& x6 N) Y/ z" Q2020-01-01 08:30:26   -37 M# @- ?  T1 V" _: w& n
    2020-01-01 08:31:43   -4
    - J9 w& N/ `2 Q# c+ ~5 n* dFreq: 77S, dtype: int322 C! i; [! }6 f3 Y
    1
    * G! P+ ^  W' e1 H; ?* ]' k/ Z: c2
    $ {9 _2 T# u, a2 ?( f34 ]* _0 {7 s5 V
    4
    9 o' O. w/ j4 a" X. c56 d# x7 t# ]! P; _) i
    6
    ( c, T; s8 G% k/ H* r7
    ) T4 D. F& P  l' R9 m! }7 f8+ J! }/ c* I, K+ h
    9
    0 q6 [  ^9 F+ \. j$ @10! [. A- w) Z, h5 F8 J: Y
    119 z8 o! T3 y4 F9 s' \2 H
    125 E" I7 \2 c5 e8 W
      下面对应的第一个组起始值为08:24:00,其是从当天0点增加72个freq=7 min得到的,如果再增加一个freq则超出了序列的最小时间戳08:26:35:
    ( n( ]4 B; U: R* V7 \9 k/ l9 G7 i+ l* e7 W7 Z, O& N5 U9 o
    s.resample('7min').mean().head()) a/ c+ n" Z8 k4 L! I. E
    Out[123]: $ _$ R$ t* v: R* e5 y8 G
    2020-01-01 08:24:00   -1.750000  # 起始值,终点值包含最后一个值
    % E, t; w7 o* B) z0 W- e  C2020-01-01 08:31:00   -2.6000008 @- @4 h% k- C8 I
    2020-01-01 08:38:00   -2.166667
    ; G4 |% V. h  l, i0 Y7 P2020-01-01 08:45:00    0.2000002 Q: }0 h/ A/ P, F" l, p5 D; t! X& d
    2020-01-01 08:52:00    2.833333. R; L# h7 U5 V: m& r3 _
    Freq: 7T, dtype: float64
    . A8 S! s0 h! \: {+ F1
    & a; @$ g6 `: P% E" ]$ ~/ l2) b% V5 O+ O. [
    3
    / |. h  W, S  _  Y/ r, w4
    5 G- c& z8 s4 o$ C3 F1 u5- q: W4 L% H5 j6 ?2 w, ^
    6
    . K- w1 h) n! ~3 e7* C5 b6 ]* v7 w0 r5 f) W! v
    8
    * w: p$ w$ j* O- J. ~  有时候,用户希望从序列的最小时间戳开始依次增加freq进行分组,此时可以指定origin参数为start:' m% C: a3 J% S9 u7 E( c5 b1 @
    3 Q9 n( o, K+ [/ ?$ V8 N
    s.resample('7min', origin='start').mean().head()
    : N7 C, o9 m2 I4 Q+ e! L* YOut[124]: 6 I5 c2 o* X2 g8 D3 w  z
    2020-01-01 08:26:35   -2.333333
    & s4 D4 o2 J1 [# A' g6 ~3 E2020-01-01 08:33:35   -2.400000) G/ C3 h. P' L8 R+ v0 i) I- c
    2020-01-01 08:40:35   -1.333333: ]& ]/ Q1 }! {! h# R0 s8 _. r+ ?
    2020-01-01 08:47:35    1.200000
    / B2 s+ z: b9 N3 B/ a; J2020-01-01 08:54:35    3.166667
    # i3 k3 D2 n1 E7 RFreq: 7T, dtype: float64
    " q1 q3 [3 i. L. q/ L3 g1
    8 y5 A1 K$ [& u8 P2
    # B8 T3 O5 A  d3' e/ F# k7 b2 x- S4 E4 Y0 C( F9 W
    4& i0 L0 O8 Z: ]- U$ ^
    5
    + Q% i* Q9 |- v9 Q% ^% w6
    " c# b1 G4 x; c' _7
    % d, t# ?8 `5 j, A8 j8 a6 V/ D6 m8
    3 N7 Z* `2 b' h0 w0 G, m  在返回值中,要注意索引一般是取组的第一个时间戳,但M, A, Q, BM, BA, BQ, W这七个是取对应区间的最后一个时间戳。如果想要得到正常索引,用’MS’就行。( w( [6 V/ }. B5 C3 L% w
    5 r' F9 T/ j& ~
    s = pd.Series(np.random.randint(2,size=366),
    ; ^2 r" f" ^6 ]% K: R7 M              index=pd.date_range('2020-01-01',
    ; `% q0 m/ ]6 N3 ^/ e                                  '2020-12-31'))
    4 ~6 T7 k3 w8 b2 [8 D$ @/ P7 p2 @: F2 S
    2 s- ]% y& s% U6 s
    s.resample('M').mean().head()' `7 H& I9 n7 J1 R8 w  y
    Out[126]:
    : W! A( V6 ?9 ^( j' `2020-01-31    0.451613
    5 Y, E' I) g8 |* I- o2020-02-29    0.448276
    2 f  `! s) p8 i2020-03-31    0.5161298 C2 Q# r1 m4 A: v5 e/ M
    2020-04-30    0.566667
    + @7 j. i0 X, L6 l9 l# f9 w2020-05-31    0.4516139 @, x- _' O" _& n* e" P# |
    Freq: M, dtype: float64
    9 v! }0 U+ a4 U* m$ y
    4 p8 I$ p" n  _- J2 P& Q1 q  ?s.resample('MS').mean().head() # 结果一样,但索引是跟正常一样
    / E( |) r' u7 l% E3 T9 ZOut[127]: 2 Q: \2 J; c$ o1 h7 ^) Y$ ^8 k+ C
    2020-01-01    0.451613
    % S& _6 u8 Z4 b2020-02-01    0.448276
    . e) a0 q) l" l7 @1 A/ m2020-03-01    0.516129
    , J& a  }/ V0 `$ E2020-04-01    0.566667) [7 o* Q. c2 L9 w" G6 Y- r
    2020-05-01    0.451613' `, e" e8 p" u# k5 v6 {
    Freq: MS, dtype: float64+ i# a/ P1 r, J' ^
    8 s2 z' T! U8 O5 a5 o+ r, c+ t
    1
    9 U6 g, j7 u2 j) |5 I) |& l* I( ]27 E: {7 _; W. Z( O
    3
    0 ^- E0 A. O( z8 t4
    4 p6 s1 M* d7 \7 L1 e; \3 k" y( I5+ G& Y# ?+ x6 o& x/ v! p, A) g: V
    6- u# g) ~) \& R
    7
    - S- [- k% E# g' o+ _4 k5 n8
    ! y; V9 P8 c8 {! w! H% |" }: m7 O9
    6 J2 m9 W4 m" n- S/ S1 _, _" a9 Q10
    . [3 x7 n! T/ d/ Y- a- U11( z) [) g- I6 f1 C
    125 a5 e# x4 ^/ j  b$ o
    133 R) @6 n3 J$ J# b* i
    146 C; _, q) W' k2 F( m. _. ^7 d' o
    152 m% p' q% L! K- {; V0 N. o$ @
    16
    + c9 [- d6 D0 ^* v17! [! \- ^5 Y% N' V  |
    18
    9 i/ `+ }; h) ^' J" z& G1 e19. r' x3 z: v; S
    20& M' ]9 L, k$ `" K3 f% k
    21
    . w, _+ [" U3 }! ~3 [& ~* C5 s* Q220 n' H5 n: o1 r9 T* ~
    对于 DataFrame 对象,关键字 on 可用于指定列而不是索引以进行重采样:
    - e4 P9 {" `- ]  e9 U5 _d = {'price': [10, 11, 9, 13, 14, 18, 17, 19],5 t# X( f% v4 x* o
         'volume': [50, 60, 40, 100, 50, 100, 40, 50]}& o( E  o) Q6 [# A# X/ F4 X, S
    df = pd.DataFrame(d)
    , b. X$ N& i9 ], Zdf['week_starting'] = pd.date_range('01/01/2018',4 Y9 `8 v, _% Z) B% w1 H" I
                                        periods=8,
    ' F: y& P& J+ |& O2 z1 z# p( y                                    freq='W')0 K& L: l3 {1 J% H, {
    df
    6 @/ Z, N1 f. T! C5 K+ z  ^   price  volume week_starting
    3 g; {- q9 h: {+ b' s! @0     10      50    2018-01-07
    2 [, W3 }0 W8 g- [2 T3 Y2 c- q, X1     11      60    2018-01-14$ \, M$ t$ y% i) i' w& b1 R
    2      9      40    2018-01-21
    ( r8 Y! n) x% Y3     13     100    2018-01-28. y, w% i/ K0 J1 R2 \' [: _5 G6 E$ s( y
    4     14      50    2018-02-04, ]. [& G, ?! N2 O2 n
    5     18     100    2018-02-11
    * ^0 M9 I7 C, u' c; M, ^6     17      40    2018-02-183 H# M- O* t) V/ I
    7     19      50    2018-02-25
    $ C4 z4 R# B; ]( s0 R- I3 n+ o; qdf.resample('M', on='week_starting').mean()
    ( a# t) u, H/ C  A( ]9 M( }               price  volume
    ( z: N, {2 c* e3 qweek_starting0 S" v# v7 v) [+ ^8 l; _1 N
    2018-01-31     10.75    62.5! g4 R$ K7 ]" E# ~3 U
    2018-02-28     17.00    60.02 [& L. ]- f# p6 y
    - E7 U+ y" S4 d
    1
    + R+ H( L; p  R7 G) z1 M2
    - I2 W' L& y3 w/ ?9 ]! q2 `0 Z3 D3( G% ]' _( C. G& Z* @" W. C
    4
    2 r0 ~& r: g- ~# f6 P. X4 f5& c" |0 e* j$ S
    6
    % {  l/ j3 y4 _, E9 B) s- K70 E, T( E3 w: @& M4 \
    8
    " Z: }* V: m8 S* v% T/ V4 b9( \8 \8 H, Y3 Z
    10# V" i9 H0 m$ V7 L# i* ^) L
    11  i, X% z6 V  x( c* d6 x. i" _; G+ y" U
    12
    - Q6 }: ?1 D0 Q$ K8 y13: _4 N! R2 i' H1 B$ T. J" p# `/ J7 f
    14
    ' Y/ B  V) h4 W2 N8 N- v) s15& M( {! c# B! V0 z
    169 J/ D! R' a. v
    17
    2 r. L  f# o% O+ S& Z! U( m4 [183 x& G6 U9 E5 F0 p3 y9 {
    198 Q  [  i; s4 q( g) N9 |
    20
    + X/ f1 @3 v; {; }0 }- v# @217 v8 h4 x! b6 d! [  G3 O- V# J4 W% d
    对于具有 MultiIndex 的 DataFrame,关键字 level 可用于指定需要在哪个级别进行重采样。! n# T4 t8 m0 p/ p: @9 B. ?
    days = pd.date_range('1/1/2000', periods=4, freq='D')
    # Y9 f# M9 V7 p0 |d2 = {'price': [10, 11, 9, 13, 14, 18, 17, 19],
    ) S7 Z. }" P2 M8 ^7 M9 C      'volume': [50, 60, 40, 100, 50, 100, 40, 50]}! [1 c; N& _' |; g# ?4 |
    df2 = pd.DataFrame(7 @8 R7 [* N) L( c0 q
        d2,) N7 v) T7 B, \) f6 c: P- F
        index=pd.MultiIndex.from_product(' K+ c; i6 k4 d
            [days, ['morning', 'afternoon']]: [# w* P7 J3 U4 H6 H0 M
        )
    5 A! V/ [7 P7 w! D; ]) E)
    9 }7 }+ ^6 A3 O" G; t/ e2 ydf2
    . }2 K. D% C0 ~* ^                      price  volume: S% T, [+ u$ J5 d# ]# n
    2000-01-01 morning       10      50/ I9 W" K" Z' m% }
               afternoon     11      60
    ( x- D% J2 v0 c% l2000-01-02 morning        9      40
    " x" B  r4 ]2 u2 s           afternoon     13     100
    2 {6 ~) n% |  J+ O6 a2000-01-03 morning       14      50  ~' U, s4 H. j. f
               afternoon     18     100  K7 {; t6 @4 D6 f, ^/ R
    2000-01-04 morning       17      409 j# K9 y+ n( f2 A, r
               afternoon     19      507 ^  A( K! l3 a% ~: H( p
    df2.resample('D', level=0).sum()
    , X( x1 C& @# I+ Z$ H6 ?" _( N3 E            price  volume
    5 Z0 c5 U8 u- H* r) _% I8 j2000-01-01     21     110# x% G. H: G3 a5 |: |) Z
    2000-01-02     22     140
    9 i% p; \) U' c; Y& Q2000-01-03     32     150
    9 h& s2 k9 @+ O' Q2000-01-04     36      90
    * R* r3 Z! h- @" ^& N% ^0 H; A& H+ h4 Z0 D( o
    1+ z, w  w% }& K* b
    2% I! g1 a0 S" F4 j
    3  t& T- v% q( w- G9 }
    47 ]. q  D5 W2 [3 r6 K. [" L1 p* c- B
    56 x" H; F  a4 F1 v7 J/ B
    6
    2 q- U/ c/ `! [9 H9 g5 _: x7+ O2 F: |. \; s: ]+ Q7 c; w0 S
    8
    2 h( t- J4 W+ n* i) y9 j9+ `2 B7 e0 X2 N: L0 u/ ~7 G* J
    10
    0 n, f& [4 W! j* g) X/ S/ \9 n( r11
      I8 G5 e  W  E( F, l* X! T124 P$ V$ {. \5 M  @; e- \. l( R
    13
    8 T3 ]) i# J/ J) K- I" `14
    1 m- ?' z: a9 b' F15
    # c0 E) Y2 b8 f( n* M# ^16
    3 y6 |  a/ G* e! ]- d1 m17/ D1 k+ ~! i6 K. O$ {
    182 ~  J! j1 v% e; [+ m* ~
    19
    # S) p9 }$ p6 ]+ @; r206 Q$ o- X8 t6 q) e+ W& z
    21( F4 D' _5 k& B  F4 T9 G
    22
    5 I7 B! u  ]# r232 L+ B" k4 T; e8 N
    242 v# C$ B$ b2 _) v& `2 Y
    255 w) i# e+ X' z( y1 _
    根据固定时间戳调整 bin 的开始:
      u! K' p4 J- w* y8 ^2 jstart, end = '2000-10-01 23:30:00', '2000-10-02 00:30:00'
      f: c2 F. P% i0 A* W+ ]: @9 d8 Xrng = pd.date_range(start, end, freq='7min')  D5 o) F9 N7 g* Q$ Z
    ts = pd.Series(np.arange(len(rng)) * 3, index=rng)+ E9 z* {0 H0 j& y
    ts; Z" ^4 H. |7 j4 `: `, G4 F# _! E
    2000-10-01 23:30:00     0; Y: `8 h3 `, e* i, H
    2000-10-01 23:37:00     38 ^* d" g9 J* c" l0 }) T# k
    2000-10-01 23:44:00     60 W1 g  H6 u5 d; r4 F0 F
    2000-10-01 23:51:00     9
    ! L8 \" ^1 C$ k2000-10-01 23:58:00    12
    # z" _' M, r- e% Z3 k( }* s2000-10-02 00:05:00    15
    ) X% ^/ Y5 k1 r2000-10-02 00:12:00    18) E3 I$ M3 Q* k% g) G6 I6 D
    2000-10-02 00:19:00    21
    1 h' ]" K. v* Y3 |2000-10-02 00:26:00    24
    3 c" U  c2 v5 L8 X) YFreq: 7T, dtype: int64
    . B& u, V* N9 ~* l6 ^1 g* j8 w6 M) p5 B
    ts.resample('17min').sum()6 m9 m8 ^' d3 s0 _6 \  X6 {
    2000-10-01 23:14:00     0, a  z4 Z" l$ `5 V/ z
    2000-10-01 23:31:00     9
    6 B8 }; D+ ]4 u2 n7 u2000-10-01 23:48:00    21
    % i6 a% @7 p# ]2 d: s- L' j2000-10-02 00:05:00    54
    ' d5 {3 B) z" d& X2000-10-02 00:22:00    24
    0 `' b6 T; ]) R' w: }3 J8 ~Freq: 17T, dtype: int64
    + p4 d, N, H, S. l! h- w% V5 @- b0 V" \% C6 m; h' M, d
    ts.resample('17min', origin='epoch').sum()5 r' a- l3 l% g
    2000-10-01 23:18:00     00 A$ v& \5 t. @; V+ ?& m; X
    2000-10-01 23:35:00    18
    9 N9 v. ^( Y" k2000-10-01 23:52:00    27
    5 G, J: }& Z; [1 }6 L/ W/ p7 T2000-10-02 00:09:00    393 E" Q1 ?/ X7 @9 e% T) _
    2000-10-02 00:26:00    24
    + ^6 x  Q* b, Z0 bFreq: 17T, dtype: int64
    ( T& \! a  Q: b' L4 X3 \; E$ F# M5 y$ W6 q4 R% ]2 b0 g
    ts.resample('17min', origin='2000-01-01').sum()
    9 ?8 E" h  y8 R& r0 f0 \6 D7 g2000-10-01 23:24:00     3! ^# I" g) {; i
    2000-10-01 23:41:00    15( S6 A: A: o: V9 d; b
    2000-10-01 23:58:00    452 J8 ^5 n& j+ `% x1 [2 G; C9 E
    2000-10-02 00:15:00    45; @% ]% X( ?+ D
    Freq: 17T, dtype: int64) S% A8 r5 y' J, J& T3 U$ R9 w
    # C: O2 Q" G7 ~
    1% b6 \5 J$ a1 h. I
    29 K9 m' p9 t9 v" N0 \/ V. o
    3( ?: _8 b# E, ]$ h$ A: e' k
    49 F4 |( A3 c: j0 H( T
    52 J! u! w+ S- s' W# }0 G" _# e
    6
    6 M) D2 z- ]9 x# t1 t/ v73 b1 h6 _5 U) t. ^/ X- ?
    86 ]* I3 c3 j9 r+ L5 ^# ~; a$ O
    9
    8 t1 `: H# N- [! E( l, I) T10
    ! W' E9 d7 c# `' i! Y9 Y0 A11
    " r& Z0 x6 q2 h0 b7 a. J2 q4 i12' [% x  {- g1 @4 a
    13
    " z9 w6 T  M1 o+ ^( ]" u' w+ W0 w147 ^* _  X' G$ k, u
    15
    % e4 g0 v. U9 M6 X* l( ?7 H5 d9 I16+ l" V8 y- W0 m* N7 g1 z
    17; j- Y: M# U& w! `: F* M8 @2 p& r
    18! x2 F7 h$ J  P! f
    193 n# D6 P0 m3 o1 r: ?5 I
    20: d, I" g. P0 G( f$ o# o$ y
    21
    5 w  f  O+ P' h' v0 {, d22
    ( j) C' `% E  z" V6 w  ^23! P" D. F5 o* d7 K1 g. }
    24" ]/ s. \+ ^1 s4 @$ i
    25& r$ I, s) G+ W! Z4 M9 L: I0 Q8 s
    26& k1 V# f" C% {5 o$ f
    270 H: w7 u8 E& p1 B- F
    28" [" z6 {2 ~4 I# `7 Y3 _: J
    29
    6 b) V/ [$ S6 S  H3 Q30" E$ L! R+ h9 u
    31
    ' z/ H7 A& v- t1 x# w8 [$ ^32
    * e3 g$ v: G9 N) M" O9 i: e. _33
    ' z2 @' [9 ]+ W% p' W' ^6 h34$ n$ Z) m) E9 e' l& {) r
    35
      s# x1 \: }0 g# r+ n1 a: a& F36
    6 S# F8 C8 ]6 h2 k& m6 Q37+ k9 f( ^) v7 [
    如果要使用偏移 Timedelta 调整 bin 的开始,则以下两行是等效的:
    & d" `8 ?+ [, b+ M! v# b1 }ts.resample('17min', origin='start').sum()
    ) U) i/ U: s0 ?. o  T. d* D4 ]2 @* G# xts.resample('17min', offset='23h30min').sum(): G% y5 g" K' d
    2000-10-01 23:30:00     9% B9 H8 X" a. K: o3 \/ w
    2000-10-01 23:47:00    21
    4 U  o# y) d6 D# Z! n( y2000-10-02 00:04:00    54, ?+ o+ ]. X7 f, o9 t) w# _2 f
    2000-10-02 00:21:00    24
    ( Y. h5 O1 Q2 B& e: TFreq: 17T, dtype: int646 e( w4 L8 y- H/ P! x
    1- ]' j9 d9 ?7 v+ _$ a+ h4 `
    2
    * B  e/ g9 [; j2 q, a; V7 N3
    % Z0 M1 ?2 l- j1 o9 e4
    4 ^7 j+ A2 e- m) f. P; j- G8 X- T- q5( |" p: P! v8 R' A3 }, {
    6
    " d/ y: _0 w" h' g" Y& f) p2 V( N7
    & L8 O2 n. k9 S10.6 练习/ T, o/ x; m1 ~$ m. M% ?  `
    Ex1:太阳辐射数据集
    0 E/ C0 k, r8 N, w0 J) v' R, N现有一份关于太阳辐射的数据集:3 x. Q) g  f6 T4 A9 k: E3 T

    1 i& e& u8 H6 G' B2 tdf = pd.read_csv('../data/solar.csv', usecols=['Data','Time','Radiation','Temperature'])
    6 Z" y$ q" O& o2 t% T$ z" P: ~6 `df.head(3)- h4 ]6 G# g- d' i4 {" l, i
    + }4 k- ?! X: V  l% W! W+ w0 P
    Out[129]:
    - b0 _* E, }7 T' z& B4 C2 q/ A                    Data      Time  Radiation  Temperature+ h1 I8 R) Y9 K' Q) r- g% W5 V
    0  9/29/2016 12:00:00 AM  23:55:26       1.21           48
    * U3 {5 A' l2 ^0 @% R1  9/29/2016 12:00:00 AM  23:50:23       1.21           48
    7 ?, ?, F- E3 M, F2  9/29/2016 12:00:00 AM  23:45:26       1.23           48( c6 T. {. d6 Z% R& H, T$ G% b& T
    1
    % h+ V# [6 ~& i4 v2
    " p. }: P, m- x1 l) B" o33 W+ w7 d8 ^/ \- N
    4
    - ~3 l* D7 g' J8 C5
    / F! G( n( f4 b* n9 z' E6
    ) j: w9 J$ r( E/ ?7
    0 o& Y" X6 L$ [, v0 u( j88 k* y6 u5 @  A4 m' O2 k
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。5 g, _; K8 o5 J
    每条记录时间的间隔显然并不一致,请解决如下问题:
    ; z) H1 w7 f; d. F9 c找出间隔时间的前三个最大值所对应的三组时间戳。; Y. p# g1 ]7 Y! T
    是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    % o% z2 }& D% m& d4 V7 {求如下指标对应的Series:6 v7 z9 H2 }+ m# h# ]7 Z
    温度与辐射量的6小时滑动相关系数5 _" q& ]  }/ C# o- w8 f) V
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列
    # g- w/ ?- [  d9 x. K. [: y! F每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)* }) E. u" H6 `7 E
    import numpy as np
    : h. |* w. b! q, q5 _" T' C- pimport pandas as pd; [( a& d2 U/ {$ |0 |6 @
    1" g; V. I# R3 Q$ o
    2) `, L; E; u7 |' X; Z8 Z
    将Datetime, Time合并为一个时间列Datetime,同时把它作为索引后排序。
    , s6 h5 [/ j& h! H$ c+ |  ldata=pd.to_datetime(df.Data) # 本身是object对象,要先转为时间序列$ g# [: p* l1 H& P% s7 F5 w" e# f
    times=pd.to_timedelta(df.Time)# d# n, ?- e4 m1 x, t
    df.Data=data+times- T' k! G0 b/ }- s# h" ~
    del df['Time']6 I" Q' z" s4 Z3 ^. k0 ]6 q
    df=df.set_index('Data').sort_index() # 如果写的是set_index(df.Data),那么Data作为索引之外,这个列还另外保留
    : V" j# r7 ?/ h: Q9 l) Kdf
    : _1 b/ [0 f- U- \                                        Radiation        Temperature
      d2 c6 h# s' k. C$ [Data               
    6 z* T) U5 [0 E$ G: L) C2016-09-01 00:00:08                2.58                51% Y" V2 Y" C$ ^
    2016-09-01 00:05:10                2.83                51
    $ s/ c* k7 g# ?0 k; h2016-09-01 00:20:06                2.16                51
    2 ^0 U2 f1 I: d5 B) U2016-09-01 00:25:05                2.21                51! r! M" b3 O% h" A  m5 M* c
    2016-09-01 00:30:09                2.25                51% {; L. v; x$ n4 ^. Q
    ...        ...        ...
    & o: h- O9 a. q5 j) `- }- I2016-12-31 23:35:02                1.22                41
      I& }* I  l; _8 z  ]) N2016-12-31 23:40:01                1.21                418 t( U+ y/ c6 N- B
    2016-12-31 23:45:04                1.21                420 _* n% d, f. k9 j$ X! r
    2016-12-31 23:50:03                1.19                41
      R# B( Z; h- y5 Q9 Y1 f2016-12-31 23:55:01                1.21                41
    ) u  g1 n, ^( g4 ^/ L. i8 Z  t5 k1 B. P" U" Z  x
    1
    4 ?4 `5 y+ C% D1 c2
    & N( C0 ~' M0 x4 I0 G0 P& w3  A4 |" D; X  v
    4$ I5 g0 i$ o, s0 A
    5
    5 y. [5 b1 Y3 ^0 e6
    3 s4 d5 P( k& e: q) ?4 ~6 R! F73 O) J' I* n* l! t4 e( \
    8
    - G$ F  W- c& m. M+ [9
    8 U: |0 U4 h: J, X7 g0 i/ r10
    0 f: R( V* b- g2 `11
    ) P) j- b: P# ]# B& ^! F9 ?( ~5 I) m9 k12/ C+ J% f, \0 R9 [/ ?  z3 [/ v
    13: s% S$ s$ Q- n$ l: F
    148 a& c6 d5 J% \( D0 m9 G  j
    15
    % |  R4 T; k( b! M8 L7 b4 q& v: g! _9 m16
    & b% o8 C# A0 \1 D* J& {8 d) h) w17' M: \' s6 e) }3 @
    18
    $ U! Z  y5 T$ S% |19  A- u! B$ }' L0 n/ g) |% M
    每条记录时间的间隔显然并不一致,请解决如下问题:% r! [- z7 g1 F
    找出间隔时间的前三个最大值所对应的三组时间戳。. h' T! D9 F+ d4 ]- s# [
    # 第一次做错了,不是找三组时间戳, e2 [0 k. F9 S0 T9 m
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3], _- x( ?4 P0 Q  E8 e6 p3 r3 v1 w2 ]
    df.reset_index().Data[idxmax3,idxmax3-1]
    # v# B) C6 s* F2 V/ Y
    4 ?8 l1 j- d% c9 l25923   2016-12-08 11:10:42
    3 ^" U* ~. }% E% l0 x6 {/ u( G24522   2016-12-01 00:00:02) @9 c. g7 U9 ?) }. F% a. d
    7417    2016-10-01 00:00:19# \+ h6 U$ N, F  ]  `0 j
    Name: Data, dtype: datetime64[ns]
    / m3 x  v0 f8 _2 Q1
    & X5 Z9 u) P4 H2
    # U7 T3 g; l! e7 }! F' m6 w3' Z9 ~8 K( Q( V& z2 ^& j- Y
    45 [, W* l8 |6 G, Y3 N- w
    5) o1 l1 Z$ J& E6 }
    6( ~! U9 z* q2 I
    7
    ) n- U+ g! ?( D81 V7 Z- P7 ?3 K7 d6 u
    idxmax3=pd.Series(df.index).diff(1).sort_values(ascending=False).index[:3]
    - Q& o+ ~2 p: Y- G$ b; glist(zip(df.reset_index().Data[idxmax3],df.reset_index().Data[idxmax3-1]))
    7 P' i1 D% p9 }4 r% v4 ^$ y' H1 p
    & g! c% I6 }# ?' F. ]% a; Y6 J7 r[(Timestamp('2016-12-08 11:10:42'), Timestamp('2016-12-05 20:45:53')),  X0 h$ C" Q) R) P4 D
    (Timestamp('2016-12-01 00:00:02'), Timestamp('2016-11-29 19:05:02')),
    + d; o/ h& z9 q3 X  }) J! `+ ^ (Timestamp('2016-10-01 00:00:19'), Timestamp('2016-09-29 23:55:26'))]& Z$ z: }/ n- L/ r) Q; K& u
    1
    ( A# p# h% X9 }( d. Q28 Q6 j9 W5 Z9 h: _5 c
    3
    % b2 `' x6 F7 n* n9 S/ P! R41 n# t! x0 P; e
    5& B/ ~- E" U7 v+ w7 X( m
    6
    . G8 @- x$ ?& L6 \5 f2 r8 M  t参考答案:7 V1 p' N5 P; o7 m# t& M. y6 _
    + e$ T  A+ y$ g9 x
    s = df.index.to_series().reset_index(drop=True).diff().dt.total_seconds()4 G% n: O7 A, O3 ]3 f8 B2 B  m
    max_3 = s.nlargest(3).index# l" k5 {/ ~) r: q  Y
    df.index[max_3.union(max_3-1)]4 G/ a8 n5 f4 t

    0 P' |$ _& w$ j/ a, I% kOut[215]:
    5 y3 c9 x% X" s) w( {& z' ADatetimeIndex(['2016-09-29 23:55:26', '2016-10-01 00:00:19',, \! M, `# h- n' x% r+ R
                   '2016-11-29 19:05:02', '2016-12-01 00:00:02',5 {9 m2 E. v! [1 a( i/ B, |
                   '2016-12-05 20:45:53', '2016-12-08 11:10:42'],
    : O$ w; b5 O* g: V+ o; ]# g& r              dtype='datetime64[ns]', name='Datetime', freq=None)
    4 w$ u! y; Q: h! T1
    * s/ Y, ^3 u7 a2
    + W; `5 ~* N* b37 j2 ~" j. T- m6 j, l
    4
    ) ~( W' A) v% b5( [4 ?- m2 ^/ G7 V4 H# ^1 K5 _8 j9 j
    6
    * H0 x9 ~1 y0 l* V9 F  L7 {7
    3 w, V8 I' u5 ^0 }! K. S- X8
    $ i# J# ?( {" k, p. c5 |9
    # Q1 m# [7 }3 E( v7 \+ s6 s是否存在一个大致的范围,使得绝大多数的间隔时间都落在这个区间中?如果存在,请对此范围内的样本间隔秒数画出柱状图,设置bins=50。
    ) Q6 E7 H% M0 X, {, o6 c; t1 q# 将df的indexydiff做差,转为秒数后排序。再求几个分位数确定取值区间
    7 N9 Y7 P* ?% V2 bs=pd.Series(df.index).diff(1).dt.total_seconds().sort_values(ascending=False)1 E  V& V+ G. W- j
    s.quantile(0.9),s.quantile(0.95),s.quantile(0.99),s.quantile(0.01),s.quantile(0.03),s.quantile(0.05)
    ( \$ Z, U! h, @" q
    : v0 B1 Y) W. Q% ^+ ]( C(304.0, 309.0, 337.15999999999985, 285.0, 290.0, 292.0), m9 n- S1 |" F! F
    1& J. p) X. |& a% C+ h4 r5 H& U
    2
    , X% `; a7 O# w, c+ e3# S% g6 A' o7 i1 ]. E+ x% D" m
    4" l5 u/ y' Q( l  H; S" S( c
    5
    : G0 ^& i* Q* b$ V0 J# ^%pylab inline* I) h2 K3 U% S5 ^7 A: w$ S
    _ = plt.hist(ss[(s.values<337)&(s.values>285)],bins=50)
    4 x$ _, j. q$ u  j- ^plt.xlabel(' Timedelta')
    0 c* L5 X' ^8 w" E0 p+ mplt.title(" Timedelta of solar")
    5 c( F8 J- S+ R; r& Q4 t' K8 q0 o1. {1 [' J- d! f# e6 ]% W: ?
    2
      P8 C; @( ]! p, C7 F2 x. Q: l3
    # F$ k* q; B- v* [4 L/ X47 H+ m+ L# S6 `7 Z; c6 |
    - n; E9 I4 h* c4 M, a' v6 ]
    9 w2 N; r. A( C5 T
    求如下指标对应的Series:
    8 g; Q" }) o" w4 v5 {温度与辐射量的6小时滑动相关系数. E0 d3 X7 F2 m/ v2 L0 m& C
    以三点、九点、十五点、二十一点为分割,该观测所在时间区间的温度均值序列% {7 L" ?, t; B5 Z
    每个观测6小时前的辐射量(一般而言不会恰好取到,此时取最近时间戳对应的辐射量)
    . W) @, C0 i2 w, C% D: D: ndf.Radiation.rolling('6H').corr(df.Temperature).tail(). T! `* \: Q4 N6 z# G; P6 j
    ! W- d" R2 `1 x" Q: s/ W3 v+ p) U9 i
    Data& Z  q! s4 l" o# C+ |
    2016-12-31 23:35:02    0.416187* O/ q( m6 ]! g, {) c+ ^
    2016-12-31 23:40:01    0.416565! F4 P/ i: @& I! Q  H9 ?. H
    2016-12-31 23:45:04    0.328574
    # r- ^$ I% k3 h/ w9 |+ c2 P: }2016-12-31 23:50:03    0.261883
    ( K* m: U: @; R+ L: r, M2016-12-31 23:55:01    0.262406
    + N9 @$ ^* }, L2 B' r' z7 q+ Bdtype: float64
      Z8 t. W) l2 l  ]# V1- a6 P# f' x7 M8 s/ B- N# k
    2) `8 X( M; d! t6 L$ V8 o. m3 l
    30 I  g0 l# B0 `, o4 C
    4& ?" N( j* H1 x0 r1 x7 s
    58 E; V1 l! L. k
    6
    6 U( U3 i  x+ j: h) j78 s  t+ O2 A/ b' z* u& B
    8, K; Y, D" z! W- y8 X  n/ U
    9# }5 Y) W$ O! R  ^
    df['Temperature'].resample('6H',offset='3H').mean().head()3 c4 C( c; ?  y
    4 v9 d+ r, H6 W1 ~* C$ W
    Data0 H% o9 z4 A. Z* b' n7 Y
    2016-08-31 21:00:00    51.218750( J6 V$ H# n, H. w
    2016-09-01 03:00:00    50.033333
    - [7 Y- }0 h2 P+ T" J5 A8 l2016-09-01 09:00:00    59.379310, e: D# ]* U0 i9 p
    2016-09-01 15:00:00    57.984375
    # w9 i7 m& {: z' }; m: `2016-09-01 21:00:00    51.393939
    2 x8 y) a# X2 ]  sFreq: 6H, Name: Temperature, dtype: float64
    $ E; a; w/ y- t0 L' B5 j1
    % |5 E! a  m4 \8 C! a2
      B: A; @6 s2 v' H$ H/ _, Y3
    2 ^8 v% i$ E! v; ^9 j44 g: }' R2 a2 w$ I5 y) f  J
    5
    : z/ v6 L. a% U- _  V66 i1 ~$ m5 j9 a: Q- A
    7
    . Q% n; `% M: ~$ c87 s4 y0 v. ~+ q7 X+ k2 _
    9
    ! N  J# t# S" S8 L# ?2 b最后一题参考答案:2 L8 C3 ?/ J& f& t
    3 I9 z/ l3 a" D# w8 u) a( M0 |' N
    # 非常慢
    4 T& K* O/ Y, N: u1 ?2 b1 S9 amy_dt = df.index.shift(freq='-6H')
    & I5 A+ H* T# o7 i& S5 e, ^/ ^) h3 f% w, Mint_loc = [df.index.get_indexer([i], method='nearest') for i in my_dt]
      r& o/ ?+ x1 `int_loc = np.array(int_loc).reshape(-1)
    , I2 o/ L- O' V% p( Z. \res = df.Radiation.iloc[int_loc]# g  z" S: w% {' J+ A$ J
    res.index = df.index
    $ b, x( n$ X( Z( Q- d* X7 |  }! L3 Y! Ures.tail(3)
    ' u( C1 I; |* x, v( [' M" {1- x; g' \& r- x) H
    2; Q9 p2 i  X) i- o, p7 S
    3
    # |4 ^, s6 N/ F4
    9 U1 j! h$ i3 w- v1 H; I9 G, Q5 X51 d" Y: _# X6 x" o
    62 a8 J# T) t& g+ b8 t
    7
    " L; Z' A' }, Q  p4 n4 x* b# 纸质版上介绍了merge_asof,性能差距可以达到3-4个数量级
    ' }: m+ ?6 a3 _9 l0 f( Ctarget = pd.DataFrame(
    8 F' E! L2 L0 S! J- w. j    {* E& V8 A7 ~: o# D
            "Time": df.index.shift(freq='-6H'),
    3 m- c: b& g* w( A& J: o! ~        "Datetime": df.index,
    1 q4 S0 d" C; }. l8 a9 X  O) L    }
    ! {$ O( l3 H& C: y)# Y2 }+ @  P" c; p8 M5 p" g9 r
    + m+ f) R6 o7 l/ o% [
    res = pd.merge_asof(# X  L8 A- ^) |, N
        target,1 n% O6 I' D6 y3 T" P
        df.reset_index().rename(columns={"Datetime": "Time"}),  a: H4 v, x+ g$ K3 s2 W: y" v; ~
        left_on="Time"," ^- A+ M1 h! C, i. r3 {
        right_on="Time",
    5 Y: X; w& o( s2 B5 E    direction="nearest"+ u% ^$ v2 k* P
    ).set_index("Datetime").Radiation9 Q' I& x. E9 c7 w8 `5 T! N
    0 |+ }) j1 ~- ]! f1 o
    res.tail(3)! A  \+ p* z+ R! h. e6 R6 m" C7 W
    Out[224]: * h  L$ P/ P# \- N1 y
    Datetime9 G  l* ]+ @- L7 d- ~3 e. W4 C( O
    2016-12-31 23:45:04    9.335 A# I- a; m* f/ o6 O9 p& \, h
    2016-12-31 23:50:03    8.49+ V0 f- z8 N; Q3 e1 h& g9 \1 M
    2016-12-31 23:55:01    5.84
    4 p/ Z0 m& Q* j# u  tName: Radiation, dtype: float642 N9 P" A: \) Q& S1 ?( o
    % K9 j4 U' ^  q7 D# b  \: ]
    1
    5 O5 J3 J" A1 j9 E" H: @1 {9 \1 G2
    ) @0 c$ Y; V& [. Q; n3
    ! D9 R; g. r4 @# k4
    ) Q3 y1 z7 r3 u" Q8 ~8 ^5# C" s9 O) t2 z6 `
    63 _  y, }: I8 k4 H6 j
    7) F" N/ Y" T, h; j% x9 r
    8
      H! @( S6 h& C% I5 B' B9
    ! }4 l5 A/ \; e4 X4 h5 y$ }. ^109 K! F" t( w( ^3 ~3 c# a
    11
      X( _1 l& Y3 D6 k+ J6 e% @2 W, @12( c6 B$ ~: {  @. b9 b* q) S
    135 {( P  i7 V9 o
    14
    / F8 K. J! N/ x; J. ^, N15
    6 t; n9 [+ t6 v2 f5 J4 n16( i+ ]. J! }# p
    17+ u9 L6 ?% p0 s1 n
    18
    ( _3 H& r( Z1 N! g5 t2 Z19. z7 s3 X' Q5 {. G) R% x( }/ l7 N
    20- C2 J# m8 e0 G2 A- A. R/ R8 p- D
    212 n1 N) G. Z+ u* F
    223 i5 V7 o- p' Y0 U7 K4 [3 J
    238 h8 X2 _0 J2 L
    Ex2:水果销量数据集8 ]; @  W- d) m" f, Z
    现有一份2019年每日水果销量记录表:
    . A. z( m3 T; m. ~5 ?7 ^7 N' u% A$ |2 }
    df = pd.read_csv('../data/fruit.csv')4 X7 v, F" A9 N( I) c' T; h
    df.head(3)& [$ m' s- H- K- m

    ; j1 y! [" a5 L" JOut[131]:
    / q3 D1 {! K0 x         Date  Fruit  Sale
    9 g+ p+ c# l0 T( r0  2019-04-18  Peach    155 Y! }: |' n' \
    1  2019-12-29  Peach    158 A9 f. Q8 _# R- G# k" C
    2  2019-06-05  Peach    198 b. s: ~+ d1 I
    11 G4 S1 R; |$ G5 D
    21 ^3 F2 p& v  N" m" s3 e# w+ Y
    31 c( g" U0 }& p! m4 C
    4
    ! u7 F9 o5 a* |( \5
    0 V( k7 ^% D5 ~" z* h$ g6
    ' d$ ?5 J4 n; g2 B  k7
    0 H+ R/ L6 m9 ?. E86 i2 P) g" v! A" F6 E' \2 H
    统计如下指标:+ ~6 F5 a5 o9 U; ~, e
    每月上半月(15号及之前)与下半月葡萄销量的比值  z( D: d0 x0 Q9 K* k$ n: C& X: t
    每月最后一天的生梨销量总和: R8 A# j1 ~  ], O  q
    每月最后一天工作日的生梨销量总和' L" _+ X  E2 |) C) A4 i) _
    每月最后五天的苹果销量均值3 {0 k* |- E1 K5 s/ J1 S3 B! T7 ]
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。
    7 v# K( {4 \) Z: _8 ~按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。6 O8 U- ~; U8 @% k& K* U  r
    import numpy as np( R; H" U; y2 J! E7 c- d
    import pandas as pd
    ( j" E4 b5 v7 y7 ^# k( _1 P0 ]1
    ! v' z2 t1 F2 A- i6 i6 W# n+ i2
    , l) h5 W9 X; k# {/ n1 L6 v统计如下指标:
    $ ?9 b7 z7 y- l* t1 _7 j每月上半月(15号及之前)与下半月葡萄销量的比值" Z7 z+ `* f- o; O
    每月最后一天的生梨销量总和3 N5 \7 q& y8 O% T0 u
    每月最后一天工作日的生梨销量总和' C# W$ T, u9 o3 F6 W3 B/ V
    每月最后五天的苹果销量均值
    1 g: M, Y, G4 ~+ @# 每月上半月(15号及之前)与下半月葡萄销量的比值
    $ \  q( I# D, x2 [3 p  Jdf.Date=pd.to_datetime(df.Date)
    0 A! s# L1 d, V. b3 B5 S5 csale=df.query('Fruit == "Grape"').groupby([df.Date.dt.month,df.Date.dt.day<=15])['Sale'].sum()3 X7 ?9 ~2 |. P6 z
    sale.columns=['Month','15Dayes','Sale'] # 为啥这么改没用啊$ o( f' l% b6 M2 Y9 V
    sale=pd.DataFrame(sale)
    6 B) i% L4 P1 M3 ?5 n3 Fsale=sale.unstack(1).rename_axis(index={'Date':'Month'},) e! o7 n5 a7 x& e" V8 E; Y
                     columns={'Date':'15Days'}).stack(1).reset_index() # unstack主要是两个索引都是Date无法直接重命名
    ( \5 S8 n; W! x4 _sale.head() # 每个月上下半月的销量
    ( e  s: }  N" M: F% ~* S) Q6 f
    % A$ G( ?8 A. u9 D' |, x  Month        15Days        Sale6 U( v# v" I: c- g. _& d7 n
    0        1        False        10503
    * ^) \+ F2 O. r& u1        1        True        12341
    $ _4 j9 v- H. p5 K7 L+ f. U2        2        False        10001! k0 e, I5 O/ J1 c
    3        2        True        10106
    & d, a  \. U5 Z4        3        False        12814
    / k8 J  e! L" C, ]+ H& }8 ?' p" p% E0 j! r7 _
    # 使用自定义聚合函数,分组后每组就上半月和下半月两个值,根据索引位置判断求比值时的分子分母顺序
    * [) G- t) }9 [2 Usale.groupby(sale['Month'])['Sale'].agg(5 ?" ]- G- {; w8 I4 ~
                    lambda x: x.max()/x.min() if x.idxmax()>x.idxmin()  else x.min()/x.max()): l* I7 ]; {: R0 ^; O, e, E
    , I' ^: \& I: g
    Month
    * U2 T8 E! Z; h1     1.174998
    2 `4 B. f: B" n. g+ T2     1.010499- \/ Y3 l& y# a
    3     0.776338$ ~$ k8 U+ b' d8 @" U% a
    4     1.026345
    / g' K, s, d0 O: n, t2 D; e5     0.900534- b/ t4 M  _0 m$ `
    6     0.980136
    1 k3 {9 T; S0 s% c9 K- P7     1.350960
    ' f2 I+ Q' n. C2 Z8     1.091584
    9 f) B# b5 ?' }8 E9     1.116508& y+ I, A  k: a  ?) n, Z. {
    10    1.020784
    1 g* U5 N! {  r: n* e: P11    1.275911
    5 O6 K2 x3 u7 E: R( R1 u12    0.989662
    & C+ L, [$ X, f2 R* F6 F+ D6 t2 lName: Sale, dtype: float64* C5 S5 a, E) |3 {  Z
    2 F) J6 n( r3 ]+ t+ s" P5 H1 C/ a
    18 i; ~) q, r# U: ?
    2
    ! n' s0 T. s3 c& E' O: `% C3" d2 |5 f& W( c/ V" {* V6 P( @
    4
    3 F, h$ j% P2 K% G" o5+ y; Y5 v4 k$ }. V/ o: h* `
    6
    : G3 a9 ]4 e; N/ o7
    : o$ ~: H( l  {: m7 O82 f  ]9 P/ G' L$ Y2 s
    9
    7 K: c; Y1 Y# ]6 i10
    ; E) R: U/ k5 G" T; g: G+ O- c! ?' S& q11
    - W7 K5 s: L. y& j, t$ S; B+ ]126 M9 ^* Z* X- p7 Q$ C! L9 f
    13
    2 Y  i; T( q: s6 _3 Y; W& X14
    : M$ l9 C& g4 K8 S154 }2 x% m  G! d  W2 H
    16
    - f% F' J* X3 C0 T" I  z- m9 {17
    1 ~$ z/ \3 M( N) L7 Y181 O* b7 s1 Z$ R) L# r
    19$ K3 {( e+ [0 r2 w" [
    20
    ' B* w+ T3 @3 I6 E* {21' H# n! \# v; {2 ?, }; G' o! [
    22
    ; I  y0 @, ]2 d# z23
    - Y# d; y4 M. \2 ]24+ ^6 I* G6 C/ F! C0 X. v. z
    25
    5 `  s3 J1 @7 l: ^# k. x( q6 \  F26
    ' K; @* R; u7 w( t! X- S5 l27& [7 y% j. K6 B0 T8 b& r0 H# ^
    282 w/ q" b0 l4 t1 q! `5 z
    29
    * j5 R6 p& ~) k/ V30+ j0 W: x% a& {
    31
    ! T4 f. C: ?' @8 U# U; R+ W32+ Y  `% y' `$ K- z" k2 ?
    33& W! M4 `* t  K: |. ^% a8 V
    34/ N' B( L! }6 K$ |" l% h, F) W
    # 每月最后一天的生梨销量总和
    ' k. H, m, c: T. z9 b. M$ {df[df.Date.dt.is_month_end].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    # V2 w" e$ D. `/ Q  j
    & d" P* D" W8 X$ MDate
    + Q, [7 v1 R2 O3 q/ V2 Z2019-01-31    847
    9 ?  S8 A; Y1 ]" s+ H2019-02-28    774. t$ t# M( N3 q" b* x( a
    2019-03-31    761
    6 n  u; z( n; y3 F, v2019-04-30    648
    0 K3 e. Q% `5 X2019-05-31    616
    9 h; P; F+ y7 C/ J15 {5 f9 e+ I9 b; h' n+ D# z, Q9 j5 @
    2
    5 Z: I' x6 r6 F) S' O3
    4 \! j. A% g5 c  C4" [* B* ]* Y- i. B, q( D( `9 Y
    5
    # X' S) M& }- q6. {+ ^% [  A' s: F$ N
    7/ N7 ^! A; n: j; _$ f
    8
    ! i- v6 r$ F8 `6 ~7 z8 u9
    & C3 Y2 Q1 [& P1 Y0 ]# 每月最后一天工作日的生梨销量总和" Z7 ?+ i4 ^: r" ^+ V
    ls=df.Date+pd.offsets.BMonthEnd()# x: Y! ?; V7 r2 E6 L$ X
    my_filter=pd.to_datetime(ls.unique()); m" J! ~6 [! }6 Q/ ]5 X1 _( B/ X
    df[df.Date.isin(my_filter)].loc[df.Fruit=='Pear'].groupby('Date')['Sale'].sum()
    , |! n3 R' k) a6 u5 ^4 U; i
    / F5 A1 }' X3 ^2 J/ O7 yDate: ]& D" Z* q8 c" U4 _
    2019-01-31     8479 ~* I; P" R5 P0 V% R* g. x4 O3 k
    2019-02-28     774
    - u; M( O9 o/ N7 K& \+ S2019-03-29     510
    8 w% ]$ T% J. L: v$ Q2019-04-30     6488 g/ ~! S; R+ G" J# J! j4 c5 t3 m
    2019-05-31     6165 Y! f9 u+ Z" A5 Q# G% q3 L+ G; k
    1
    * m# V: \( e$ m/ V9 Q$ _. `2
    / e: i5 [6 N) g) m" H* M# K3
    ( F# i) B1 g9 t! c1 b5 G4
    ! l4 S9 }! h3 m! ^, L5
    $ f3 U2 d# G! T2 M/ U+ k6
    / u! Q; y. Q, K- D) v7 }- \7! Z. o5 Q. C. w3 R
    8
    ) M$ `8 }  E, z* |9' O0 z9 R& Q2 Z# [5 B5 Z+ y) ~
    10
    ' K- R* K; c& E. ^5 o11( \* @5 S& [; {! a6 m6 ?1 j& e
    # 每月最后五天的苹果销量均值
    5 b* Q3 O* ~& E- i3 Hstart, end = '2019-01-01', '2019-12-31'2 q5 [1 B/ w/ h% W/ a! B1 z
    end = pd.date_range(start, end, freq='M')! U* p; r5 |; b. N" d/ R6 z' y0 k
    end=end.repeat(5) # 每月最后一天的日期列表,重复5次方便做差
    : h+ G  w/ }2 |; J* G
    - ^5 l% x( ~$ C- A: d6 ptd= pd.Series(pd.timedelta_range(start='0 days', periods=5),)
    ) d8 e% J2 Z: q$ qtd=pd.concat([td]*12) # 日期偏置,最后一天减去0-4天# Q& v2 ?4 o( J4 J
    end5=(end-td).reset_index(drop=True) # 每个月最后5天的列表
    " D* I; Z" t' f# J. s1 `6 ^. s# R/ c, m* y5 @" y: R7 s
    apple5=df[df.Date.isin(end5)].query("Fruit == 'Apple'") # 每月最后五天苹果销量' C/ }0 x! Y- w) a
    apple5.groupby(apple5.Date.dt.month)['Sale'].mean().head(): u' \+ a1 {- s  i- {

    * p9 C) Y8 W% M5 p8 _Date( B+ a- O; t2 D1 T( w* O
    1     65.313725) `1 o$ n5 t' h% q! y5 m0 E* i9 [( ?
    2     54.061538/ W$ c3 d( L  v5 c9 p
    3     59.325581
    ' c7 q) K: a1 H  u5 N! o4     65.795455  t0 L' z2 S2 L# d1 k8 T
    5     57.465116
    ' t3 V+ m" k: Y
    ! b' v1 T& Y5 M  w; I1
    & r( {0 q4 q- w. O% [- B. N2
    ) H1 X! f) R4 J8 A$ ?3
    # ~" H  b0 H7 |  W7 J3 s4
    / B  `6 |8 p+ L% [9 o) [2 e9 M7 B55 u$ J/ P! l0 ~1 J. p7 H2 |
    6
    - Y9 Z$ |, ]+ q& A) n7
    9 I- Z) y+ O+ c$ H' P8
    # r7 f! E! ?% p3 E9 S8 @9# L3 I6 g1 T! @
    10! q5 Y" `3 [& w* ]5 F1 @' M
    11
    9 t4 c  C* p' z: _6 f. x- k12! L* P) j, `% r# u
    130 ]- b0 e# F# @# a" T
    14
    + [/ [' N/ X5 F2 z15
    $ R% D( ~- o; A7 W/ _/ X  v4 I# o8 M16' M- {* i5 b2 x6 Y! y% g' L
    17
    6 A& R! v8 w1 b6 D  F18
    , k5 b0 _& \, G: G# 参考答案:7 j* q3 d4 F3 G0 \
    target_dt = df.drop_duplicates().groupby(df.Date.drop_duplicates(
    # z8 S0 `( n* {) k) R2 {# {' D            ).dt.month)['Date'].nlargest(5).reset_index(drop=True)
    " K7 X9 W" ]( I! H
    4 b8 g0 Y! r) }6 Kres = df.set_index('Date').loc[target_dt].reset_index(
    7 N1 [  W4 Z8 G" {7 Z            ).query("Fruit == 'Apple'")
    % y0 t9 ?( u* m4 x2 h  s5 u) F; H8 j( f) P, ^) \' X
    res = res.groupby(res.Date.dt.month)['Sale'].mean(* H) ^2 w' ?, e2 {: r4 D* v" K
                ).rename_axis('Month')8 l6 [9 @' C) L" w( i

    # z  B/ o; ^' u& P4 J. N6 p$ [5 k' S) c- z
    res.head()
    1 @. X  t9 a8 e/ }4 B* hOut[236]:
    9 ~$ h- W5 I/ ~  n" F+ zMonth( p  E# V: K! a) M- V* e5 H; t
    1    65.313725, M* o- ^1 T$ T7 z2 q
    2    54.061538" }3 a7 N3 s, v$ l5 B/ A
    3    59.325581" K9 F0 A  z; G$ e; H6 L8 L" s
    4    65.795455
    # h8 o- L% p2 Z5 L- N+ x5    57.4651164 x( N/ ^, m5 a6 z8 w5 @
    Name: Sale, dtype: float64
    ) e) A8 h' F( D5 }  x- t
      [( ?% ~7 Q9 W! U1 D" \% K) z1( w) X% Z6 R) r
    28 j. \7 {& o% ?4 i' C2 E" [1 p$ P' O
    32 ]& B# M8 V" o0 O! i5 d# N9 p
    4
    1 I4 l( H( v/ U1 q# P$ R( ]5' o4 J( U/ ^4 `* s9 P0 b
    6
    8 N$ b  X' C, f8 T2 V7, f. m- Q. g* z/ ?% {! r$ p. [
    85 S0 g! J6 N5 a1 o& A
    9
    / @7 P$ `, S1 [! W! D8 O, N  ]10! D: j+ z3 P5 _2 N, K7 z
    11) |. {; c- s6 N% ]( Z6 b9 u
    12
    . A6 S  N1 Q* `  T7 h' _13
    8 [" v! \0 W1 @0 q" _( n4 w14! X! R+ X4 c2 O' h8 c$ z: g! u
    15
    * U: J5 I; K5 B4 p) Y16
    ! L. o/ C1 Z. Q17
    5 P, X; a5 {1 a* \2 F% @181 b7 A2 |+ z; G% r1 j( ^7 j# |1 Z
    19
    / X4 q  i8 l1 B5 t20; _# x3 s: K# z( t2 ^* A7 S
    按月计算周一至周日各品种水果的平均记录条数,行索引外层为水果名称,内层为月份,列索引为星期。7 Q; G3 o3 ?# p$ Y
    result=pd.DataFrame(df.groupby([df.Date.dt.month,df.Date.: g2 W0 u8 c9 [0 [6 c
                                            dt.dayofweek,df.Fruit])['Sale'].count()) # 分组统计 / l. }, F( ^, i
                                            7 O& o( G3 s" c! B7 N9 S& x% Q
    result=result.unstack(1).rename_axis(index={'Date':'Month'},
    6 T4 R1 ]$ c/ q: z" L# A                 columns={'Date':'Week'})  # 两个index名字都是Date,只能转一个到列,分开来改名字." [/ _; r, e, o! x- M
    result=result.swaplevel(0,1,axis=0).droplevel(0,axis=1)
    5 i* a4 A* M* s9 D! Aresult.head() # 索引名有空再改吧
    1 d- t6 e( a' k1 N1 \* H
    3 V) [$ t8 ^  @1 [* T; t          Week        0        1        2        3        4        5        6
    ! ^7 G/ i$ I* ^. aFruit Month                                                        & C/ W) z$ ~8 O- S7 r/ f0 m1 N! q
    Apple        1        46        50        50        45        32        42        23
    7 @4 }* M' k) J4 CBanana        1        27        29        24        42        36        24        35# d' k# u9 o; D( d# Y  L9 X
    Grape        1        42        75        53        63        36        57        463 p+ p* \# |7 W! T1 F& Q5 ]; i
    Peach        1        67        78        73        88        59        49        72' e  o1 S: n4 b. P7 b
    Pear        1        39        69        51        54        48        36        405 J' s% A' ?9 a/ b
    1
    2 d1 W/ U; X5 r, L: P% b2
    % A+ T9 ^# q6 Q/ q/ p6 K3
    , N; i9 t- C1 O5 H' N42 s8 ?# s3 A* h
    5$ I" p$ A3 T# n3 }3 _7 Q
    6
    + Y( T4 u" x  _  Q  F6 v1 x7; D2 i( t6 j( V. q, h2 Z6 P
    8
    2 N; f; q  q5 @1 W( J: A! i6 y7 y97 C( Y5 f7 z  z& x
    10
    & J5 x6 |4 N' X2 t4 b11
    2 ~2 q) [' a! ?( I# R12
    ( Y- w! J- ]4 G4 z13
    7 y' l5 w! ?" H- R' N2 E$ S1 d142 A% X5 k) ?$ ]: h) K6 W* W* N8 _
    15
    : j4 F' v/ H. a# {$ K8 w7 L  G# `按天计算向前10个工作日窗口的苹果销量均值序列,非工作日的值用上一个工作日的结果填充。
    ) k4 [; e; W& X( l. g2 J5 t# 工作日苹果销量按日期排序1 E5 `( x6 t# l# F: |# r5 F! Z
    select_bday=df[~df.Date.dt.dayofweek.isin([5,6])].query('Fruit=="Apple"').set_index('Date').sort_index()5 U1 R2 G. |1 k& y* c7 T
    select_bday=select_bday.groupby(select_bday.index)['Sale'].sum() # 每天的销量汇总" z0 o6 |2 X+ b  V, Z# I
    select_bday.head()! K8 L  B. I0 T" x) R
    * ~. a& a: u! K
    Date- j1 X" I! C( I; U" b
    2019-01-01    189
    7 p6 {$ x; `3 J! a; i2019-01-02    482
    . Q' {1 e% T7 f$ Q$ H" B5 n2019-01-03    890
    7 U; {7 `. e  v' l( t2019-01-04    550
    8 Z' x8 c( Y4 W' k$ ^2019-01-07    494
    0 |, H0 v2 u  X8 J2 y, m# k5 S( _3 [) i. R3 ~2 N
    # 此时已经是工作日,正常滑窗。结果重设索引,对周末进行向后填充。8 o- k& Y2 m! Z' C
    select_bday.rolling('10D').mean().reindex(df.Date.unique()).sort_index().ffill().head()& \0 x. e5 j& t" B6 T4 U9 l4 \) d
    7 p, ~  D# c; r# i. ?3 o7 }
    Date! x3 c7 f0 T) m' c
    2019-01-01    189.000000% J: f8 ~' c/ u* T' a8 E" Y% L
    2019-01-02    335.500000  [, C; _# |! O2 n  ]+ u
    2019-01-03    520.333333
    9 a3 i& f1 n5 v2019-01-04    527.750000
    # r/ R# ]( Y* r2 Z* r1 l2019-01-05    527.750000
    2 T& r9 [! @1 [* d, f* l# {2 ~
    ( X5 |% z' X3 Y————————————————% k, {" \/ s# w; x. L) i
    版权声明:本文为CSDN博主「神洛华」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ' R' X1 E6 s' b! W4 k原文链接:https://blog.csdn.net/qq_56591814/article/details/126633913. n7 B: {+ n8 W0 g
    ; M9 t9 G/ }4 M. U
    ; Z5 J/ m3 Q( ]+ n
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-4-15 02:45 , Processed in 0.545120 second(s), 50 queries .

    回顶部