這一期主要是和你們分享一些pandas的實用技巧,會在平常使用中大大提高效率,但願能夠幫助到你們,仍是老樣子, 先給你們奉上這一期的章節目錄:python
好啦,話很少說,讓咱們一個個看吧git
首先,你們可能不知道,pandas裏面有一個方法pd.set_option(),利用它咱們能夠改變一些pandas中默認的核心設置, 從而適應咱們自身的須要,開始前仍是老樣子,讓咱們先導入numpy和pandas包github
import numpy as np
import pandas as pd
f'Using {pd.__name__}, Version {pd.__version__}'
複製代碼
'Using pandas, Version 0.23.0'
複製代碼
如今讓咱們編寫一個start方法來實現自定義pandas設置json
def start():
options = {
'display': {
'max_columns': None,
'max_colwidth': 25,
'expand_frame_repr': False, # Don't wrap to multiple pages
'max_rows': 14,
'max_seq_items': 50, # Max length of printed sequence
'precision': 4,
'show_dimensions': False
},
'mode': {
'chained_assignment': None # Controls SettingWithCopyWarning
}
}
for category, option in options.items():
for op, value in option.items():
pd.set_option(f'{category}.{op}', value) # Python 3.6+
if __name__ == '__main__':
start()
del start # Clean up namespace in the interpreter
複製代碼
你們能夠發現,咱們在方法的最後調用了pandas的set_option方法,直接利用咱們自定義的參數替代了原有的pandas參數,如今讓咱們測試一下:api
pd.get_option('display.max_rows')
複製代碼
14
複製代碼
能夠發現max_rows 已經被替換成了咱們設置的14,如今用一個真實的例子,咱們利用一組公開的鮑魚各項指標的數據來實驗,數據源來自機器學習平臺的公開數據數組
url = ('https://archive.ics.uci.edu/ml/'
'machine-learning-databases/abalone/abalone.data')
cols = ['sex', 'length', 'diam', 'height', 'weight', 'rings']
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 |
咱們能夠看到,數據截斷爲14行,保留了小數點後4位小數做爲精度,和咱們剛剛設置的precision=4是同樣的app
經過pandas.util.testing提供的方法,咱們能夠很容易的經過幾行代碼就構建出一個簡單的測試數據類型,好比咱們如今構建一個DataTime類型的數據, 時間間隔爲月:dom
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() # 設置時間間隔爲月
# tm.makeTimeDataFrame(freq='D').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 |
關於能夠隨機生成的數據類型, 一共大概有30多種,你們若是感興趣能夠多試試:學習
[i for i in dir(tm) if i.startswith('make')]
複製代碼
['makeBoolIndex',
'makeCategoricalIndex',
'makeCustomDataframe',
'makeCustomIndex',
'makeDataFrame',
'makeDateIndex',
'makeFloatIndex',
'makeFloatSeries',
'makeIntIndex',
'makeIntervalIndex',
'makeMissingCustomDataframe',
'makeMissingDataframe',
'makeMixedDataFrame',
'makeMultiIndex',
'makeObjectSeries',
'makePanel',
'makePeriodFrame',
'makePeriodIndex',
'makePeriodPanel',
'makePeriodSeries',
'makeRangeIndex',
'makeStringIndex',
'makeStringSeries',
'makeTimeDataFrame',
'makeTimeSeries',
'makeTimedeltaIndex',
'makeUIntIndex',
'makeUnicodeIndex']
複製代碼
這樣咱們若是有測試的需求,會很容易地構建相對應的假數據來測試。
accessor(訪問器) 具體就是相似getter和setter,固然,Python裏面不提倡存在setter和getter方法,可是這樣能夠便於你們理解,pandas Series類型有3類accessor:
pd.Series._accessors
複製代碼
{'cat', 'dt', 'str'}
複製代碼
讓咱們從.str開始看:假設如今咱們有一些原始的城市/州/ 郵編數據做爲Dataframe的一個字段:
addr = pd.Series([
'Washington, D.C. 20003',
'Brooklyn, NY 11211-1755',
'Omaha, NE 68154',
'Pittsburgh, PA 15211'
])
複製代碼
addr.str.upper() # 由於字符串方法是矢量化的,這意味着它們在沒有顯式for循環的狀況下對整個數組進行操做
複製代碼
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
複製代碼
若是咱們想把每一行分紅城市,州,郵編分開,能夠用正則;
regex = (r'(?P<city>[A-Za-z ]+), ' # One or more letters
r'(?P<state>[A-Z]{2}) ' # 2 capital letters
r'(?P<zip>\d{5}(?:-\d{4})?)') # Optional 4-digit extension
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 |
第二個訪問器.dt用於相似日期時間的數據。它其實屬於Pandas的DatetimeIndex,若是在Series上調用,它首先轉換爲DatetimeIndex
daterng = pd.Series(pd.date_range('2018', periods=9, freq='Q')) # 時間間隔爲季度
daterng
複製代碼
0 2018-03-31
1 2018-06-30
2 2018-09-30
3 2018-12-31
4 2019-03-31
5 2019-06-30
6 2019-09-30
7 2019-12-31
8 2020-03-31
dtype: datetime64[ns]
複製代碼
daterng.dt.day_name()
複製代碼
0 Saturday
1 Saturday
2 Sunday
3 Monday
4 Sunday
5 Sunday
6 Monday
7 Tuesday
8 Tuesday
dtype: object
複製代碼
daterng[daterng.dt.quarter > 2] # 查看2019年第3季度和第4季度
複製代碼
2 2018-09-30
3 2018-12-31
6 2019-09-30
7 2019-12-31
dtype: datetime64[ns]
複製代碼
daterng[daterng.dt.is_year_end] #查看年底的一天
複製代碼
3 2018-12-31
7 2019-12-31
dtype: datetime64[ns]
複製代碼
最後有關.cat訪問器咱們會在第5個技巧中提到
如今先讓咱們構建一個包含時間類型數據的Dataframe:
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 |
咱們能夠發現year,month,day是分開的三列,咱們若是想要把它們合併爲完整的時間並做爲df的索引,能夠這麼作:
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 |
咱們能夠扔掉沒用的列並把這個df壓縮爲Series:
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
複製代碼
type(df)
複製代碼
pandas.core.series.Series
複製代碼
df.index.dtype_str
複製代碼
'datetime64[ns]'
複製代碼
剛剛咱們在第3個技巧的時候提到了訪問器,如今讓咱們來看最後一個.cat
pandas中Categorical這個數據類型很是強大,經過類型轉換可讓咱們節省變量在內存佔用的空間,提升運算速度,不過有關具體的pandas加速實戰,我會在 下一期說,如今讓咱們來看一個小栗子:
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
複製代碼
咱們首先建立了一個Series,填充了各類顏色,接着查看了每一個地址對應的顏色所佔內存的大小
注意這裏咱們使用sys.getsizeof()來獲取佔內存大小,可是實際上空格也是佔內存的,sys.getsizeof('')返回的是49bytes
接下來咱們想把每種顏色用佔內存更少的數字來表示(機器學習種很是常見),這樣能夠減小佔用的內存,首先讓咱們建立一個mapper字典,給每一種顏色指定 一個數字
mapper = {v: k for k, v in enumerate(colors.unique())}
mapper
複製代碼
{'periwinkle': 0, 'mint green': 1, 'burnt orange': 2, 'rose': 3, 'navy': 4}
複製代碼
接着咱們把剛纔的colors數組轉化爲int類型:
# 也能夠經過 pd.factorize(colors)[0] 實現
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
複製代碼
如今能夠觀察到咱們的內存佔用的空間幾乎是以前的一半,其實,剛剛咱們作的正是模擬Categorical Data的轉化原理。如今讓咱們直接調用一下:
colors.memory_usage(index=False, deep=True)
Out:650
複製代碼
colors.astype('category').memory_usage(index=False, deep=True)
Out: 495
複製代碼
你們可能感受節省的空間並非很是大對不對? 由於目前咱們這個數據根本不是真實場景,咱們僅僅把數據容量增長10倍,如今再讓咱們看看效果:
manycolors = colors.repeat(10)
len(manycolors) / manycolors.nunique() # Much greater than 2.0x
Out:20.0
複製代碼
f"Not using category : { manycolors.memory_usage(index=False, deep=True)}"
複製代碼
'Not using category : 6500'
複製代碼
f"Using category : { manycolors.astype('category').memory_usage(index=False, deep=True)}"
複製代碼
'Using category : 585'
複製代碼
這回內存的佔用量差距明顯就出來了,如今讓咱們用.cat來簡化一下剛剛的工做:
new_colors = colors.astype('category')
new_colors
複製代碼
0 periwinkle
1 mint green
2 burnt orange
3 periwinkle
4 burnt orange
5 rose
6 rose
7 mint green
8 rose
9 navy
dtype: category
Categories (5, object): [burnt orange, mint green, navy, periwinkle, rose]
複製代碼
new_colors.cat.categories # 可使用.cat.categories查看錶明的顏色
複製代碼
Index(['burnt orange', 'mint green', 'navy', 'periwinkle', 'rose'], dtype='object')
複製代碼
如今讓咱們查看把顏色表明的數字:
new_colors.cat.codes
複製代碼
0 3
1 1
2 0
3 3
4 0
5 4
6 4
7 1
8 4
9 2
dtype: int8
複製代碼
咱們若是不滿意順序也能夠重新排序:
new_colors.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
複製代碼
有關cat其餘的方法,咱們仍是能夠經過遍歷dir來查看:
[i for i in dir(new_colors.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']
複製代碼
Categorical 數據一般不太靈活,好比咱們不能直接在new_colors上新增一個新的顏色,要首先經過 .add_categories來添加
ccolors.iloc[5] = 'a new color'
複製代碼
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-36-1766a795336d> in <module>()
----> 1 ccolors.iloc[5] = 'a new color'
NameError: name 'ccolors' is not defined
複製代碼
new_colors = new_colors.cat.add_categories(['a new color'])
複製代碼
new_colors.iloc[5] = 'a new color' # 不會報錯
複製代碼
new_colors.values # 成功添加
複製代碼
假設如今咱們有存貯國家的一組數據,和一組用來映射國家所對應的大洲的數據:
countries = pd.Series([
'United States',
'Canada',
'Mexico',
'Belgium',
'United Kingdom',
'Thailand'
])
groups = {
'North America': ('United States', 'Canada', 'Mexico', 'Greenland'),
'Europe': ('France', 'Germany', 'United Kingdom', 'Belgium')
}
複製代碼
咱們能夠經過下面的方法來實現簡單的映射:
from typing import Any
def membership_map(s: pd.Series, groups: dict, fillvalue: Any=-1) -> pd.Series:
# Reverse & expand the dictionary key-value pairs
groups = {x: k for k, v in groups.items() for x in v}
return s.map(groups).fillna(fillvalue)
複製代碼
membership_map(countries, groups, fillvalue='other')
複製代碼
很簡單對不對,如今讓咱們看一下最關鍵的一行代碼,groups = {x: k for k, v in groups.items() for x in v},這個是我以前提到過的字典推導式:
test = dict(enumerate(('ab', 'cd', 'xyz')))
{x: k for k, v in test.items() for x in v}
複製代碼
若是你的pandas版本大於0.21.0,那麼均可以直接把pandas用壓縮形式寫入,常見的類型有gzip, bz2, zip,這裏咱們直接用剛纔鮑魚的數據集:
複製代碼
abalone.to_json('df.json.gz', orient='records',lines=True, compression='gzip') # 壓縮爲gz類型
abalone.to_json('df.json', orient='records', lines=True) #壓縮爲json
複製代碼
import os.path
os.path.getsize('df.json') / os.path.getsize('df.json.gz') #壓縮大小差了10倍,仍是gz更厲害
複製代碼
這一期家總結了不少pandas實用的小技巧,但願你們喜歡
我把這一期的ipynb文件和py文件放到了Github上,你們若是想要下載能夠點擊下面的連接: