文章目錄
- 前言
- 一、查看數據基本信息
- 二、拆分pub列
- 三、日期列處理
- 四、價格列處理
- 五、出版社列處理
- 六、評價人數列處理
- 七、缺失值處理
- 八、重復數據處理
- 九、異常值處理
- 十、完整代碼
- 十一、清洗與處理后的數據集展示
前言
豆瓣作為國內知名的文化社區,擁有龐大且豐富的圖書數據資源。這些數據涵蓋了圖書的分類、標簽、詳細信息以及用戶的評價等多個維度,為我們深入了解圖書世界提供了寶貴的素材。然而,原始的豆瓣圖書數據往往存在格式不規范、信息缺失、數據重復以及異常值等問題,這些問題會嚴重影響數據的質量和后續的分析與應用。
為了充分挖掘豆瓣圖書數據的價值,我們需要對其進行一系列的清洗和處理工作。通過對數據的全面檢查和針對性處理,我們可以解決數據中存在的各種問題,使數據更加完整、準確和一致。本項目圍繞豆瓣圖書數據集展開,詳細闡述了從數據的初步查看、各列數據的處理(包括拆分、格式轉換、異常值處理等),到缺失值和重復值的處理,以及最終將處理后的數據保存到數據庫的整個過程。
一、查看數據基本信息
def data_info(data):"""打印數據的前幾行、列名和基本信息。:param data: 待分析的DataFrame數據"""print("數據前幾行:")print(data.head().to_csv(sep='\t', na_rep='nan')) # 使用制表符分隔,方便查看,缺失值用nan表示print("數據列名:")print(data.columns)print("數據基本信息:")data.info()
數據前幾行如下所示:
數據前幾行:category_name url img_url name pub rating rating_count plot buy_info
0 J.K.羅琳 https://book.douban.com/subject/35671734/ https://img9.doubanio.com/view/subject/s/public/s34048686.jpg 哈利·波特與死亡圣器 J.K. 羅琳 / 中國圖書進出口總公司 / 2016-8-1 / 30.00元 9.6 (310人評價) 「“把哈利·波特交出來,”伏地魔的聲音說,“你們誰也不會受傷。把哈利·波特交出來,我會讓學校安然無恙。把哈利·波特交出來,你們會得到獎賞。”」當哈利·波特攀... nan
1 J.K.羅琳 https://book.douban.com/subject/27625554/ https://img3.doubanio.com/view/subject/s/public/s33973792.jpg 神奇動物在哪里(插圖版) [英]紐特·斯卡曼德 / 一目、馬愛農 / 人民文學出版社 / 2018-3 / 45.00 8.5 (483人評價) 《神奇動物在哪里》是“哈利·波特”系列*著名的官方衍生書。它是哈利·波特在霍格沃茨魔法學校的指定教材,由J.K.羅琳(化名紐特·斯卡曼德)撰寫。 這一全新的... 紙質版 25.00元
2 J.K.羅琳 https://book.douban.com/subject/27594566/ https://img1.doubanio.com/view/subject/s/public/s32311010.jpg 詩翁彼豆故事集(插圖版) [英]J.K.羅琳 / 馬愛農 / 人民文學出版社 / 2018-3 8.9 (721人評價) 《詩翁彼豆故事集》是“哈利·波特”系列的官方衍生書。它是魔法世界家喻戶曉的床邊故事集,也是鄧布利多留給赫敏·格蘭杰的禮物。書中故事均由J.K.羅琳撰寫。 這... 紙質版 25.00元
3 J.K.羅琳 https://book.douban.com/subject/35909174/ https://img1.doubanio.com/view/subject/s/public/s34330260.jpg 哈利·波特與被詛咒的孩子 [英] J.K.羅琳、[英] 約翰·蒂法尼、[英] 杰克·索恩 / 馬愛農 / 人民文學出版社 / 2022-6 / 69.00元 7.0 (84人評價) 第八個故事。 十九年后。 十九年前,哈利·波特、羅恩·韋斯萊和赫敏·格蘭杰攜手拯救了魔法世界;如今,他們再次踏上了一場非同尋常的冒險旅程。這次與他們同行的,... 紙質版 40.40元
4 J.K.羅琳 https://book.douban.com/subject/26984868/ https://img9.doubanio.com/view/subject/s/public/s29443244.jpg 神奇動物在哪里 [英] J. K. 羅琳 / 馬愛農、馬珈 / 人民文學出版社 / 2017-4 / 58 8.3 (1346人評價) 讀懂“哈利·波特”,從前傳開始 《神奇動物在哪里》 J.K.羅琳首次挑戰電影編劇 紐特·斯卡曼德邀請你一同探索魔法世界新紀元! 冒險家、神奇動物學家紐特·... 紙質版 32.00元
數據列名如下所示:
數據列名:
Index(['category_name', 'url', 'img_url', 'name', 'pub', 'rating','rating_count', 'plot', 'buy_info'],dtype='object')
數據基本信息如下所示:
數據基本信息:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 129839 entries, 0 to 129838
Data columns (total 9 columns):# Column Non-Null Count Dtype
--- ------ -------------- ----- 0 category_name 129839 non-null object 1 url 129839 non-null object 2 img_url 129839 non-null object 3 name 129827 non-null object 4 pub 129436 non-null object 5 rating 111810 non-null float646 rating_count 129839 non-null object 7 plot 119919 non-null object 8 buy_info 93330 non-null object
dtypes: float64(1), object(8)
memory usage: 8.9+ MB
二、拆分pub列
# 拆分pub列的函數
def split_pub(pub):"""拆分pub列,根據不同的分隔符和格式提取作者、譯者、出版社、出版日期和價格信息。:param pub: pub列的單個值:return: 包含提取信息的Series"""if pd.isna(pub):return pd.Series([None, None, None, None, None],index=['author', 'translator', 'publisher', 'publish_date', 'price'])author = translator = publisher = publish_date = price = Noneif '/' in pub:parts = pub.split('/')if len(parts) == 5:author, translator, publisher, publish_date, price = partselif len(parts) == 4:if '.' in pub:author, publisher, publish_date, price = partselse:author, translator, publisher, publish_date = partselif len(parts) == 3:if '.' in pub:author, publish_date, price = partselse:author, publisher, publish_date = partselif len(parts) == 2:if '-' in pub and '.' in pub:publish_date, price = partselse:publisher, publish_date = partselse:if '-' in pub:publish_date = pubelif '出版社' in pub:publisher = pubelse:price = pubreturn pd.Series([author, translator, publisher, publish_date, price],index=['author', 'translator', 'publisher', 'publish_date', 'price'])
三、日期列處理
此函數用于處理日期列,將日期字符串轉換為有效的圖書發布年份。會對輸入的日期字符串進行格式檢查,提取年份部分,并驗證年份是否處于合理范圍(1900 - 2025),若不符合要求則返回 None。
def process_date(date_str):"""處理日期列,提取年份并進行有效性檢查。:param date_str: 日期字符串:return: 有效的年份或None"""if pd.notna(date_str):date_str = date_str.strip()try:year_str = date_str[:4]if len(year_str.strip()) != 4:return Noneyear = int(year_str)if 1900 <= year <= 2025: # 檢查年份范圍return yearexcept ValueError:passreturn None
四、價格列處理
此函數的作用是處理包含價格信息的字符串,從其中提取有效的價格,并對提取出的價格進行有效性檢查。 包含價格信息的字符串,通常是類似 “紙質版 39.8 元” 這樣的格式。若字符串中包含有效的價格信息且價格在合理范圍內,返回該價格(浮點數類型);若價格超出合理范圍,返回設定的上限價格 2000;若字符串為空值、格式不符合要求或在處理過程中出現錯誤,返回 None。
def process_buy_info(buy_info_str):"""處理價格信息,提取價格并進行有效性檢查。:param buy_info_str: 包含價格信息的字符串:return: 有效的價格或None"""if pd.notna(buy_info_str):buy_info_str = buy_info_str.strip()try:price_str = buy_info_str.split(' ')[1].split('元')[0]price = float(price_str)if price <= 2000: # 檢查價格范圍return pricereturn 2000except (IndexError, ValueError):passreturn None
五、出版社列處理
該函數用于處理數據集中的出版社列信息,對傳入的出版社信息字符串進行檢查,去除其中包含無效信息的部分,篩選出有效的出版社信息。出版社信息的字符串可能是正確的出版社名稱,也可能包含如日期等無效信息。若字符串是有效的出版社信息,則返回該字符串;若字符串為缺失值或者包含無效信息(如“年”或“-”),則返回 None。
def process_publish(publish_str):"""處理出版社列,去除無效信息。:param publish_str: 出版社信息字符串:return: 有效的出版社信息或None"""if pd.notna(publish_str):publish_str = publish_str.strip()if '年' not in publish_str and '-' not in publish_str:return publish_strreturn None
六、評價人數列處理
該函數的主要作用是處理包含評價人數信息的字符串,將不同格式的評價人數描述轉換為對應的整數形式,以便后續的數據處理和分析。如果輸入的字符串為空值或者無法正確轉換為整數,則返回 None。
def process_rating_count(rating_count_str):"""處理評價人數列,將不同格式的評價人數轉換為整數。:param rating_count_str: 評價人數信息字符串:return: 評價人數的整數表示或None"""if pd.notna(rating_count_str):rating_count_str = rating_count_str.strip()if rating_count_str == '(少于10人評價)':return 10elif rating_count_str == '(目前無人評價)':return 0try:rating_count = int(rating_count_str.split('(')[1].split('人')[0])return rating_countexcept (IndexError, ValueError):passreturn None
七、缺失值處理
該函數的主要功能是處理輸入的DataFrame數據中的缺失值。針對數據中的不同列,采用不同的策略來處理缺失值,以提高數據的質量和可用性。
缺失值統計情況如下:
缺失值情況:
category_name 0
url 0
img_url 0
name 12
rating 18029
rating_count 0
plot 9920
author 3842
translator 63390
publisher 15669
publish_year 15801
price 36509
dtype: int64
對于圖書名稱列的缺失值,使用dropna方法,指定subset為[‘name’],即刪除’name’列中值為缺失值的行。
對于作者列的缺失值,首先使用replace方法,利用正則表達式r’^\s*$'匹配空字符串(包括只包含空白字符的字符串),將其替換為pd.NA(表示缺失值)。然后再使用dropna方法,刪除’author’列中值為缺失值的行。
對于評分列的缺失值,使用groupby方法按’category_name’分組,然后對每個組內的’rating’列進行操作。通過transform方法,對每個組內的缺失值使用該組的評分均值(保留一位小數)進行填充。
對于情節簡介列的缺失值,使用fillna方法,將’plot’列中的缺失值填充為字符串’未知’。
對于譯者列的缺失值,先使用replace方法,將空字符串(包括只包含空白字符的字符串)替換為pd.NA。然后使用fillna方法,將缺失值填充為字符串’無譯者’。最后使用apply方法,對每一個譯者字符串應用lambda函數,去除字符串前后的空格。
對于出版社列的缺失值,類似作者列的處理方式,先將空字符串替換為pd.NA,然后刪除該列中值為缺失值的行。
對于出版年份列的缺失值,按’category_name’分組,對每個組內的’publish_year’列的缺失值使用該組的中位數進行填充。
對于價格列的缺失值,按’category_name’分組,對每個組內的’price’列的缺失值使用該組的均值(保留兩位小數)進行填充。
缺失值統計及處理代碼如下:
def process_null_data(data):"""處理數據中的缺失值,根據不同列采用不同的處理方法。:param data: 待處理的DataFrame數據"""# data_info(data) # 查看處理前的數據信息print("缺失值情況:")print(data.isnull().sum())# 處理各列的缺失值data.dropna(subset=['name'], inplace=True) # 刪除圖書名稱缺失的行data['author'] = data['author'].replace(r'^\s*$', pd.NA, regex=True)data.dropna(subset=['author'], inplace=True) # 刪除作者缺失的行data['rating'] = data.groupby('category_name')['rating'].transform(lambda x: x.fillna(x.mean().round(1))) # 按類別填充評分缺失值data['plot'].fillna('未知', inplace=True) # 填充情節簡介缺失值data['translator'] = data['translator'].replace(r'^\s*$', pd.NA, regex=True)data['translator'].fillna('無譯者', inplace=True)data['translator'] = data['translator'].apply(lambda x: x.strip()) # 去除譯者前后空格data['publisher'] = data['publisher'].replace(r'^\s*$', pd.NA, regex=True)data.dropna(subset=['publisher'], inplace=True) # 刪除出版社缺失的行data['publish_year'] = data.groupby('category_name')['publish_year'].transform(lambda x: x.fillna(x.median())) # 按類別填充出版年份缺失值data['price'] = data.groupby('category_name')['price'].transform(lambda x: x.fillna(x.mean().round(2))) # 按類別填充價格缺失值print("處理后缺失值情況:")print(data.isnull().sum())
處理后缺失值情況如下,可以看到已經沒有缺失值。
處理后缺失值情況:
category_name 0
url 0
img_url 0
name 0
rating 0
rating_count 0
plot 0
author 0
translator 0
publisher 0
publish_year 0
price 0
dtype: int64
八、重復數據處理
def process_repeat_data(data):"""處理數據中的重復值,刪除重復行。:param data: 待處理的DataFrame數據:return: 處理后的DataFrame數據"""print("重復值情況:")count = data.duplicated().sum()print(count)if count > 0:data.drop_duplicates(inplace=True)return data
九、異常值處理
def process_outliers(data):"""處理數據中的異常值,對評分進行范圍限制。:param data: 待處理的DataFrame數據"""print("異常值情況:")print(data.describe())data['rating'] = data['rating'].clip(0, 10) # 限制評分范圍在0-10之間
十、完整代碼
豆瓣圖書數據清洗與處理的完整代碼如下:
import pandas as pd
from sqlalchemy import create_engine# 查看數據基本信息的函數
def data_info(data):"""打印數據的前幾行、列名和基本信息。:param data: 待分析的DataFrame數據"""print("數據前幾行:")print(data.head().to_csv(sep='\t', na_rep='nan')) # 使用制表符分隔,方便查看,缺失值用nan表示print("數據列名:")print(data.columns)print("數據基本信息:")data.info()# 拆分pub列的函數
def split_pub(pub):"""拆分pub列,根據不同的分隔符和格式提取作者、譯者、出版社、出版日期和價格信息。:param pub: pub列的單個值:return: 包含提取信息的Series"""if pd.isna(pub):return pd.Series([None, None, None, None, None],index=['author', 'translator', 'publisher', 'publish_date', 'price'])author = translator = publisher = publish_date = price = Noneif '/' in pub:parts = pub.split('/')if len(parts) == 5:author, translator, publisher, publish_date, price = partselif len(parts) == 4:if '.' in pub:author, publisher, publish_date, price = partselse:author, translator, publisher, publish_date = partselif len(parts) == 3:if '.' in pub:author, publish_date, price = partselse:author, publisher, publish_date = partselif len(parts) == 2:if '-' in pub and '.' in pub:publish_date, price = partselse:publisher, publish_date = partselse:if '-' in pub:publish_date = pubelif '出版社' in pub:publisher = pubelse:price = pubreturn pd.Series([author, translator, publisher, publish_date, price],index=['author', 'translator', 'publisher', 'publish_date', 'price'])# 日期列處理函數
def process_date(date_str):"""處理日期列,提取年份并進行有效性檢查。:param date_str: 日期字符串:return: 有效的年份或None"""if pd.notna(date_str):date_str = date_str.strip()try:year_str = date_str[:4]if len(year_str.strip()) != 4:return Noneyear = int(year_str)if 1900 <= year <= 2025: # 檢查年份范圍return yearexcept ValueError:passreturn None# 價格列處理函數
def process_buy_info(buy_info_str):"""處理價格信息,提取價格并進行有效性檢查。:param buy_info_str: 包含價格信息的字符串:return: 有效的價格或None"""if pd.notna(buy_info_str):buy_info_str = buy_info_str.strip()try:price_str = buy_info_str.split(' ')[1].split('元')[0]price = float(price_str)if price <= 2000: # 檢查價格范圍return pricereturn 2000except (IndexError, ValueError):passreturn None# 出版社列處理函數
def process_publish(publish_str):"""處理出版社列,去除無效信息。:param publish_str: 出版社信息字符串:return: 有效的出版社信息或None"""if pd.notna(publish_str):publish_str = publish_str.strip()if '年' not in publish_str and '-' not in publish_str:return publish_strreturn None# 評價人數列處理函數
def process_rating_count(rating_count_str):"""處理評價人數列,將不同格式的評價人數轉換為整數。:param rating_count_str: 評價人數信息字符串:return: 評價人數的整數表示或None"""if pd.notna(rating_count_str):rating_count_str = rating_count_str.strip()if rating_count_str == '(少于10人評價)':return 10elif rating_count_str == '(目前無人評價)':return 0try:rating_count = int(rating_count_str.split('(')[1].split('人')[0])return rating_countexcept (IndexError, ValueError):passreturn None# 空值處理函數
def process_null_data(data):"""處理數據中的缺失值,根據不同列采用不同的處理方法。:param data: 待處理的DataFrame數據"""# data_info(data) # 查看處理前的數據信息print("缺失值情況:")print(data.isnull().sum())# 處理各列的缺失值data.dropna(subset=['name'], inplace=True) # 刪除圖書名稱缺失的行data['author'] = data['author'].replace(r'^\s*$', pd.NA, regex=True)data.dropna(subset=['author'], inplace=True) # 刪除作者缺失的行data['rating'] = data.groupby('category_name')['rating'].transform(lambda x: x.fillna(x.mean().round(1))) # 按類別填充評分缺失值data['plot'].fillna('未知', inplace=True) # 填充情節簡介缺失值data['translator'] = data['translator'].replace(r'^\s*$', pd.NA, regex=True)data['translator'].fillna('無譯者', inplace=True)data['translator'] = data['translator'].apply(lambda x: x.strip()) # 去除譯者前后空格data['publisher'] = data['publisher'].replace(r'^\s*$', pd.NA, regex=True)data.dropna(subset=['publisher'], inplace=True) # 刪除出版社缺失的行data['publish_year'] = data.groupby('category_name')['publish_year'].transform(lambda x: x.fillna(x.median())) # 按類別填充出版年份缺失值data['price'] = data.groupby('category_name')['price'].transform(lambda x: x.fillna(x.mean().round(2))) # 按類別填充價格缺失值print("處理后缺失值情況:")print(data.isnull().sum())# 重復數據處理函數
def process_repeat_data(data):"""處理數據中的重復值,刪除重復行。:param data: 待處理的DataFrame數據:return: 處理后的DataFrame數據"""print("重復值情況:")count = data.duplicated().sum()print(count)if count > 0:data.drop_duplicates(inplace=True)return data# 異常值處理函數
def process_outliers(data):"""處理數據中的異常值,對評分進行范圍限制。:param data: 待處理的DataFrame數據"""print("異常值情況:")print(data.describe())data['rating'] = data['rating'].clip(0, 10) # 限制評分范圍在0-10之間# 保存數據到MySQL數據庫的函數
def save_to_mysql(data, table_name):"""將處理后的數據保存到MySQL數據庫。:param data: 待保存的DataFrame數據:param table_name: 數據庫表名"""engine = create_engine(f'mysql+mysqlconnector://root:zxcvbq@127.0.0.1:3306/douban')try:data.to_sql(table_name, con=engine, index=False, if_exists='replace')print(f'清洗后的數據已保存到 {table_name} 表')except Exception as e:print(f"保存數據到數據庫時出錯: {e}")if __name__ == '__main__':# 讀取數據data = pd.read_csv('./原始數據層/豆瓣圖書數據集.csv')# 查看數據基本信息data_info(data)# 拆分pub列data[['author', 'translator', 'publisher', 'publish_date', 'price']] = data['pub'].apply(split_pub)data.drop(['pub'], axis=1, inplace=True)data.drop(['price'], axis=1, inplace=True)data.to_csv('./中間處理層/拆分列后的豆瓣圖書數據集.csv', index=False)data = pd.read_csv('./中間處理層/拆分列后的豆瓣圖書數據集.csv')# 日期列處理data['publish_date'] = data['publish_date'].apply(process_date)data.rename(columns={'publish_date': 'publish_year'}, inplace=True)# 價格列處理data['price'] = data['buy_info'].apply(process_buy_info)data.drop(['buy_info'], axis=1, inplace=True)# 出版社列處理data['publisher'] = data['publisher'].apply(process_publish)# 評價人數列處理data['rating_count'] = data['rating_count'].apply(process_rating_count)# 空值處理process_null_data(data)# 重復數據處理data = process_repeat_data(data)# 異常值處理process_outliers(data)# 保存處理后的數據data.to_csv('./中間處理層/清洗后的豆瓣圖書數據集.csv', index=False, encoding='utf-8-sig')# 保存分析后的數據到MySQL數據庫save_to_mysql(data, '清洗后的豆瓣圖書數據集')
十一、清洗與處理后的數據集展示
清洗與處理后的數據集部分數據如下圖所示: