Dmytro Samchuk
本文所整理的技巧與Pandas的常用技巧不同,你可能並不會經常的使用它,但是有時候當你遇到一些非常棘手的問題時,這些技巧可以幫你快速解決一些不常見的問題。
1、Categorical類型
默認情況下,具有有限數量選項的列都會被分配object 類型。 但是就記憶體來說並不是一個有效的選擇。 我們可以這些列建立索引,並僅使用對對象的引用而實際值。Pandas 提供了一種稱為 Categorical的Dtype來解決這個問題。
例如一個帶有圖片路徑的大型數據集組成。 每行有三列:anchor, positive, and negative.。
如果類別列使用 Categorical 可以顯著減少記憶體使用量。
# raw data
+ - - - - - + - - - - - - - - - - - - +
| class | filename |
+ - - - - - + - - - - - - - - - - - - +
| Bathroom | Bathroom\bath_1.jpg |
| Bathroom | Bathroom\bath_100.jpg |
| Bathroom | Bathroom\bath_1003.jpg |
| Bathroom | Bathroom\bath_1004.jpg |
| Bathroom | Bathroom\bath_1005.jpg |
+ - - - - - + - - - - - - - - - - - - +
# target
+ - - - - - - - - - - - - + - - - - - - - - - - - - + - - - - - - - - - - - - - - +
| anchor | positive | negative |
+ - - - - - - - - - - - - + - - - - - - - - - - - - + - - - - - - - - - - - - - - +
| Bathroom\bath_1.jpg | Bathroom\bath_100.jpg | Dinning\din_540.jpg |
| Bathroom\bath_100.jpg | Bathroom\bath_1003.jpg | Dinning\din_1593.jpg |
| Bathroom\bath_1003.jpg | Bathroom\bath_1004.jpg | Bedroom\bed_329.jpg |
| Bathroom\bath_1004.jpg | Bathroom\bath_1005.jpg | Livingroom\living_1030.jpg |
| Bathroom\bath_1005.jpg | Bathroom\bath_1007.jpg | Bedroom\bed_1240.jpg |
+ - - - - - - - - - - - - + - - - - - - - - - - - - + - - - - - - - - - - - - - - +
filename列的值會經常被複製重複。因此,所以通過使用Categorical可以極大的減少記憶體使用量。
讓我們讀取目標數據集,看看記憶體的差異:
triplets.info(memory_usage="deep")
# Column Non-Null Count Dtype
# - - - - - - - - - - - - - - -
# 0 anchor 525000 non-null category
# 1 positive 525000 non-null category
# 2 negative 525000 non-null category
# dtypes: category(3)
# memory usage: 4.6 MB
# without categories
triplets_raw.info(memory_usage="deep")
# Column Non-Null Count Dtype
# - - - - - - - - - - - - - - -
# 0 anchor 525000 non-null object
# 1 positive 525000 non-null object
# 2 negative 525000 non-null object
# dtypes: object(3)
# memory usage: 118.1 MB
差異非常大,並且隨著重複次數的增加,差異呈非線性增長。
2、行列轉換
sql中經常會遇到行列轉換的問題,Pandas有時候也需要,讓我們看看來自Kaggle比賽的數據集。census_start .csv檔:
可以看到,這些按年來保存的,如果有一個列year和pct_bb,並且每一行有相應的值,則會好得多,對吧。
cols = sorted([col for col in original_df.columns \
if col.startswith("pct_bb")])
df = original_df[(["cfips"] + cols)]
df = df.melt(id_vars="cfips",
value_vars=cols,
var_name="year",
value_name="feature").sort_values(by=["cfips", "year"])
看看結果,這樣是不是就好很多了:
3、apply()很慢
我們上次已經介紹過,最好不要使用這個方法,因為它遍曆每行並調用指定的方法。但是要是我們沒有別的選擇,那還有沒有辦法提高速度呢?
可以使用swifter或pandarallew這樣的包,使過程並行化。
Swifter
import pandas as pd
import swifter
def target_function(row):
return row * 10
def traditional_way(data):
data['out'] = data['in'].apply(target_function)
def swifter_way(data):
data['out'] = data['in'].swifter.apply(target_function)
Pandarallel
import pandas as pd
from pandarallel import pandarallel
def target_function(row):
return row * 10
def traditional_way(data):
data['out'] = data['in'].apply(target_function)
def pandarallel_way(data):
pandarallel.initialize()
data['out'] = data['in'].parallel_apply(target_function)
通過多線程,可以提高計算的速度,當然當然,如果有集群,那麼最好使用dask或pyspark
4、空值,int, Int64
標準整型數據類型不支持空值,所以會自動轉換為浮點數。所以如果數據要求在整數字段中使用空值,請考慮使用Int64數據類型,因為它會使用pandas.NA來表示空值。
5、Csv, 壓縮還是parquet?
盡可能選擇parquet。parquet會保留數據類型,在讀取數據時就不需要指定dtypes。parquet檔默認已經使用了snappy進行壓縮,所以佔用的磁片空間小。下面可以看看幾個的對比
| file | size |
+ - - - - - - - - - - - - + - - - - -+
| triplets_525k.csv | 38.4 MB |
| triplets_525k.csv.gzip | 4.3 MB |
| triplets_525k.csv.zip | 4.5 MB |
| triplets_525k.parquet | 1.9 MB |
+ - - - - - - - - - - - - + - - - - -+
讀取parquet需要額外的包,比如pyarrow或fastparquet。chatgpt說pyarrow比fastparquet要快,但是我在小數據集上測試時fastparquet比pyarrow要快,但是這裏建議使用pyarrow,因為pandas 2.0也是默認的使用這個。
6、value_counts ()
計算相對頻率,包括獲得絕對值、計數和除以總數是很複雜的,但是使用value_counts,可以更容易地完成這項任務,並且該方法提供了包含或排除空值的選項。
df = pd.DataFrame({"a": [1, 2, None], "b": [4., 5.1, 14.02]})
df["a"] = df["a"].astype("Int64")
print(df.info())
print(df["a"].value_counts(normalize=True, dropna=False),
df["a"].value_counts(normalize=True, dropna=True), sep="\n\n")
這樣是不是就簡單很多了
7、Modin
注意:Modin現在還在測試階段。
pandas是單線程的,但Modin可以通過縮放pandas來加快工作流程,它在較大的數據集上工作得特別好,因為在這些數據集上,pandas會變得非常緩慢或記憶體佔用過大導致OOM。
!pip install modin[all]
import modin.pandas as pd
df = pd.read_csv("my_dataset.csv")
以下是modin官網的架構圖,有興趣的研究把:
8、extract()
如果經常遇到複雜的半結構化的數據,並且需要從中分離出單獨的列,那麼可以使用這個方法:
import pandas as pd
regex = (r'(?P<title>[A-Za-z\'\s]+),'
r'(?P<author>[A-Za-z\s\']+),'
r'(?P<isbn>[\d-]+),'
r'(?P<year>\d{4}),'
r'(?P<publisher>.+)')
addr = pd.Series([
"The Lost City of Amara,Olivia Garcia,978–1–234567–89–0,2023,HarperCollins",
"The Alchemist's Daughter,Maxwell Greene,978–0–987654–32–1,2022,Penguin Random House",
"The Last Voyage of the HMS Endeavour,Jessica Kim,978–5–432109–87–6,2021,Simon & Schuster",
"The Ghosts of Summer House,Isabella Lee,978–3–456789–12–3,2000,Macmillan Publishers",
"The Secret of the Blackthorn Manor,Emma Chen,978–9–876543–21–0,2023,Random House Children's Books"
])
addr.str.extract(regex)
9、讀寫剪貼板
這個技巧有人一次也用不到,但是有人可能就是需要,比如:在分析中包含PDF檔中的表格時。通常的方法是複製數據,粘貼到Excel中,導出到csv檔中,然後導入Pandas。但是,這裏有一個更簡單的解決方案:pd.read_clipboard()。我們所需要做的就是複製所需的數據並執行一個方法。
有讀就可以寫,所以還可以使用to_clipboard()方法導出到剪貼板。
但是要記住,這裏的剪貼板是你運行python/jupyter主機的剪切板,並不可能跨主機粘貼,一定不要搞混了。
10、數組列分成多列
假設我們有這樣一個數據集,這是一個相當典型的情況:
import pandas as pd
df = pd.DataFrame({"a": [1, 2, 3],
"b": [4, 5, 6],
"category": [["foo", "bar"], ["foo"], ["qux"]]})
# let's increase the number of rows in a dataframe
df = pd.concat([df]*10000, ignore_index=True)
我們想將category分成多列顯示,例如下面的
先看看最慢的apply:
def dummies_series_apply(df):
return df.join(df['category'].apply(pd.Series) \
.stack() \
.str.get_dummies() \
.groupby(level=0) \
.sum()) \
.drop("category", axis=1)
%timeit dummies_series_apply(df.copy())
#5.96 s ± 66.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
sklearn的MultiLabelBinarizer
from sklearn.preprocessing import MultiLabelBinarizer
def sklearn_mlb(df):
mlb = MultiLabelBinarizer()
return df.join(pd.DataFrame(mlb.fit_transform(df['category']), columns=mlb.classes_)) \
.drop("category", axis=1)
%timeit sklearn_mlb(df.copy())
#35.1 ms ± 1.31 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
是不是快了很多,我們還可以使用一般的向量化操作對其求和:
from sklearn.preprocessing import MultiLabelBinarizer
def sklearn_mlb(df):
mlb = MultiLabelBinarizer()
return df.join(pd.DataFrame(mlb.fit_transform(df['category']), columns=mlb.classes_)) \
.drop("category", axis=1)
%timeit sklearn_mlb(df.copy())
#35.1 ms ± 1.31 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
總結
我希望每個人都能從這些技巧中學到一些新的東西。重要的是要記住盡可能使用向量化操作而不是apply()。此外,除了csv之外,還有其他有趣的存儲數據集的方法。不要忘記使用分類數據類型,它可以節省大量記憶體。感謝閱讀!
文章來源:Deephub Imba
原文連結:https://mp.weixin.qq.com/s/WOBCxuW4lBthHi_EJmLA2Q
※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
我是「數據分析那些事」。常年分享數據分析乾貨,不定期分享好用的職場技能工具。各位也可以關注我的Facebook,按讚我的臉書並私訊「10」,送你十週入門數據分析電子書唷!期待你與我互動起來~
文章推薦
◆跟資料打交道的人都得會的這8種資料模型,滿足工作中95%的需求
回顧十週入門數據分析系列文:
關注數據君的臉書:
我是「數據分析那些事」。常年分享數據分析乾貨,不定期分享好用的職場技能工具。按贊我的臉書,會有豐富資料包贈送唷!