文章來源: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 zip0
Washington DC20003
1
Brooklyn NY11211
-1755
2
Omaha NE68154
3
Pittsburgh PA15211
/<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表中數據是這樣的:
在純文本文件中,比如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>>>> importos
.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>