此篇文是由 Joyce 所撰寫
EDA翻譯成中文是探索式資料分析,是在對資料進行前處理,
主要的功能分成兩種,第一種是描述性統計,第二種是透過圖表觀察。不管是哪一種,它背後的數學技巧都是很基礎的,操作起來也很簡單,但是千萬不要因此小看EDA的威力,它可以讓我們在分析數據前,先對資料的分布、型態,有無離群值,有個大概的了解。
描述性統計的資料分為兩種,一種是集中量數,用來描述數據的集中程度,另一種是離散量數,則是用來描述離散程度,可以透過這些統計值,對資料的分佈有初步的認識。
常見的集中量數為眾數、中位數、平均數,用來觀察資料是否集中,而除此之外,我們也常常用這三個數討論為常態分佈還是偏態分布。可以透過三個述職的大小加上圖形分析決定為哪一種分布。
常見的離散量數有最大(小)值、四分位差、標準差,可以透過這些數值,查看資料的分散性。
除了透過數值的觀察,最直接的方式是透過視覺化的圖形,像是趨勢圖、散佈圖,讓數據的走向、型態一目了然。
將數據用趨勢圖的方式呈現,為了方便分析,常常我們會加入迴歸線,並附上迴歸函數,調整參數讓迴歸線貼近趨勢圖。觀察下面這張圖,我們可以推測當x軸越大,對應的y值也會越大,呈正相關。
觀察累積圖的分布可以一眼看出集中區域,像是此圖size2就特別的多,因為他的斜率最大,這樣一來,我們就知道size2是我們需要特別注意觀察的對象。
將數據用散佈圖的方式呈現,可以一眼看出是否有數據是異常值、極端值,數據是否能有一區一區的分類。
透過直方圖的觀察,可以看出是否呈現常態分佈,也可以一眼看出眾數、最大(小)值落在哪個區間。
繪製盒方圖可以看出數據的離散程度,且也可以看出最大(小)值,及四分位差。
透過選擇適當的EDA分析,會對數據有一定的了解。那麼接下來就要就要更深入地去思考數據背後的意義。舉些小例子,
如果數據沒有錯誤,那麼每一筆數據都有其背後值得分析的意義,像是特徵值的選取,異常值分析,分布情形,分析完後,要怎麼去解釋,就要看分析者的功力了。
我們用IMMC 2019 中華賽 秋季賽 B 題的題目進行舉例,先將csv檔下載下來。
import pandas as pd # 引入pandas
data = pd.read_csv("weboggle_immc_selected.csv") # 讀取資料
原始數據形式
len(data["num_id"].unique())
output
10000
data["commit_type"].unique() # 查看不重複的元素
output
array([ nan, 1., 2., 1.5, 1.3333334, 1.6666666])
data["commit_limit"].unique()
output
array([1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00,
5.000000e+00, 6.000000e+00, 7.000000e+00, 8.000000e+00,
9.000000e+00, 1.000000e+01, 1.100000e+01, 1.200000e+01,
1.300000e+01, 1.400000e+01, 1.500000e+01, 1.600000e+01,
1.700000e+01, 1.800000e+01, 1.900000e+01, 2.000000e+01,
2.200000e+01, 2.300000e+01, 2.400000e+01, 2.500000e+01,
2.700000e+01, 2.900000e+01, 3.000000e+01, 3.100000e+01,
3.200000e+01, 3.300000e+01, 3.500000e+01, 4.000000e+01,
4.100000e+01, 4.300000e+01, 4.400000e+01, 4.700000e+01,
5.000000e+01, 5.100000e+01, 5.400000e+01, 5.500000e+01,
6.000000e+01, 6.100000e+01, 6.500000e+01, 7.000000e+01,
7.100000e+01, 7.200000e+01, 7.400000e+01, 7.500000e+01,
7.700000e+01, 8.000000e+01, 8.100000e+01, 8.200000e+01,
8.800000e+01, 9.000000e+01, 9.100000e+01, 9.200000e+01,
9.300000e+01, 9.500000e+01, 9.600000e+01, 9.900000e+01,
1.000000e+02, 1.110000e+02, 1.400000e+02, 1.990000e+02,
2.000000e+02, 2.220000e+02, 2.500000e+02, 3.000000e+02,
4.000000e+02, 5.000000e+02, 5.400000e+02, 5.550000e+02,
5.670000e+02, 6.000000e+02, 6.570000e+02, 6.660000e+02,
6.750000e+02, 6.780000e+02, 7.650000e+02, 7.770000e+02,
7.890000e+02, 8.730000e+02, 8.760000e+02, 8.880000e+02,
9.000000e+02, 9.830000e+02, 9.870000e+02, 9.990000e+02,
1.000000e+03, 1.200000e+03, 1.234000e+03, 4.234000e+03,
4.523000e+03, 5.000000e+03, 5.643000e+03, 7.895000e+03,
7.898000e+03, 8.888000e+03, 9.876000e+03, 9.999000e+03,
1.000000e+04, 1.234500e+04, 2.000000e+04, 5.000000e+04,
7.777700e+04, 8.765400e+04, 9.876500e+04, 9.999900e+04,
1.000000e+05, 1.111110e+05, 1.234540e+05, 1.234560e+05,
7.654320e+05, 7.777770e+05, 8.765430e+05, 8.888880e+05,
9.876540e+05, 9.999990e+05, 1.000000e+06, 4.444444e+06,
5.000000e+06, 7.777777e+06, nan])
# count the number of sessions for each ID
data_reduce = data.drop_duplicates(subset=["session"])
result = data_reduce.groupby("num_id").count()["session"]
# output count_session to count.csv
result.to_csv("count.csv", index=False)
繪製成直方圖,由下方的直方圖可知因為局數的範圍太大了,不太好做分析。
import matplotlib.pyplot as plt # 引入matplotlib的函數
result_session = pd.read_csv("count.csv")
plt.title("Total session")
plt.xlabel("Si : sessions")
plt.ylabel("numbers of people")
plt.hist(result_session["session"])# 繪製直方圖
plt.show() # 顯現圖形
因此我們把局數縮小到只考慮20局以內。考慮20局以內的玩家,可以由下圖看出玩家的分布大概呈指數下降。
list_20 = []
for _ in range(1,21):
list_20.append(0)
result_session = pd.read_csv("count.csv")
for i in result_session["session"]:
if(i <= 20):
list_20[i-1] += 1
count = list(range(1,21))
plt.title("Distribution (1 ≦ Si ≦ 20)")
plt.xlabel("Si : sessions")
plt.ylabel("numbers of people")
plt.plot(count, list_20)# 繪製直方圖
plt.show() # 顯現圖形
# split each Id
num_id = data["num_id"].unique()
# creat an empty dictionary
time_data = {"num_id": [], "total_time": []}
for num in num_id:
id_temp = data[data["num_id"] == num]
length = len(id_temp)
# get each ID first play time and last play time
time_first = id_temp.iloc[0, 1]
time_last = id_temp.iloc[length - 1, 1]
# add blank space to time string
time_first = time_first[:2] + ' ' + time_first[2:5] + ' ' + time_first[5:]
time_last = time_last[:2] + ' ' + time_last[2:5] + ' ' + time_last[5:]
# change time string to standard format
timeArray_first = time.strptime(time_first, "%d %b %Y %H:%M:%S")
timeArray_last = time.strptime(time_last, "%d %b %Y %H:%M:%S")
# chage the standard format to seconds
first_second = int(time.mktime(timeArray_first))
last_second = int(time.mktime(timeArray_last))
# calculate the difference and convert to year
# 31536000 seconds = 1 year
total = (last_second - first_second)/31536000
# save to dictionary
time_data["num_id"].append(num)
time_data["total_time"].append(total)
# output to the file
result = pd.DataFrame(time_data)
result.to_csv("time.csv", index=False)
繪製成直方圖
result_time = pd.read_csv("time.csv")
plt.title("Total time")
plt.xlabel("Si : time")
plt.ylabel("numbers of people")
plt.hist(result_time["total_time"])# 繪製直方圖
plt.show() # 顯現圖形
看到數據的分布情形,我們可以將資料分群,像是分兩群,短期玩家及長期玩家,或是分三群,過客型玩家、短期成癮玩家、長期穩定型玩家,該如何分析就需要自己決定了。還有很多的參數,我們沒有進行檢查或分析,這裡只舉些小小的分析供大家學習。
EDA是由有著統計界畢卡索之稱的John Tukey提出的,以下是他的經典名言
"An approximate answer to the right question is worth a great deal more than a precise answer to the wrong question."
對正確的問題有個近似的答案,勝過對錯的問題有精確的答案。
利用EDA的分析,讓我們能在處理問題、分析數據上能更加有把握。