數據科學 | 從 Pandas 小白到 Pandas 能手

2021-03-02 運籌OR帷幄
在本文中,作者從 Pandas 的簡介開始,一步一步講解了 Pandas 的發展現狀、內存優化等問題。這是一篇最佳實踐教程,既適合用過 Pandas 的讀者,也適合沒用過但想要上手的小白。通過本文,你將有望發現一到多種用 pandas 編碼的新方法。

Pandas 發展現狀;

內存優化;

索引;

方法鏈;

隨機提示。

在閱讀本文時,我建議你閱讀每個你不了解的函數的文檔字符串(docstrings)。簡單的 Google 搜索和幾秒鐘 Pandas 文檔的閱讀,都會使你的閱讀體驗更加愉快。Pandas 是一個「開源的、有 BSD 開源協議的庫,它為 Python 程式語言提供了高性能、易於使用的數據架構以及數據分析工具」。總之,它提供了被稱為 DataFrame 和 Series(對那些使用 Panel 的人來說,它們已經被棄用了)的數據抽象,通過管理索引來快速訪問數據、執行分析和轉換運算,甚至可以繪圖(用 matplotlib 後端)。Pandas 的當前最新版本是 v0.25.0 (https://github.com/pandas-dev/pandas/releases/tag/v0.25.0)Pandas 正在逐步升級到 1.0 版,而為了達到這一目的,它改變了很多人們習以為常的細節。Pandas 的核心開發者之一 Marc Garcia 發表了一段非常有趣的演講——「走向 Pandas 1.0」。演講連結:https://www.youtube.com/watch?v=hK6o_TDXXN8用一句話來總結,Pandas v1.0 主要改善了穩定性(如時間序列)並刪除了未使用的代碼庫(如 SparseDataFrame)。讓我們開始吧!選擇「1985 到 2016 年間每個國家的自殺率」作為玩具數據集。這個數據集足夠簡單,但也足以讓你上手 Pandas。數據集連結:https://www.kaggle.com/russellyates88/suicide-rates-overview-1985-to-2016在深入研究代碼之前,如果你想重現結果,要先執行下面的代碼準備數據,確保列名和類型是正確的。
import pandas as pdimport numpy as npimport os# to download https:
data_path = 'path/to/folder/'df = (pd.read_csv(filepath_or_buffer=os.path.join(data_path, 'master.csv')) .rename(columns={'suicides/100k pop' : 'suicides_per_100k', ' gdp_for_year ($) ' : 'gdp_year',  'gdp_per_capita ($)' : 'gdp_capita', 'country-year' : 'country_year'}) .assign(gdp_year=lambda _df: _df['gdp_year'].str.replace(',','').astype(np.int64)) )

提示:如果你讀取了一個大文件,在 read_csv(https://pandas.pydata.org/pandasdocs/stable/reference/api/pandas.read_csv.html)中參數設定為 chunksize=N,這會返回一個可以輸出 DataFrame 對象的迭代器。
>>> df.columnsIndex(['country', 'year', 'sex', 'age', 'suicides_no', 'population',       'suicides_per_100k', 'country_year', 'HDI for year', 'gdp_year',       'gdp_capita', 'generation'],      dtype='object')

這裡有 101 個國家、年份從 1985 到 2016、兩種性別、六個年代以及六個年齡組。有一些獲得這些信息的方法:可以用 unique() 和 nunique() 獲取列內唯一的值(或唯一值的數量);
>>> df['generation'].unique()array(['Generation X', 'Silent', 'G.I. Generation', 'Boomers', 'Millenials', 'Generation Z'], dtype=object)>>> df['country'].nunique()101

可以用 describe() 輸出每一列不同的統計數據(例如最小值、最大值、平均值、總數等),如果指定 include='all',會針對每一列目標輸出唯一元素的數量和出現最多元素的數量;可以用 head() 和 tail() 來可視化數據框的一小部分。在處理數據之前,了解數據並為數據框的每一列選擇合適的類型是很重要的一步。在內部,Pandas 將數據框存儲為不同類型的 numpy 數組(比如一個 float64 矩陣,一個 int32 矩陣)。
import pandas as pd

def mem_usage(df: pd.DataFrame) -> str: """This method styles the memory usage of a DataFrame to be readable as MB. Parameters df: pd.DataFrame Data frame to measure. Returns -- str Complete memory usage as a string formatted for MB. """ return f'{df.memory_usage(deep=True).sum() / 1024 ** 2 : 3.2f} MB'

def convert_df(df: pd.DataFrame, deep_copy: bool = True) -> pd.DataFrame: """Automatically converts columns that are worth stored as ``categorical`` dtype. Parameters df: pd.DataFrame Data frame to convert. deep_copy: bool Whether or not to perform a deep copy of the original data frame. Returns -- pd.DataFrame Optimized copy of the input data frame. """ return df.copy(deep=deep_copy).astype({ col: 'category' for col in df.columns if df[col].nunique() / df[col].shape[0] < 0.5})

Pandas 提出了一種叫做 memory_usage() 的方法,這種方法可以分析數據框的內存消耗。在代碼中,指定 deep=True 來確保考慮到了實際的系統使用情況。memory_usage():https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.memory_usage.html(https://pandas.pydata.org/pandasdocs/stable/getting_started/basics.html#basics-dtypes)很重要。它可以通過兩種簡單的方法節省高達 90% 的內存使用:除了降低數值類型的大小(用 int32 而不是 int64)外,Pandas 還提出了分類類型:https://pandas.pydata.org/pandas-docs/stable/user_guide/categorical.html如果你是用 R 語言的開發人員,你可能覺得它和 factor 類型是一樣的。這種分類類型允許用索引替換重複值,還可以把實際值存在其他位置。教科書中的例子是國家。和多次存儲相同的字符串「瑞士」或「波蘭」比起來,為什麼不簡單地用 0 和 1 替換它們,並存儲在字典中呢?
categorical_dict = {0: 'Switzerland', 1: 'Poland'}

Pandas 做了幾乎相同的工作,同時添加了所有的方法,可以實際使用這種類型,並且仍然能夠顯示國家的名稱。回到 convert_df() 方法,如果這一列中的唯一值小於 50%,它會自動將列類型轉換成 category。這個數是任意的,但是因為數據框中類型的轉換意味著在 numpy 數組間移動數據,因此我們得到的必須比失去的多。
>>> mem_usage(df)10.28 MB>>> mem_usage(df.set_index(['country', 'year', 'sex', 'age']))5.00 MB>>> mem_usage(convert_df(df))1.40 MB>>> mem_usage(convert_df(df.set_index(['country', 'year', 'sex', 'age'])))1.40 MB

通過使用「智能」轉換器,數據框使用的內存幾乎減少了 10 倍(準確地說是 7.34 倍)。Pandas 是強大的,但也需要付出一些代價。當你加載 DataFrame 時,它會創建索引並將數據存儲在 numpy 數組中。這是什麼意思?一旦加載了數據框,只要正確管理索引,就可以快速地訪問數據。訪問數據的方法主要有兩種,分別是通過索引和查詢訪問。根據具體情況,你只能選擇其中一種。但在大多數情況中,索引(和多索引)都是最好的選擇。我們來看下面的例子:
>>> %%time>>> df.query('country == "Albania" and year == 1987 and sex == "male" and age == "25-34 years"')CPU times: user 7.27 ms, sys: 751 µs, total: 8.02 ms# ==================>>> %%time>>> mi_df.loc['Albania', 1987, 'male', '25-34 years']CPU times: user 459 µs, sys: 1 µs, total: 460 µs

%%timemi_df = df.set_index(['country', 'year', 'sex', 'age'])CPU times: user 10.8 ms, sys: 2.2 ms, total: 13 ms

通過查詢訪問數據的時間是 1.5 倍。如果你只想檢索一次數據(這種情況很少發生),查詢是正確的方法。否則,你一定要堅持用索引,CPU 會為此感激你的。.set_index(drop=False) 允許不刪除用作新索引的列。.loc[]/.iloc[] 方法可以很好地讀取數據框,但無法修改數據框。如果需要手動構建(比如使用循環),那就要考慮其他的數據結構了(比如字典、列表等),在準備好所有數據後,創建 DataFrame。否則,對於 DataFrame 中的每一個新行,Pandas 都會更新索引,這可不是簡單的哈希映射。
>>> (pd.DataFrame({'a':range(2), 'b': range(2)}, index=['a', 'a']) .loc['a'])   a ba 0 0a 1 1

因此,未排序的索引可以降低性能。為了檢查索引是否已經排序並對它排序,主要有兩種方法:
%%time>>> mi_df.sort_index()CPU times: user 34.8 ms, sys: 1.63 ms, total: 36.5 ms>>> mi_df.index.is_monotonicTrue‍

使用 DataFrame 的方法鏈是連結多個返回 DataFrame 方法的行為,因此它們都是來自 DataFrame 類的方法。在現在的 Pandas 版本中,使用方法鏈是為了不存儲中間變量並避免出現如下情況:
import numpy as npimport pandas as pddf = pd.DataFrame({'a_column': [1, -999, -999], 'powerless_column': [2, 3, 4], 'int_column': [1, 1, -1]}) df['a_column'] = df['a_column'].replace(-999, np.nan) df['power_column'] = df['powerless_column'] ** 2 df['real_column'] = df['int_column'].astype(np.float64) df = df.apply(lambda _df: _df.replace(4, np.nan)) df = df.dropna(how='all')

df = (pd.DataFrame({'a_column': [1, -999, -999], 'powerless_column': [2, 3, 4], 'int_column': [1, 1, -1]}) .assign(a_column=lambda _df: _df['a_column'].replace(-999, np.nan)) .assign(power_column=lambda _df: _df['powerless_column'] ** 2) .assign(real_column=lambda _df: _df['int_column'].astype(np.float64)) .apply(lambda _df: _df.replace(4, np.nan)) .dropna(how='all') )

方法鏈的工具箱是由不同的方法(比如 apply、assign、loc、query、pipe、groupby 以及 agg)組成的,這些方法的輸出都是 DataFrame 對象或 Series 對象
(df .groupby('age') .agg({'generation':'unique'}) .rename(columns={'generation':'unique_generation'})# Recommended from v0.25# .agg(unique_generation=('generation', 'unique')))

除了了解到「X 代」覆蓋了三個年齡組外,分解這條鏈。第一步是對年齡組分組。這一方法返回了一個 DataFrameGroupBy 對象,在這個對象中,通過選擇組的唯一年代標籤聚合了每一組。在這種情況下,聚合方法是「unique」方法,但它也可以接受任何(匿名)函數。在 0.25 版本中,Pandas 引入了使用 agg 的新方法:https://dev.pandas.io/whatsnew/v0.25.0.html#groupby-aggregation-with-relabeling。
(df .groupby(['country', 'year']) .agg({'suicides_per_100k': 'sum'}) .rename(columns={'suicides_per_100k':'suicides_sum'})# Recommended from v0.25# .agg(suicides_sum=('suicides_per_100k', 'sum')) .sort_values('suicides_sum', ascending=False) .head(10))

用排序值(sort_values)和 head 得到自殺率排前十的國家和年份
(df .groupby(['country', 'year']) .agg({'suicides_per_100k': 'sum'}) .rename(columns={'suicides_per_100k':'suicides_sum'})# Recommended from v0.25# .agg(suicides_sum=('suicides_per_100k', 'sum')) .nlargest(10, columns='suicides_sum'))

用排序值 nlargest 得到自殺率排前十的國家和年份
在這些例子中,輸出都是一樣的:有兩個指標(國家和年份)的 MultiIndex 的 DataFrame,還有包含排序後的 10 個最大值的新列 suicides_sum。nlargest(10) 比 sort_values(ascending=False).head(10) 更有效。另一個有趣的方法是 unstack:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.unstack.html,這種方法允許轉動索引水平。
(mi_df .loc[('Switzerland', 2000)] .unstack('sex') [['suicides_no', 'population']])

「age」是索引,列「suicides_no」和「population」都有第二個水平列「sex」。下一個方法 pipe 是最通用的方法之一。這種方法允許管道運算(就像在 shell 腳本中)執行比鏈更多的運算。
def log_head(df, head_count=10):     print(df.head(head_count))     return df
def log_columns(df):    print(df.columns)    return df
def log_shape(df):    print(f'shape = {df.shape}')    return df

舉個例子,我們想驗證和 year 列相比,country_year 是否正確:
(df .assign(valid_cy=lambda _serie: _serie.apply( lambda _row: re.split(r'(?=\d{4})', _row['country_year'])[1] == str(_row['year']), axis=1)) .query('valid_cy == False') .pipe(log_shape))

用來驗證「country_year」列中年份的管道。管道的輸出是 DataFrame,但它也可以在標準輸出(console/REPL)中列印。
(df .pipe(log_shape) .query('sex == "female"') .groupby(['year', 'country']) .agg({'suicides_per_100k':'sum'}) .pipe(log_shape) .rename(columns={'suicides_per_100k':'sum_suicides_per_100k_female'})# Recommended from v0.25# .agg(sum_suicides_per_100k_female=('suicides_per_100k', 'sum')) .nlargest(n=10, columns=['sum_suicides_per_100k_female']))

shape = (27820, 12)shape = (2321, 1)

除了記錄到控制臺外,pipe 還可以直接在數據框的列上應用函數。
from sklearn.preprocessing import MinMaxScaler
def norm_df(df, columns):    return df.assign(**{col: MinMaxScaler().fit_transform(df[[col]].values.astype(float))      for col in columns})  
for sex in ['male', 'female']:     print(sex)     print( df .query(f'sex == "{sex}"')     .groupby(['country'])     .agg({'suicides_per_100k': 'sum', 'gdp_year': 'mean'})     .rename(columns={'suicides_per_100k':'suicides_per_100k_sum',  'gdp_year': 'gdp_year_mean'})    # Recommended in v0.25    # .agg(suicides_per_100k=('suicides_per_100k_sum', 'sum'),     # gdp_year=('gdp_year_mean', 'mean'))     .pipe(norm_df, columns=['suicides_per_100k_sum', 'gdp_year_mean'])     .corr(method='spearman') )     print('\n')

自殺數量是否和 GDP 的下降相關?是否和性別相關?
male                    suicides_per_100k_sum gdp_year_meansuicides_per_100k_sum       1.000000         0.421218gdp_year_mean               0.421218         1.000000

female                     suicides_per_100k_sum gdp_year_meansuicides_per_100k_sum        1.000000         0.452343gdp_year_mean                0.452343         1.000000

深入研究代碼。norm_df() 將一個 DataFrame 和用 MinMaxScaling 擴展列的列表當做輸入。使用字典理解,創建一個字典 {column_name: method, …},然後將其解壓為 assign() 函數的參數 (colunmn_name=method, …)。在這種特殊情況下,min-max 縮放不會改變對應的輸出:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.corr.html,它僅用於參數。在(遙遠的?)未來,緩式評估(lazy evaluation)可能出現在方法鏈中,所以在鏈上做一些投資可能是一個好想法。itertuples() 可以更高效地遍歷數據框的行;
>>> %%time>>> for row in df.iterrows(): continueCPU times: user 1.97 s, sys: 17.3 ms, total: 1.99 s>>> for tup in df.itertuples(): continueCPU times: user 55.9 ms, sys: 2.85 ms, total: 58.8 ms

在 Jupyter 筆記本中,在代碼塊的開頭寫上 %%time,可以有效地測量時間;UInt8 類:https://pandas.pydata.org/pandas-docs/stable/user_guide/gotchas.html#support-for-integer-na支持帶有整數的 NaN 值;記住,任何密集的 I/O(例如展開大型 CSV 存儲)用低級方法都會執行得更好(儘可能多地用 Python 的核心函數)。還有一些本文沒有涉及到的有用的方法和數據結構,這些方法和數據結構都很值得花時間去理解:數據透視表:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.pivot.html?source=post_page時間序列/日期功能:https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html?source=post_page繪圖:https://pandas.pydata.org/pandas-docs/stable/user_guide/visualization.html?source=post_page希望你可以因為這篇簡短的文章,更好地理解 Pandas 背後的工作原理,以及 Pandas 庫的發展現狀。本文還展示了不同的用於優化數據框內存以及快速分析數據的工具。希望對現在的你來說,索引和查找的概念能更加清晰。最後,你還可以試著用方法鏈寫更長的鏈。這裡還有一些筆記:https://github.com/unit8co/medium-pandas-wan?source=post_page除了文中的所有代碼外,還包括簡單數據索引數據框(df)和多索引數據框(mi_df)性能的定時指標。熟能生巧,所以繼續修煉技能,並幫助我們建立一個更好的世界吧。原文連結:https://medium.com/unit8-machine-learning-publication/from-pandas-wan-to-pandas-master-4860cf0ce442

『運籌OR帷幄算法社區』知識星球,依託社區25w+專業受眾和25+細分領域碩博微信群,特邀騰訊,百度,阿里,華為等公司大咖與您一起聊算法,快來掃碼加入!

加入『運籌OR帷幄』知識星球的好處

● 全球Top名校教授|博士和名企研發高管一起交流算法相關學術|研發乾貨

● 中國你能說出名字的幾乎所有大廠|歐美數家大廠(資深)算法工程師入駐

● 依託『運籌OR帷幄』25w+專業受眾和25+細分領域碩博微信群的算法技術交流

● 以上所有公司|高校獨家內推招聘|實習機會、多家Offer選擇指導

● 以面試題|作業題|業界項目為學習資料學習算法乾貨,從小白變成大咖

● 不定期的線上、線下交流會和聚會,拓展人脈

相關焦點

  • Python 數據分析:Pandas 進階
    概述我們在上一篇文章初識 Pandas中已經對 Pandas 作了一些基本介紹,本文我們進一步來學習 Pandas 的一些使用。2. 缺失項在現實中我們獲取到的數據有時會存在缺失項問題,對於這樣的數據,我們通常需要做一些基本處理,下面我們通過示例來看一下。
  • 這是 Pandas 最詳細教程了
    有一個用於數據科學的包絕對是必需的,它就是 pandas。pandas 最有趣的地方在於裡面隱藏了很多包。它是一個核心包,裡面有很多其他包的功能。這點很棒,因為你只需要使用 pandas 就可以完成工作。
  • 史上最全Pandas 教程!
    有一個用於數據科學的包絕對是必需的,它就是 pandas。pandas 最有趣的地方在於裡面隱藏了很多包。它是一個核心包,裡面有很多其他包的功能。這點很棒,因為你只需要使用 pandas 就可以完成工作。pandas 相當於 python 中 excel:它使用表(也就是 dataframe),能在數據上做各種變換,但還有其他很多功能。如果你早已熟知 python 的使用,可以直接跳到第三段。
  • Expo pandas arrive in Shanghai
    Ten giant pandas selected for the world expo by the Chinese giant panda protection and research center were flown to Shanghai on Jan. 5 from the center's Bifengxia base in Ya'an City, southwest China's
  • 一日二技:Pandas 與 Docker 的使用技巧
    pandas 讀取無頭 CSV我們知道,CSV 一般是長這樣的:
  • 【pandas 基礎】
    構建的含有更高級數據結構和工具的數據分析包類似於 Numpy 的核心是 ndarray,pandas 也是圍繞著 Series 和 DataFrame 兩個核心數據結構展開的 。pandas 約定俗成的導入方法如下:lang:pythonfrom pandas import Series,DataFrameimport pandas as pd<br />SeriesSeries 可以看做一個定長的有序字典。
  • 別找了,這是 Pandas 最詳細教程了
    有一個用於數據科學的包絕對是必需的,它就是 pandas。pandas 最有趣的地方在於裡面隱藏了很多包。它是一個核心包,裡面有很多其他包的功能。這點很棒,因為你只需要使用 pandas 就可以完成工作。pandas 相當於 python 中 excel:它使用表(也就是 dataframe),能在數據上做各種變換,但還有其他很多功能。如果你早已熟知 python 的使用,可以直接跳到第三段。
  • 數據處理必看:如何讓你的 pandas 循環加快 71803 倍
    雷鋒網 AI 開發者按,如果你使用 python 和 pandas 進行數據分析,那麼不久你就會第一次使用循環了。然而,即使是對小型數據集,使用標準循環也很費時,你很快就會意識到大型數據幀可能需要很長的時間。當我第一次等了半個多小時來執行代碼時,我找到了接下來想與你共享的替代方案。標準循環數據幀是具有行和列的 pandas 對象。
  • Pandas 數據處理|Datetime 時間模塊在 Pandas 中的使用
    Datatime 作為 Python 中的時間模塊類型,處理時間有關數據是非常方便的, Pandas 作為數據分析程序包同樣也支持 DataTime 數據機制,例如1,函數 to_datetime() 將數據列表中的 Series 列轉化為 datetime 類型,#Convert the type to datetimeapple.Date =
  • 用 Pandas 讀寫網頁中的 HTML 表格數據
    我們可以使用HTML的<table>標籤來呈現表格數據。Pandas 數據分析庫提供了read_html()和to_html()之類的功能,因此我們可以將數據導入和導出到DataFrames。在本文中,我們將學習如何從HTML文件讀取表格數據並將其加載到Pandas DataFrame中。我們還將學習如何將數據從Pandas DataFrame寫入HTML文件。
  • Pandas 數據處理 | Datetime 時間模塊在 Pandas 中的使用
    Datatime 作為 Python 中的時間模塊類型,處理時間有關數據是非常方便的, Pandas 作為數據分析程序包同樣也支持 DataTime 數據機制,例如1,函數 to_datetime() 將數據列表中的 Series 列轉化為 datetime 類型,#Convert the type to datetimeapple.Date
  • Laid-back pandas ease into new home
    The two pandas - 4-year-old female Meng Meng and 7-year-old male Jiao Qing - arrived in the German capital last month.
  • 國外大神製作的超棒 Pandas 可視化教程
    、機器學習、或者用 Python 做數據科學的研究,你會經常接觸到 Pandas 庫。Pandas 是一個開源、能用於數據操作和分析的 Python 庫。1.加載數據加載數據最方便、最簡單的辦法是我們能一次性把表格(CSV 文件或者 EXCEL 文件)導入。然後我們能用多種方式對它們進行切片和裁剪。Pandas 可以說是我們加載數據的完美選擇。
  • Six pandas transferred from Wolong center
    A total of six pandas were taken away on Friday from a major panda base in Wolong of southwest China's Sichuan Province because of damaged shelters and food shortage after
  • Pandas face tough time this winter
    CHENGDU -- Giant pandas living in the wild in Sichuan will face a struggle to survive this winter because of the damage caused to their environment by the May 12 earthquake
  • It’s a first in the world! A giant panda recovers from...
    Maotao going for a leisurely walk before the illness「At present, occurrence of pylorus or duodenal obstruction in giant pandas
  • Red pandas enjoy their life at Panda World in Fuzhou
    One of a triplet red pandas (ailurus fulgens) is seen at the Panda World in Fuzhou, capital of southeast China's
  • 適合新手的 python pandas 學習筆記(2)
    同時我也通過探索找到了適合自己的學習入口:先了解 pandas 所特有的數據結構series與dateframe。昨天,我並沒有按照官方的10 minutes to pandas完整地練習一遍,但我已經知道這兩種數據容器有增刪改查、IO處理等各種方法,只是還沒用過。我決定,結合一個實際的需求試著把 pandas 先用起來。
  • 數據分析利器 pandas 系列教程(四):對比 sql 學 pandas
    pandas 教程的第四篇,本篇將對比 sql 語言,學習 pandas 中各種類 sql 操作,文章篇幅較長,可以先收藏後食用,但不可以收藏後積灰~為了方便,依然以下面這個 DataFrame 為例,其變量名為 df,設有一同樣結構的 SQL 表,表名為 tb:
  • Python 數據處理庫 pandas 入門教程
    關於如何獲取pandas請參閱官網上的說明:pandas Installation。這也是pandas庫取名的原因:pan(el)-da(ta)-s。但這種數據結構由於很少被使用到,因此已經被廢棄了。Series由於Series是一維結構的數據,我們可以直接通過數組來創建這種數據,像這樣:# data_structure.py import pandas as pdimport numpy as np series1 = pd.Series([1, 2, 3, 4])print("series1