那些功能逆天,卻鮮為人知的pandas騷操作

文章來源:Python數據科學

作者:東哥


pandas有些功能很逆天,但卻鮮為人知,本篇給大家盤點一下。


一、ACCESSOR


pandas有一種功能非常強大的方法,它就是accessor,可以將它理解為一種屬性接口,通過它可以獲得額外的方法。其實這樣說還是很籠統,下面我們通過代碼和實例來理解一下。


<code> > pd.Series._accessors
{

'cat'

'str'

'dt'

}/<code>

對於Series數據結構使用_accessors方法,可以得到了3個對象:cat,str,dt。

  • .cat:用於分類數據(Categorical data)
  • .str:用於字符數據(String Object data)
  • .dt:用於時間數據(datetime-like data)


下面我們依次看一下這三個對象是如何使用的。


str對象的使用


Series數據類型:str字符串


<code> 

>>>

 

addr

 

=

 

pd.Series([

...

     

'Washington, D.C. 20003'

,

...

     

'Brooklyn, NY 11211-1755'

,

...

     

'Omaha, NE 68154'

,

...

     

'Pittsburgh, PA 15211'

...

 

])

 

>>>

 

addr.str.upper()

0

     

WASHINGTON,

 

D.C.

 

20003

1

    

BROOKLYN,

 

NY

 

11211

-1755

2

            

OMAHA,

 

NE

 

68154

3

       

PITTSBURGH,

 

PA

 

15211

dtype:

 

object

>>>

 

addr.str.count(r'\d')

 

0

    

5

1

    

9

2

    

5

3

    

5

dtype:

 

int64

/<code>


關於以上str對象的2個方法說明:

  • Series.str.upper:將Series中所有字符串變為大寫
  • Series.str.count:對Series中所有字符串的個數進行計數


其實不難發現,該用法的使用與Python中字符串的操作很相似。沒錯,在pandas中你一樣可以這樣簡單的操作,而不同的是你操作的是一整列的字符串數據。仍然基於以上數據集,再看它的另一個操作:


<code>>>> regex = (

r'(?P[A-Za-z ]+), '

       ...          

r'(?P[A-Z]{2}) '

         ...          

r'(?P\d{5}(?:-\d{4})?)'

)   ... >>> addr.str.replace(

'.'

''

).str.extract(regex)          city state         zip

0

  Washington    DC       

20003

1

    Brooklyn    NY  

11211

-1755

2

       Omaha    NE       

68154

3

  Pittsburgh    PA       

15211

/<code>


關於以上str對象的2個方法說明:

  • Series.str.replace:將Series中指定字符串替換
  • Series.str.extract:通過正則表達式提取字符串中的數據信息


這個用法就有點複雜了,因為很明顯看到,這是一個鏈式的用法。通過replace將 " . " 替換為"",即為空緊接著又使用了3個正則表達式(分別對應city,state,zip)通過extract對數據進行了提取並由原來的Series數據結構變為了DataFrame數據結構。


當然,除了以上用法外,常用的屬性和方法還有.rstrip,.contains,split等,我們通過下面代碼查看一下str屬性的完整列表:


<code> > [i for i in dir(pd.Series.str) if not i.startswith(

'_'

)] [

'capitalize'

,  

'cat'

,  

'center'

,  

'contains'

,  

'count'

,  

'decode'

,  

'encode'

,  

'endswith'

,  

'extract'

,  

'extractall'

,  

'find'

,  

'findall'

,  

'get'

,  

'get_dummies'

,  

'index'

,  

'isalnum'

,  

'isalpha'

,  

'isdecimal'

,  

'isdigit'

,  

'islower'

,  

'isnumeric'

,  

'isspace'

,  

'istitle'

,  

'isupper'

,  

'join'

,  

'len'

,  

'ljust'

,  

'lower'

,  

'lstrip'

,  

'match'

,  

'normalize'

,  

'pad'

,  

'partition'

,  

'repeat'

,  

'replace'

,  

'rfind'

,  

'rindex'

,  

'rjust'

,  

'rpartition'

,  

'rsplit'

,  

'rstrip'

,  

'slice'

,  

'slice_replace'

,  

'split'

,  

'startswith'

,  

'strip'

,  

'swapcase'

,  

'title'

,  

'translate'

,  

'upper'

,  

'wrap'

,  

'zfill'

]/<code>


屬性有很多,對於具體的用法,如果感興趣可以自己進行摸索練習。


dt對象的使用


Series數據類型:datetime


因為數據需要datetime類型,所以下面使用pandas的date_range()生成了一組日期datetime演示如何進行dt對象操作。


<code>

>>>

 

daterng

 

=

 

pd.Series(pd.date_range('2017',

 

periods=9,

 

freq='Q'))

>>>

 

daterng

0

   

2017

-03

-31

1

   

2017

-06

-30

2

   

2017

-09

-30

3

   

2017

-12

-31

4

   

2018

-03

-31

5

   

2018

-06

-30

6

   

2018

-09

-30

7

   

2018

-12

-31

8

   

2019

-03

-31

dtype:

 

datetime64[ns]

>>>

  

daterng.dt.day_name()

0

      

Friday

1

      

Friday

2

    

Saturday

3

      

Sunday

4

    

Saturday

5

    

Saturday

6

      

Sunday

7

      

Monday

8

      

Sunday

dtype:

 

object

>>>

 

>>>

 

daterng[daterng.dt.quarter

 

>

 

2

]

2

   

2017

-09

-30

3

   

2017

-12

-31

6

   

2018

-09

-30

7

   

2018

-12

-31

dtype:

 

datetime64[ns]

>>>

 

daterng[daterng.dt.is_year_end]

3

   

2017

-12

-31

7

   

2018

-12

-31

dtype:

 

datetime64[ns]

/<code>


以上關於dt的3種方法說明:

  • Series.dt.day_name():從日期判斷出所處星期數
  • Series.dt.quarter:從日期判斷所處季節
  • Series.dt.is_year_end:從日期判斷是否處在年底

其它方法也都是基於datetime的一些變換,並通過變換來查看具體微觀或者宏觀日期。


cat對象的使用


Series數據類型:Category


在說cat對象的使用前,先說一下Category這個數據類型,它的作用很強大。雖然我們沒有經常性的在內存中運行上g的數據,但是我們也總會遇到執行幾行代碼會等待很久的情況。使用Category數據的一個好處就是:可以很好的節省在時間和空間的消耗。下面我們通過幾個實例來學習一下。


<code>

>>>

 

colors

 

=

 

pd.Series([

...

     

'periwinkle'

,

...

     

'mint green'

,

...

     

'burnt orange'

,

...

     

'periwinkle'

,

...

     

'burnt orange'

,

...

     

'rose'

,

...

     

'rose'

,

...

     

'mint green'

,

...

     

'rose'

,

...

     

'navy'

...

 

])

...

>>>

 

import

 

sys

>>>

 

colors.apply(sys.getsizeof)

0

    

59

1

    

59

2

    

61

3

    

59

4

    

61

5

    

53

6

    

53

7

    

59

8

    

53

9

    

53

dtype:

 

int64

/<code>


上面我們通過使用sys.getsizeof來顯示內存佔用的情況,數字代表字節數。還有另一種計算內容佔用的方法:memory_usage(),後面會使用。


現在我們將上面colors的不重複值映射為一組整數,然後再看一下佔用的內存。


<code>

>>>

 

mapper

 

=

 

{v:

 

k

 

for

 

k,

 

v

 

in

 

enumerate(colors.unique())}

>>>

 

mapper

{'periwinkle':

 

0

,

 

'mint green'

:

 

1

,

 

'burnt orange'

:

 

2

,

 

'rose'

:

 

3

,

 

'navy'

:

 

4

}

>>>

 

as_int

 

=

 

colors.map(mapper)

>>>

 

as_int

0

    

0

1

    

1

2

    

2

3

    

0

4

    

2

5

    

3

6

    

3

7

    

1

8

    

3

9

    

4

dtype:

 

int64

>>>

 

as_int.apply(sys.getsizeof)

0

    

24

1

    

28

2

    

28

3

    

24

4

    

28

5

    

28

6

    

28

7

    

28

8

    

28

9

    

28

dtype:

 

int64

/<code>


注:對於以上的整數值映射也可以使用更簡單的pd.factorize()方法代替。


我們發現上面所佔用的內存是使用object類型時的一半。其實,這種情況就類似於Category data類型內部的原理。


內存佔用區別:Categorical所佔用的內存與Categorical分類的數量和數據的長度成正比,相反,object所佔用的內存則是一個常數乘以數據的長度。


下面是object內存使用和category內存使用的情況對比。


<code>>>> colors.memory_usage(index=

False

, deep=

True

)

650

>>> colors.astype(

'category'

).memory_usage(index=

False

, deep=

True

)

495

/<code>


上面結果是使用object和Category兩種情況下內存的佔用情況。我們發現效果並沒有我們想象中的那麼好。但是注意Category內存是成比例的,如果數據集的數據量很大,但不重複分類(unique)值很少的情況下,那麼Category的內存佔用可以節省達到10倍以上,比如下面數據量增大的情況:


<code> > manycolors = colors.repeat(

10

) > len(manycolors) / manycolors.nunique() 

20.0

> manycolors.memory_usage(index=False, deep=True)

6500

> manycolors.astype(

'category'

).memory_usage(index=False, deep=True)

585

/<code>


可以看到,在數據量增加10倍以後,使用Category所佔內容節省了10倍以上。


除了佔用內存節省外,另一個額外的好處是計算效率有了很大的提升。

因為對於Category類型的Series,str字符的操作發生在.cat.categories的非重複值上,而並非原Series上的所有元素上。也就是說對於每個非重複值都只做一次操作,然後再向與非重複值同類的值映射過去。


對於Category的數據類型,可以使用accessor的cat對象,以及相應的屬性和方法來操作Category數據。


<code> > ccolors = colors.astype(

'category'

) > ccolors.cat.categories Index([

'burnt orange'

'mint green'

'navy'

'periwinkle'

'rose'

], dtype=

'object'

)/<code>


實際上,對於開始的整數類型映射,可以先通過reorder_categories進行重新排序,然後再使用cat.codes來實現對整數的映射,來達到同樣的效果。


<code> 

>>>

 

ccolors.cat.reorder_categories(mapper).cat.codes

0

    

0

1

    

1

2

    

2

3

    

0

4

    

2

5

    

3

6

    

3

7

    

1

8

    

3

9

    

4

dtype:

 

int8

/<code>


dtype類型是Numpy的int8(-127~128)。可以看出以上只需要一個單字節就可以在內存中包含所有的值。我們開始的做法默認使用了int64類型,然而通過pandas的使用可以很智能的將Category數據類型變為最小的類型。


讓我們來看一下cat還有什麼其它的屬性和方法可以使用。下面cat的這些屬性基本都是關於查看和操作Category數據類型的。


<code> > [i for i in dir(ccolors.cat) if not i.startswith(

'_'

)] [

'add_categories'

,  

'as_ordered'

,  

'as_unordered'

,  

'categories'

,  

'codes'

,  

'ordered'

,  

'remove_categories'

,  

'remove_unused_categories'

,  

'rename_categories'

,  

'reorder_categories'

,  

'set_categories'

]/<code>


但是Category數據的使用不是很靈活。例如,插入一個之前沒有的值,首先需要將這個值添加到.categories的容器中,然後再添加值。


<code> > ccolors.iloc[

5

] = 

'a new color'

ValueError:

 Cannot setitem on a Categorical with a new category, set the categories first > ccolors = ccolors.cat.add_categories([

'a new color'

]) > ccolors.iloc[

5

] = 

'a new color'

  /<code>


如果你想設置值或重塑數據,而非進行新的運算操作,那麼Category類型不是那麼有用。


二、從clipboard剪切板載入數據


當我們的數據存在excel表裡,或者其它的IDE編輯器中的時候,我們想要通過pandas載入數據。我們通常的做法是先保存再載入,其實這樣做起來十分繁瑣。一個簡單的方法就是使用pd.read_clipboard() 直接從電腦的剪切板緩存區中提取數據。


這樣我們就可以直接將結構數據轉變為DataFrame或者Series了。excel表中數據是這樣的:


那些功能逆天,卻鮮為人知的pandas騷操作


在純文本文件中,比如txt文件,是這樣的:


<code>

a

   

b

           

c

       

d

0

   

1

           

inf

     

1

/1/00

2

   

7.389056099

 

N/A

     

5

-Jan-13

4

   

54.59815003

 

nan

     

7

/24/18

6

   

403.4287935

 

None

    

NaT

/<code>


將上面excel或者txt中的數據選中然後複製,然後使用pandas的read_clipboard()即可完成到DataFrame的轉換。parse_dates參數設置為 "d",可以自動識別日期,並調整為xxxx-xx-xx的格式。


<code>

>>>

 

df

 

=

 

pd.read_clipboard(na_values=[None],

 

parse_dates=['d'])

>>>

 

df

   

a

         

b

    

c

          

d

0

  

0

    

1.0000

  

inf

 

2000

-01

-01

1

  

2

    

7.3891

  

NaN

 

2013

-01

-05

2

  

4

   

54.5982

  

NaN

 

2018

-07

-24

3

  

6

  

403.4288

  

NaN

        

NaT

>>>

 

df.dtypes

a

             

int64

b

           

float64

c

           

float64

d

    

datetime64[ns]

dtype:

 

object

/<code>


三、將pandas對象轉換為“壓縮”格式


在pandas中,我們可以直接將objects打包成為 gzip, bz2, zip, or xz 等壓縮格式,而不必將沒壓縮的文件放在內存中然後進行轉化。來看一個例子如何使用:


<code>

>>>

 

abalone

 

=

 

pd.read_csv(url,

 

usecols=[0,

 

1

,

 

2

,

 

3

,

 

4

,

 

8

],

 

names=cols)

>>>

 

abalone

     

sex

  

length

   

diam

  

height

  

weight

  

rings

0

      

M

   

0.455

  

0.365

   

0.095

  

0.5140

     

15

1

      

M

   

0.350

  

0.265

   

0.090

  

0.2255

      

7

2

      

F

   

0.530

  

0.420

   

0.135

  

0.6770

      

9

3

      

M

   

0.440

  

0.365

   

0.125

  

0.5160

     

10

4

      

I

   

0.330

  

0.255

   

0.080

  

0.2050

      

7

5

      

I

   

0.425

  

0.300

   

0.095

  

0.3515

      

8

6

      

F

   

0.530

  

0.415

   

0.150

  

0.7775

     

20

...

   

..

     

...

    

...

     

...

     

...

    

...

4170

   

M

   

0.550

  

0.430

   

0.130

  

0.8395

     

10

4171

   

M

   

0.560

  

0.430

   

0.155

  

0.8675

      

8

4172

   

F

   

0.565

  

0.450

   

0.165

  

0.8870

     

11

4173

   

M

   

0.590

  

0.440

   

0.135

  

0.9660

     

10

4174

   

M

   

0.600

  

0.475

   

0.205

  

1.1760

      

9

4175

   

F

   

0.625

  

0.485

   

0.150

  

1.0945

     

10

4176

   

M

   

0.710

  

0.555

   

0.195

  

1.9485

     

12

/<code>


導入文件,讀取並存為abalone(DataFrame結構)。當我們要存為壓縮的時候,簡單的使用 to_json() 即可輕鬆完成轉化過程。下面通過設置相應參數將abalone存為了.gz格式的壓縮文件。


<code>

abalone

.to_json

(

'df.json.gz'

, orient=

'records'

,                 lines=True, compression=

'gzip'

)/<code>


如果我們想知道儲存壓縮文件的大小,可以通過內置模塊os.path,使用getsize方法來查看文件的字節數。下面是兩種格式儲存文件的大小對比。


<code>>>> import 

os

.

path

>>> abalone.to_json(

'df.json'

, orient=

'records'

lines

=True) >>> 

os

.

path

.getsize(

'df.json'

) / 

os

.

path

.getsize(

'df.json.gz'

)

11.603035760226396

/<code>


四、使用"測試模塊"製作偽數據


在pandas中,有一個測試模塊可以幫助我們生成半真實(偽數據),並進行測試,它就是util.testing。下面同我們通過一個簡單的例子看一下如何生成數據測試:


<code>

>>>

 

import

 

pandas.util.testing

 

as

 

tm

>>>

 

tm.N,

 

tm.K

 

=

 

15

,

 

3

  

>>>

 

import

 

numpy

 

as

 

np

>>>

 

np.random.seed(444)

>>>

 

tm.makeTimeDataFrame(freq='M').head()

                 

A

       

B

       

C

2000

-01

-31

  

0.3574

 

-0.8804

  

0.2669

2000

-02

-29

  

0.3775

  

0.1526

 

-0.4803

2000

-03

-31

  

1.3823

  

0.2503

  

0.3008

2000

-04

-30

  

1.1755

  

0.0785

 

-0.1791

2000

-05

-31

 

-0.9393

 

-0.9039

  

1.1837

>>>

 

tm.makeDataFrame().head()

                 

A

       

B

       

C

nTLGGTiRHF

 

-0.6228

  

0.6459

  

0.1251

WPBRn9jtsR

 

-0.3187

 

-0.8091

  

1.1501

7B3wWfvuDA

 

-1.9872

 

-1.0795

  

0.2987

yJ0BTjehH1

  

0.8802

  

0.7403

 

-1.2154

0luaYUYvy1

 

-0.9320

  

1.2912

 

-0.2907

/<code>


上面簡單的使用了

makeTimeDataFrame 和 makeDataFrame 分別生成了一組時間數據和DataFrame的數據。但這只是其中的兩個用法,關於testing中的方法有大概30多個,如果你想全部瞭解,可以通過查看dir獲得:


<code> > [i for i in dir(tm) if i.startswith(

'make'

)] [

'makeBoolIndex'

,  

'makeCategoricalIndex'

,  

'makeCustomDataframe'

,  

'makeCustomIndex'

,    

'makeTimeSeries'

,  

'makeTimedeltaIndex'

,  

'makeUIntIndex'

,  

'makeUnicodeIndex'

]/<code>


五、從列項中創建DatetimeIndex


也許我們有的時候會遇到這樣的情形(為了說明這種情情況,我使用了product進行交叉迭代的創建了一組關於時間的數據):


<code>

>>>

 

from

 

itertools

 

import

 

product

>>>

 

datecols

 

=

 

['year',

 

'month'

,

 

'day'

]

>>>

 

df

 

=

 

pd.DataFrame(list(product([2017,

 

2016

],

 

[1,

 

2

],

 

[1,

 

2

,

 

3

])),

...

                   

columns=datecols)

>>>

 

df['data']

 

=

 

np.random.randn(len(df))

>>>

 

df

    

year

  

month

  

day

    

data

0

   

2017

      

1

    

1

 

-0.0767

1

   

2017

      

1

    

2

 

-1.2798

2

   

2017

      

1

    

3

  

0.4032

3

   

2017

      

2

    

1

  

1.2377

4

   

2017

      

2

    

2

 

-0.2060

5

   

2017

      

2

    

3

  

0.6187

6

   

2016

      

1

    

1

  

2.3786

7

   

2016

      

1

    

2

 

-0.4730

8

   

2016

      

1

    

3

 

-2.1505

9

   

2016

      

2

    

1

 

-0.6340

10

  

2016

      

2

    

2

  

0.7964

11

  

2016

      

2

    

3

  

0.0005

/<code>


明顯看到,列項中有year,month,day,它們分別在各個列中,而並非是一個完整日期。那麼如何從這些列中將它們組合在一起並設置為新的index呢?


通過to_datetime的使用,我們就可以直接將年月日組合為一個完整的日期,然後賦給索引。代碼如下:


<code>

>>>

 

df.index

 

=

 

pd.to_datetime(df[datecols])

>>>

 

df.head()

            

year

  

month

  

day

    

data

2017

-01

-01

  

2017

      

1

    

1

 

-0.0767

2017

-01

-02

  

2017

      

1

    

2

 

-1.2798

2017

-01

-03

  

2017

      

1

    

3

  

0.4032

2017

-02

-01

  

2017

      

2

    

1

  

1.2377

2017

-02

-02

  

2017

      

2

    

2

 

-0.2060

/<code>


當然,你可以選擇將原有的年月日列移除,只保留data數據列,然後squeeze轉換為Series結構。


<code>

>>>

 

df

 

=

 

df.drop(datecols,

 

axis=1).squeeze()

>>>

 

df.head()

2017

-01

-01

   

-0.0767

2017

-01

-02

   

-1.2798

2017

-01

-03

    

0.4032

2017

-02

-01

    

1.2377

2017

-02

-02

   

-0.2060

Name:

 

data,

 

dtype:

 

float64

>>>

 

df.index.dtype_str

'datetime64[ns]

/<code>
<code>

/<code> 


分享到:


相關文章: