教你用R進行資料探勘
引言
R是一種廣泛用於資料分析和統計計算的強大語言,於上世紀90年代開始發展起來。得益於全世界眾多 愛好者的無盡努力,大家繼而開發出了一種基於R但優於R基本文字編輯器的R Studio(使用者的介面體驗更好)。也正是由於全世界越來越多的資料科學社群和使用者對R包的慷慨貢獻,讓R語言在全球範圍內越來越流行。其中一些R包,例如MASS,SparkR, ggplot2,使資料操作,視覺化和計算功能越來越強大。
我們所說的機器學習和R有什麼關係呢?我對R的第一印象是,它只是一個統計計算的一個軟體。但是後來我發現R有足夠的能力以一個快速和簡單的方式來實現機器學習演算法。這是用R來學習資料科學和機器學習的完整教程,讀完本文,你將有使用機器學習的方法來構建預測模型的基本能力。
注:這篇文章對於之前沒有很多資料科學知識的同學們是特別值得一看的,同時掌握一定的代數和統計知識將會更有益於您的學習。
一、初識R語言
1、為什麼學R ?
事實上,我沒有程式設計經驗,也沒有學過計算機。但是我知道如果要學習資料科學,一個人必須學習R或Python作為開始學習的工具。我選擇了前者,同時在學習過程中我發現了一些使用R的好處:
用R語言編碼非常的簡單;
R是一個免費的開源軟體,同時它可以直接在官網上下載;
R語言中有來自於全世界愛好者貢獻的即時訪問超過7800個用於不同計算的R包。
R語言還有遍佈全世界的學習社群及論壇,你能很輕鬆的獲取幫助;
我們憑藉R包能夠獲得高效能的計算體驗;
它是,資料分析公司高度尋求技能之一。
2、如何安裝R / Rstudio?
你可以https://www.r-project.org/官網下載並安裝R,需要注意的是R的更新速度很快,下載新版本的體驗會更好一些。
另外,我建議你從RStudio開始,因為RStudio的介面程式設計體驗更好一些。你可以通過https://www.rstudio.com/products/rstudio/download/ 在“支援的平臺上安裝”部分中, 根據您的作業系統選擇您需要的安裝程式。點選桌面圖示RStudio,就開始你的程式設計體驗,如下圖所示:
讓我們快速的瞭解:一下R介面
R script::在這個空間裡可以寫程式碼,要執行這些程式碼,只需選擇的程式碼行和按下Ctrl + R即可或者,你可以點選“執行”按鈕位置在右上角R的指令碼。
R console:這個區域顯示的輸出程式碼執行:,同時你可以在控制檯直接寫程式碼。但是程式碼直接進入R控制檯無法追蹤。
R環境:這個空間是顯示設定的外部元素補充道。這裡麵包括資料集、變數向量,還可以檢查R資料是否被正確載入。
圖形輸出視窗:這個空間顯示圖表中建立的探索性資料分析。不僅僅輸出圖形,您可以選擇包,尋求幫助和嵌入式R的官方文件。
3、如何安裝包?
R的計算能力在於它擁有強大的R包。在R中,大多數資料處理任務可以從兩方面進行,使用R包和基本功能。在本教程中,我們將介紹最方便的和強大的R包。特別的,一般不太建議直接在R軟體的中直接安裝載入包,因為這樣可能會影響你的計算速度。我們建議你直接在R的官網上下載好您所需要的R包,通過本地安裝的形式進行安裝,如下:
在軟體中安裝:install.packages(“package name”)
本地安裝: install.packages(“E:/r/ggplot2_2.1.0.zip”)
4、用R進行基本的統計計算
讓我們開始熟悉R的程式設計環境及一些基本的計算,在R程式設計指令碼視窗中輸入程式,如下:
1 2 3 4 5 6 7 8 9 10 |
> 2 + 3 > 5 > 6 / 3 > 2 > (3*8)/(2*3) > 4 > log(12) > 1.07 > sqrt (121) > 11 |
類似地,您也可以自己嘗試各種組合的計算形式並得到結果。但是,如果你做了太多的計算,這樣的程式設計未免過於麻煩,在這種情況下,建立變數是一個有用的方法。在R中,您可以建立變數的形式來簡化。建立變數時使用< -或=符號,例如我想建立一個變數x計算7和8的總和,如下:
1 2 3 |
> x <- 8 + 7 > x > 15 |
特別的,一旦我們建立一個變數,你不再直接得到的輸出,此時我們需要輸入對應的變數然後再執行結果。注意,變數可以是字母,字母數字而不是數字,數字是不能建立數值變數的、
二、程式設計基礎慨念及R包
1、R中的資料型別和物件
資料型別
R中資料型別包括數值型,字元型,邏輯型,日期型及預設值,這個資料型別我們在運用資料的過程中,大家很容易可以自行了解,在此不做詳細解釋。
1 2 3 |
> a <- c(1.8, 4.5) #數值型 > b <- c(1 + 2i, 3 - 6i) #混合型 > c <- c("zhangsan",”lisi” ) #字元型 |
資料物件
R中的資料物件主要包括向量(數字、整數等)、列表、資料框和矩陣。讓具體的進行了解:
○1向量
正如上面提到的,一個向量包含同一個類的物件。但是,你也可以混合不同的類的物件。當物件的不同的類混合在一個列表中,這種效應會導致不同型別的物件轉換成一個類。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
> qt <- c("Time", 24, "October", TRUE, 3.33) #字元型 > ab <- c(TRUE, 24) #數值型 > cd <- c(2.5, "May") #字元型 > qt [1] "Time" "24" "October" "TRUE" "3.33" > ab [1] 1 24 > cd [1] "2.5" "May |
注:1、檢查任何物件的類,使用class()函式的功能。
2、轉換一個資料的類,使用as.()函式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
> class(qt) >"character" > bar <- 0:5 > class(bar) > "integer" > as.numeric(bar) > class(bar) > "numeric" > as.character(bar) > class(bar) > "character" |
類似地,您可以自己嘗試改變其他任何的類向量
○2列表
一個列表是一種包含不同的資料型別的元素特殊型別的向量。例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
> my_list <- list(22, "ab", TRUE, 1 + 2i) > my_list [[1]] [1] 22 [[2]] [1] "ab" [[3]] [1] TRUE [[4]] [1] 1+2i |
可以看出,,列表的輸出不同於一個向量。這是因為不同型別的所有物件。第一個雙括號[1]顯示了第一個元素包括的索引內容,依次類推。另外的,您自己還可以嘗試:
1 2 3 4 5 6 7 8 9 |
> my_list[[3]] > [1] TRUE > my_list[3] > [[1]] [1] TRUE |
○3矩陣
當一個向量與行和列即維度屬性,它變成了一個矩陣。一個矩陣是由行和列組成的,讓我們試著建立一個3行2列的矩陣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
> my_matrix <- matrix(1:6, nrow=3, ncol=2) > my_matrix [,1] [,2] [1,] 1 4 [2,] 2 5 [3,] 3 6 > dim(my_matrix) [1] 3 2 > attributes(my_matrix) $dim [1] 3 2 |
正如你所看到的,一個矩陣的維度你可以通過dim()或attributes()命令獲得,從一個矩陣中提取一個特定元素,只需使用上面矩陣的形式。例如
1 2 3 4 5 6 7 |
> my_matrix[,2] #提取出第二列 > my_matrix[,1] #提取出第二列 > my_matrix[2,] #提取出第二行 > my_matrix[1,] #提取出第二行 |
同樣的,,您還可以從個一個向量開始建立所需要的矩陣,我們,需要做的是利用dim()分配好維度。如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
> age <- c(23, 44, 15, 12, 31, 16) > age [1] 23 44 15 12 31 16 > dim(age) <- c(2,3) > age [,1] [,2] [,3] [1,] 23 15 31 [2,] 44 12 16 > class(age) [1] "matrix" |
另外,你也可以加入兩個向量使用cbind()和rbind()函式。但是,需要確保兩向量相同數量的元素。如果沒有的話,它將返回NA值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
> x <- c(1, 2, 3, 4, 5, 6) > y <- c(20, 30, 40, 50, 60) > cbind(x, y) > cbind(x, y) x y [1,] 1 20 [2,] 2 30 [3,] 3 40 [4,] 4 50 [5,] 5 60 [6,] 6 70 > class(cbind(x, y)) [1] “matrix |
○4資料框
這是最常用的一種資料型別,它是用來儲存列表資料的。它不同於矩陣,在一個矩陣中,每一個元素必須有相同的類。但是,在一個資料框裡你可以把向量包含不同類別的列表。這意味著,每一列的資料就像一個列表,每次你在R中讀取資料將被儲存在一個資料框中。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
> df <- data.frame(name = c("ash","jane","paul","mark"), score = c(67,56,87,91)) > df name score 1 ash 67 2 jane 56 3 paul 87 4 mark 91 > dim(df) [1] 4 2 > str(df) 'data.frame': 4 obs. of 2 variables: $ name : Factor w/ 4 levels "ash","jane","mark",..: 1 2 4 3 $ score: num 67 56 87 91 > nrow(df) [1] 4 > ncol(df) [1] 2 |
讓我們解釋一下上面的程式碼。df是資料框的名字。dim()返回資料框的規格是4行2列,str()返回的是一個資料框的結構,nrow()和ncol()返回是資料框的行數和列數。特別的,我們需要理解一下R中缺失值的概念,NA代表缺失值,這也是預測建模的關鍵部分。現在,我們示例檢查是否一個資料集有缺失值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
> df[1:2,2] <- NA #令前兩行第二列的數值為NA > df name score 1 ash NA 2 jane NA 3 paul 87 4 mark 91 > is.na(df) #檢查整個資料集缺失值和返回邏輯輸出值 name score [1,] FALSE TRUE [2,] FALSE TRUE [3,] FALSE FALSE [4,] FALSE FALSE > table(is.na(df)) #返回邏輯值各類的數量 FALSE TRUE 6 2 > df[!complete.cases(df),] #返回缺失值所在的行值 name score 1 ash NA 2 jane NA |
缺失值的存在嚴重阻礙了我們正常計算資料集。例如,因為有兩個缺失值,它不能直接做均值得分。例如:
1 2 3 4 5 6 7 |
mean(df$score) [1] NA > mean(df$score, na.rm = TRUE) [1] 89 |
na.rm = TRUE告訴R計算時忽略缺失值,只是計算選定的列中剩餘值的均值(得分)。刪除在資料中的行和NA,您可以使用na.omit
1 2 3 4 5 6 7 8 9 |
> new_df <- na.omit(df) > new_df name score 3 paul 87 4 mark 91 |
2、R中的控制語句
正如它的名字一樣,這樣的語句在編碼中起控制函式的作用,寫一個函式也是一組多個命令自動重複編碼的過程。例如:你有10個資料集,你想找到存在於每一個資料集中的“年齡”列。這可以通過兩種方法,一種需要我們執行一個特定的程式執行10次,另外一種就需要通過編寫一個控制語句來完成。我們先了解下R中的控制結構簡單的例子:
If.else,這個結構是用來測試一個條件的,下面是語法:
1 2 3 4 5 6 7 8 9 |
if (<condition>){ ##do something } else { ##do something } |
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#initialize a variable N <- 10 #check if this variable * 5 is > 40 if (N * 5 > 40){ print("This is easy!") } else { print ("It's not easy!") } [1] "This is easy!" |
For語句,這個結構是當一個迴圈執行固定的次數時使用。下面是語法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
for (<search condition>){ #do something } Example #initialize a vector y <- c(99,45,34,65,76,23) #print the first 4 numbers of this vector for(i in 1:4){ print (y[i]) } [1] 99 [1] 45 [1] 34 [1] 65 |
while,語句
它首先測試條件,並只有在條件是正確的時才執行,一旦執行迴圈,條件是再次測試,直到滿足指定的條件然後輸出。下面是語法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#初始化條件 Age <- 12 #檢驗年齡是否小於17 while(Age < 17){ print(Age) Age <- Age + 1 } [1] 12 [1] 13 [1] 14 [1] 15 [1] 16 |
當然,還有其他的控制結構,但不太常用的比上面的解釋。例如:
Repeat 它執行一個無限迴圈
break——它打破迴圈的執行
next——它允許跳過一個迭代迴圈
return——它幫助退出函式
注意:如果你發現這部分的控制結構難以理解,不用擔心。R語言中來自於眾多人貢獻的包,會幫助你很多。
3、常用的R包
在R的映象(CRAN)中,有超過7800個包可供大家呼叫,其中很多包可以用來預測建模在本文中,我們在下面會簡單的介紹其中幾個。之前,我們已經解釋了安裝包的方法,大家可以根據自己的需要去下載安裝。
匯入資料
R為資料的匯入進口提供了廣泛的包,並且可以接入任何格式的資料。如txt,,csv,,sql等均可快速匯入大檔案的資料,。
資料視覺化
R同樣可以用來構建繪圖命令並且是建立簡單的圖表非常好用。但是,當建立的圖形變得較為複雜時,你應該安裝ggplot2。
資料操作
R中有很多關於資料操作集合的包,他們可以做基本的和先進的快速計算、例如dplyr,plyr ,tidyr,lubricate,stringr等。
建模學習/機器學習
對於模型學習,caret包是強大到足以滿足大多建立機器學習模型的必要。當然,您也可以安裝演算法包,例如對於隨機森林,決策樹等等。
到這裡為止,你會覺得對於R的相關元件都相對熟悉啦,從現在開始我們開始介紹一些關於模型預測的知識。
三、用R進行資料預處理
從這一節開始,我們將深入閱讀預測建模的不同階段。對於資料的預處理是非常重要的,這一階段學習將強化我們的對資料操作的應用,讓我們在接下來的R中去學習和應用一下。在本教程中,我們以這個大市場銷售預測資料集為例。首先,我們先理解一下資料結構,如下圖:
1、資料集中基礎概念
○1最後一列ItemOutlet_Sales為響應變數(因變數y),是我們需要做出預測的。前面的變數是自變數xi,是用來預測因變數的。
○2資料集
預測模型一般是通過訓練資料集建立,訓練資料總是包括反變數;測試資料:一旦模型構建,它在測試資料集中的測試是較為準確的,這個資料總是比訓練資料集包含更少數量的觀察值,而且是它不包括反應變數的。
資料的匯入和基本探索
○1在使用R語言時一個重要設定是定義工作目錄,即設定當前執行路徑(這樣你的全部資料和程式都將儲存在該目錄下)
1 2 3 |
#設定當前工作目錄 setwd(“E:/r”) |
一旦設定了目錄,我們可以很容易地匯入資料,使用下面的命令匯入csv檔案:
1 2 3 4 5 |
#載入資料集 train <- read.csv("E:/r/Train_UWu5bXk.csv") test <- read.csv("E:/r/Test_u94Q5KV.csv") |
通過R環境檢查資料是否已成功載入,然後讓我們來探討資料
1 2 3 4 5 6 7 8 9 |
#檢視資料的維度 > dim(train) [1] 8523 12 > dim(test) [1] 5681 11 |
從結果我們可以看到訓練集有8523行12列資料,測試集有5681行和11列訓練資料,並且這也是正確的。測試資料應該總是少一列的。現在讓我們深入探索訓練資料集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#檢查訓練集中的變數和型別 > str(train) 'data.frame': 8523 obs. of 12 variables: $ Item_Identifier : Factor w/ 1559 levels "DRA12","DRA24",..: 157 9 663 1122 1298 759 697 739 441 991 ... $ Item_Weight : num 9.3 5.92 17.5 19.2 8.93 ... $ Item_Fat_Content : Factor w/ 5 levels "LF","low fat",..: 3 5 3 5 3 5 5 3 5 5 ... $ Item_Visibility : num 0.016 0.0193 0.0168 0 0 ... $ Item_Type : Factor w/ 16 levels "Baking Goods",..: 5 15 11 7 10 1 14 14 6 6 ... $ Item_MRP : num 249.8 48.3 141.6 182.1 53.9 ... $ Outlet_Identifier : Factor w/ 10 levels "OUT010","OUT013",..: 10 4 10 1 2 4 2 6 8 3 ... $ Outlet_Establishment_Year: int 1999 2009 1999 1998 1987 2009 1987 1985 2002 2007 ... $ Outlet_Size : Factor w/ 4 levels "","High","Medium",..: 3 3 3 1 2 3 2 3 1 1 ... $ Outlet_Location_Type : Factor w/ 3 levels "Tier 1","Tier 2",..: 1 3 1 3 3 3 3 3 2 2 ... $ Outlet_Type : Factor w/ 4 levels "Grocery Store",..: 2 3 2 1 2 3 2 4 2 2 ... $ Item_Outlet_Sales : num 3735 443 2097 732 995 ... |
2、圖形表示
當使用圖表來表示時,我想大家會更好的瞭解這些變數。一般來講,我們可以從兩個方面分析資料:單變數分析和雙變數分析。對於單變數分析來講較為簡單,在此不做解釋。我們本文以雙變數分析為例:
(對於視覺化,我們將使用ggplot2包。這些圖可以幫助我們更好理解變數的分佈和頻率的資料集)
首先做出Item_Visibility和Item_Outlet_Sales兩個變數的散點圖
1 |
ggplot(train, aes(x= Item_Visibility, y = Item_Outlet_Sales)) + geom_point(size = 2.5, color="navy") + xlab("Item Visibility") + ylab("Item Outlet Sales") + ggtitle("Item Visibility vs Item Outlet Sales") |
從圖中,我們可以看到大多數銷售已從產品能見度小於0.2。這表明item_visibility < 0.2,則該變數必須是確定銷售的一個重要因素。
做出Outlet_Identifier和Item_Outlet_Sales兩個變數的柱狀關係圖
1 |
ggplot(train, aes(Outlet_Identifier, Item_Outlet_Sales)) + geom_bar(stat = "identity", color = "purple") +theme(axis.text.x = element_text(angle = 70, vjust = 0.5, color = "black")) + ggtitle("Outlets vs Total Sales") + theme_bw() |
在這裡,我們推斷可能是OUT027的銷量影響啦OUT35的銷量緊隨其後。OUT10和OUT19可能是由於最少的客流量,從而導致最少的出口銷售。
做出Outlet_type和Item_Outlet_Sales兩個變數的箱體圖
1 |
ggplot(train, aes(Item_Type, Item_Outlet_Sales)) + geom_bar( stat = "identity") +theme(axis.text.x = element_text(angle = 70, vjust = 0.5, color = "navy")) |
從這個圖表,我們可以推斷出水果和蔬菜最有利於銷售零食數量的出口,其次是家用產品。
做出Item_Type和Item_MRP兩個變數的箱線圖
這次我們使用箱線圖來表示,箱線圖的好處在於我們可以看到相應變數的異常值和平均偏差水平。
1 |
ggplot(train, aes(Item_Type, Item_MRP)) +geom_boxplot() +ggtitle("Box Plot") + theme(axis.text.x = element_text(angle = 70, vjust = 0.5, color = "red")) + xlab("Item Type") + ylab("Item MRP") + ggtitle("Item Type vs Item MRP") |
在圖中,,黑色的點就是一個異常值,盒子裡黑色的線是每個專案型別的平均值。
3、缺失值處理
缺失值對於自變數和因變數之間的關係有很大的影響。現在,讓我們理解一下缺失值的處理的知識。讓我們來做一些快速的資料探索,首先,我們將檢查資料是否有缺失值。
1 2 3 4 5 |
> table(is.na(train)) FALSE TRUE 100813 1463 |
我們可以看出在訓練資料集中有1463個缺失值。讓我們檢查這些缺失值的變數在哪裡,其實很多資料科學家一再建議初學者在在資料探索階段應密切關注缺失值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
> colsums(is.na(train)) Item_Identifier Item_Weight 0 1463 Item_Fat_Content Item_Visibility 0 0 Item_Type Item_MRP 0 0 Outlet_Identifier Outlet_Establishment_Year 0 0 Outlet_Size Outlet_Location_Type 0 0 Outlet_Type Item_Outlet_Sales 0 0 |
因此,我們看到列Item_Weight 有1463個缺失的資料。從這個資料我們還可以得到更多的推論:
1 |
> summary(train) |
從圖中,我們可以看到每列的最小值,最大值,中位數,平均值,缺失值的資訊等等。我們看到變數Item_Weight中有缺失值,而且Item_Weight是一個連續變數。因此,在這種情況下,我們一般用樣本中變數的均值或中位數賦值給缺失值。計算變數item_weight的均值和中位數,這是最常用處理缺失值的的方法,其他的方法在此不贅述。
我們可以先把兩個資料集合並,這樣就不需要編寫獨立編碼訓練和測試資料集,這也會節省我們的計算時間。但是合併結合兩個資料框,我們必須確保他們相同的列,如下:
1 2 3 4 5 6 7 |
> dim(train) [1] 8523 12 > dim(test) [1] 5681 11 |
我們知道,測試資料集有個少一列因變數。首先來新增列,我們可以給這個列賦任何值。一個直觀的方法是我們可以從訓練資料集中提取銷售的平均值,並使用$Item_Outlet_Sales作為測試變數的銷售列。不過,在此,我們讓它簡單化給最後一列賦值為1。
1 2 3 |
test$Item_Outlet_Sales <- 1 > combi <- rbind(train, test) |
接下來我們先來計算中位數,選用中位數是因為它在離散值中很有代表性。
1 2 3 4 5 6 7 |
combi$Item_Weight[is.na(combi$Item_Weight)] <- median(combi$Item_Weight, na.rm = TRUE) > table(is.na(combi$Item_Weight)) FALSE 14204 |
4、連續變數和分類變數的處理
在資料處理中,對連續資料集和分類變數的非別處理是非常重要的。在這個資料集,我們只有3個連續變數,其他的是分類變數。如果你仍然感到困惑,建議你再次使用str()檢視資料集。
對於變數Item_Visibility,在上面的圖中可以看到該項中有的能見度為零值,這幾乎是不可行的。因此,我們考慮將它看成缺失值,用中位數來處理。
1 2 3 |
> combi$Item_Visibility <- ifelse(combi$Item_Visibility == 0, median(combi$Item_Visibility) |
現在讓我們繼續處理一下分類變數。在初步的資料探索中,我們看到有錯誤的水平變數需要糾正。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
levels(combi$Outlet_Size)[1] <- "Other" > library(plyr) > combi$Item_Fat_Content <- revalue(Combi$Item_Fat_Content,c("LF" = "Low Fat", "reg" = "Regular")) #將源資料中“LF”重新命名為“Low Fat” >combi$Item_Fat_Content <- revalue(Combi$Item_Fat_Content, c("low fat" = "Low Fat")) #將源資料中“low fat”重新命名為“Low Fat” > table(combi$Item_Fat_Content) #計算各水平下的頻數 Low Fat Regular 9185 5019 |
使用上面的命令,我們指定的名稱“others”為其他未命名的變數,簡要劃分了Item_Fat_Content的等級。
5、特徵值變數計算
現在我們已經進入了大資料時代,很多時候需要大量的資料演算法計算,但是之前所選出的變數不一定會和模型擬合的效果很好。,所以我們需要提取新的變數,提供儘可能多的“新”的資訊來幫助模型做出更準確的預測。以合併後的資料集為例,你覺得哪些因素)可能會影響Item_Outlet_Sales?
關於商店種類變數計算
在源資料中有10個不同的門店,門店的數目越多,說明某種商品更容易在這個商店中售出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
> library(dplyr) > a <- combi%>% group_by(Outlet_Identifier)%>% tally() #用管道函式對門店按編碼分類計數 > head(a) Source: local data frame [6 x 2] Outlet_Identifier n (fctr) (int) 1 OUT010 925 2 OUT013 1553 3 OUT017 1543 4 OUT018 1546 5 OUT019 880 6 OUT027 1559 > names(a)[2] <- "Outlet_Count" > combi <- full_join(a, combi, by = "Outlet_Identifier") |
注:管道函式的思路,將左邊的值管道輸出為右邊呼叫的函式的第一個引數。
商品種類計算
同樣的,我們也可以計算商品種類的資訊,這樣我們可以通過結果看到商品在各家商店出現的頻率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
> b <- combi%>%。 group_by(Item_Identifier)%>% tally() > names(b)[2] <- "Item_Count" > b Item_Identifier Item_Count #數量較多不一一列舉 Source: local data frame [1,559 x 2] Item_Identifier Item_Count (fctr) (int) 1 DRA12 9 2 DRA24 10 3 DRA59 10 4 DRB01 8 5 DRB13 9 6 DRB24 8 7 DRB25 10 8 DRB48 9 9 DRC01 9 10 DRC12 8 .. ... ... > combi <- merge(b, combi, by = “Item_Identifier”) |
商店的成立時間的變數探索
我們假設商店的成立時間越久,該商店的客流量和產品銷量越會越多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
> c <- combi%>% select(Outlet_Establishment_Year)%>% mutate(Outlet_Year = 2013 - combi$Outlet_Establishment_Year) > head(c) Outlet_Establishment_Year Outlet_Year 1 1999 14 2 2009 4 3 1999 14 4 1998 15 5 1987 26 6 2009 4 > combi <- full_join(c, combi) |
以第一個年份為例,這表明機構成立於1999年,已有14年的歷史(以2013年為截止年份)。
注:mutate函式,是對已有列進行資料運算並新增為新列。
商品所屬型別的相關計算
通過對商品所屬型別的計算,我們可以從其中發現人們的消費趨勢。從資料中們可以看出仔細看商品標註DR的,大多是可以吃的食物。對於FD,大多是屬於飲品類的。同樣的我們注意到到NC類,可能是生活用品(非消耗品),但是NC類中的所標註較為複雜。於是,我們將把這些變數提取出來,並放到一個新變數中。在這裡我將使用substr()和gsub()函式來實現提取和重新命名變數。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
> q <- substr(combi$Item_Identifier,1,2) #字元中的特徵值識別為FD和DR > q <- gsub("FD","Food",q) #將資料中FD標記為Food > q <- gsub("DR","Drinks",q) #將資料中DR標記為Drinks > q <- gsub("NC","Non-Consumable",q) #將資料中NC標Non-Consumable > table(q) Drinks Food Non-Consumable 1317 10201 2686 > combi$Item_Type_New <- q #將處理過的變數型別命名為Item_Type_New |
當然,你也可以試著去增加一些新變數幫助構建更好的模型,但是,增加新變數時必須使它與其他的變數之間是不相關的。如果你不確定與其他變數之間是否存在相關關係,可以通過函式cor()來進行判斷。
對字元變數進行編碼
○1標籤編碼
這一部分的任務是將字元型的標籤進行編碼,例如在在我們的資料集中,變數Item_Fat_Content有2個級別低脂肪和常規,我們將低脂編碼為0和常規型的編碼為1 。因為這樣能夠幫助我們進行定量的分析。 我們可以通過ifelse語句來實現。
1 |
> combi$Item_Fat_Content <- ifelse(combi$Item_Fat_Content == "Regular",1,0) # 將低脂編碼為0和常規型的編碼為1 |
○2獨熱編碼
獨熱編碼即 One-Hot 編碼,又稱一位有效編碼,其方法是使用N位狀態暫存器來對N個狀態進行編碼,每個狀態都由有獨立的暫存器位,並且在任意時候,其中只有一位有效。例如:變數Outlet_ Location_Type。它有三個層次在獨熱編碼中,,將建立三個不同變數1和0組成。1將代表變數存在,,0代表變數不存在。如下::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
sample <- select(combi, Outlet_Location_Type) > demo_sample <- data.frame(model.matrix(~.-1,sample)) > head(demo_sample) Outlet_Location_TypeTier.1 Outlet_Location_TypeTier.2 Outlet_Location_TypeTier.3 1 1 0 0 2 0 0 1 3 1 0 0 4 0 0 1 5 0 0 1 6 0 0 1 |
這是一個獨熱編碼的示範。希望你現在已經理解這個概念。現在這們將這種技術也適用於我們的資料集分類變數中(不含ID變數)。
1 2 3 |
>library(dummies) >combi <- dummy.data.frame(combi, names = c('Outlet_Size','Outlet_Location_Type','Outlet_Type', 'Item_Type_New'), sep='_') |
以上,我們介紹了兩種不同方法在R中去做獨熱編碼,我們可以檢查一下編碼是否已經完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
> str (combi) $ Outlet_Size_Other : int 0 1 1 0 1 0 0 0 0 0 ... $ Outlet_Size_High : int 0 0 0 1 0 0 0 0 0 0 ... $ Outlet_Size_Medium : int 1 0 0 0 0 0 1 1 0 1 ... $ Outlet_Size_Small : int 0 0 0 0 0 1 0 0 1 0 ... $ Outlet_Location_Type_Tier 1 : int 1 0 0 0 0 0 0 0 1 0 ... $ Outlet_Location_Type_Tier 2 : int 0 1 0 0 1 1 0 0 0 0 ... $ Outlet_Location_Type_Tier 3 : int 0 0 1 1 0 0 1 1 0 1 ... $ Outlet_Type_Grocery Store : int 0 0 1 0 0 0 0 0 0 0 ... $ Outlet_Type_Supermarket Type1: int 1 1 0 1 1 1 0 0 1 0 ... $ Outlet_Type_Supermarket Type2: int 0 0 0 0 0 0 0 1 0 0 ... $ Outlet_Type_Supermarket Type3: int 0 0 0 0 0 0 1 0 0 1 ... $ Item_Outlet_Sales : num 1 3829 284 2553 2553 ... $ Year : num 14 11 15 26 6 9 28 4 16 28 ... $ Item_Type_New_Drinks : int 1 1 1 1 1 1 1 1 1 1 ... $ Item_Type_New_Food : int 0 0 0 0 0 0 0 0 0 0 ... $ Item_Type_New_Non-Consumable : int 0 0 0 0 0 0 0 0 0 0 ... |
我們可以看出獨熱編碼之後,之前的變數是已經自動被移除了資料集。
四、用機器學習方法進行預測建模
在進行構造資料模型前,我們將刪除之前已經被轉過的原始變數,可以通過使用dplyr包中的select()實現,如下:
1 2 3 |
> combi <- select(combi, -c(Item_Identifier, Item_Fat_Content ,Outlet_Identifier,, Outlet_Establishment_Year,Item_Type)) > str(combi) |
在本節中,我將介紹迴歸、決策樹和隨機森林等演算法。這些演算法的詳細解釋已經超出了本文的範圍,如果你想詳細的瞭解,推薦大家看機器學習的相關書籍。現在我們要將兩個資料集分開,以便我們來進行預測建模。如下:
1 2 3 |
> new_train <- combi[1:nrow(train),] > new_test <- combi[-(1:nrow(train)),] |
1、多元線性迴歸
使用多元迴歸建模時,一般用於響應變數(因變數)是連續型和可供預測變數有很多時。如果它因變數被分類,我們一般會使用邏輯迴歸。在我們做迴歸前,我們先來了解一些迴歸的基本假設:
在響應變數和自變數之間存在某種線性關係;
各個自變數之間是不相關的,如果存在相關關係,我們稱這個模型出現了多重共線性。
誤差項也是要求不相關的。否則,它將導致模型出現自相關。
誤差項必須有恆定方差。否則,它將導致模型出現異方差性。
在R中我們使用lm()函式來做迴歸,如下:
1 2 3 |
linear_model <- lm(Item_Outlet_Sales ~ ., data = new_train) #構建模型 > summary(linear_model) |
調整後的R²可以很好的衡量一個迴歸模型的擬合優度。R²越高說明模型擬合的越好從上圖可以看出adjusted R²= 0.2084。這意味著我們擬合的這個模型很不理想。而且可以p值看出這些新變數例如Item count, Outlet Count 和 Item_Type_New.對於我們的模型構造而言並沒有什麼幫助,因為它們的sign.遠小於0.05的顯著性水平。對模型重要的變數是p值小於0.05的變數,也就是上圖中後面帶有*的變數。
另外,我們知道變數之間存在相關性,會影響模型的準確性,我們可以利用cor()函式來看一下各變數之間的相關關係。如下:
cor(new_train)
另外,您還可以使用corrplot包來做相關係數,如下的程式就幫助我們找到一個共線性很強的兩個變數
1 2 3 |
cor(new_train$Outlet_Count, new_train$`Outlet_Type_Grocery Store`) [1] -0.9991203 |
可以看出變數Outlet_Count與變數Outlet_Type_Grocery Store成高度負相關關係。另外,我們通過剛才的分析發現了模型中的一些問題:
模型中有相關關係的變數存在;
我們做了獨熱編碼編碼和標籤編碼,但從結果來看,通過建立虛擬變數對於這個線性迴歸模型的建立意義不大。
建立的新變數對於迴歸模型的擬合也沒有很大影響。
接下來,我們嘗試建立不含編碼和新變數的較大的迴歸模型。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#載入資料 train <- read.csv("E:/r/Train_UWu5bXk.csv") test <- read.csv("E:/r/Test_u94Q5KV.csv") > test$Item_Outlet_Sales <- 1 #給測試樣本中的響應變數賦值 #合併訓練集和測試集 > combi <- rbind(train, test) #impute missing value in Item_Weight > combi$Item_Weight[is.na(combi$Item_Weight)] <- median(combi$Item_Weight, na.rm = TRUE) #用中位數處理缺失值 > combi$Item_Visibility <- ifelse(combi$Item_Visibility == 0, median(combi$Item_Visibility), combi$Item_Visibility) #給變數 Outlet_Size整理等級水平 > levels(combi$Outlet_Size)[1] <- "Other" #給變數Item_Fat_Content重新命名 > library(plyr) > combi$Item_Fat_Content <- revalue(combi$Item_Fat_Content,c("LF" = "Low Fat", "reg" ="Regular")) > combi$Item_Fat_Content <- revalue(combi$Item_Fat_Content, c("low fat" = "Low Fat")) #建立一個新列 > combi$Year <- 2013 - combi$Outlet_Establishment_Year #刪除模型中不需要的變數 > library(dplyr) > combi <- select(combi, -c(Item_Identifier, Outlet_Identifier, Outlet_Establishment_Year)) #拆分資料集 > new_train <- combi[1:nrow(train),] > new_test <- combi[-(1:nrow(train)),] #線性迴歸 > linear_model <- lm(Item_Outlet_Sales ~ ., data = new_train) > summary(linear_model) |
上圖中可以看到,調整後的R²= 0.5623。這告訴我們,有時只需你的計算過程簡單一些可能會得到更精確的結果。讓我們從一些迴歸圖中去發現一些能夠提高模型精度的辦法。
1 2 3 |
> par(mfrow=c(2,2)) > plot(linear_model) |
從左上的第一個殘差擬合圖中我們可以看出實際值與預測值之間殘差不是恆定的,這說明該模型中存在著異方差。解決異方差性的一個常見的做法就是對響應變數取對數(減少誤差)。
1 2 3 |
>linear_model <- lm(log(Item_Outlet_Sales) ~ ., data = new_train) > summary(linear_model) |
可以看出調整後的R²= 0.72,說明模型的構建有了顯著的改善,我們可以再做一次擬合迴歸圖
1 2 3 |
> par(mfrow=c(2,2)) > plot(linear_model) |
上圖中,殘差值與擬合值之間已經沒有了長期趨勢,說明該模型的擬合效果理想。我們也經常用RMSE來衡量模型的好壞,並且我們可以通過這個值與其他演算法相比較。如下所示
1 2 3 4 5 6 7 |
install.packages("E:/r/ Metrics_0.1.1.zip ") > library(Metrics) > rmse(new_train$Item_Outlet_Sales, exp(linear_model$fitted.values)) [1] 1140.004 |
接下來讓我們進行決策樹演算法來改善我們的RMSE得分
2、決策樹
決策樹演算法一般優於線性迴歸模型,我們簡單介紹一下 ,在機器學習中決策樹是一個預測模型。他代表的是物件屬性與物件值之間的一種對映關係。樹中每個節點表示某個物件,而每個分叉路徑則代表的某個可能的屬性值,而每個葉結點則對應從根節點到該葉節點所經歷的路徑所表示的物件的值。
在R中,決策樹演算法的實現可以使用rpart包。此外,我們將使用caret包做交叉驗證。通過交叉驗證技術來構建較複雜的模型時可以使模型不容易出現過度擬合的情況。(關於交叉驗證讀者可自行查閱)另外,,決策樹使用引數CP來衡量訓練集的複雜性和準確性。引數較小的CP值可能將導致更大的決策樹,這也可能會出現過度擬合的模型。相反,引數大的CP值也導致擬合不充分的模型,也就是我們不能準確的把握所需變數的資訊。以下我們選用五折交叉驗證法來找出具有最優CP的模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# 載入所需的包 > library(rpart) > library(e1071) > library(rpart.plot) > library(caret) #設定決策樹的控制引數 > fitControl <- trainControl(method = "cv", number = 5) #選用五折交叉驗證的方法 > cartGrid <- expand.grid(.cp=(1:50)*0.01) #decision tree > tree_model <- train(Item_Outlet_Sales ~ ., data = new_train, method = "rpart", trControl = fitControl, tuneGrid = cartGrid) > print(tree_model) |
從上圖可以看出,引數cp = 0.01所對應的RMSE最小,在此我們只提供了部分的資料,你可以在R consle中查詢到更多資訊。
1 |
main_tree <- rpart(Item_Outlet_Sales ~ ., data = new_train, control = rpart.control(cp=0.01)) #在cp=0.01下構造決策樹 |
以上就是我們決策樹模型的結構,而且我們可以明顯看出變數Item_MRP是最重要的根節點,作為最重要的變數也就是根節點,來劃分預測未來的銷售量。此外讓我們檢查一下這個模型的RMSE是否有所改善。
1 2 3 4 5 |
> pre_score <- predict(main_tree, type = "vector") > rmse(new_train$Item_Outlet_Sales, pre_score) #計算均方根誤差 [1] 1102.774 |
可以看出,通過決策樹做出的誤差為1102.774,比線性迴歸得出的誤差小。說明這種方法更優一些。當然你也可以通過調引數來進一步優化降低這個誤差(如使用十折交叉驗證的方法)
3、隨機森林
隨機森林顧名思義,是用隨機的方式建立一個森林,森林裡面有很多的決策樹組成,隨機森林的每一棵決策樹之間是沒有關聯的。在得到森林之後,當有一個新的輸入樣本進入的時候,就讓森林中的每一棵決策樹分別進行一下判斷,看看這個樣本應該屬於哪一類(對於分類演算法),然後看看哪一類被選擇最多,就預測這個樣本為那一類。隨機森林演算法可以很好的處理缺失值,異常值和其他非線性的資料,其他相關知識讀者可以自行查閱。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#載入隨機森林演算法的包 > library(randomForest) #設定引數 > control <- trainControl(method = "cv", number = 5) #模型構建 > rf_model <- train(Item_Outlet_Sales ~ ., data = new_train, method = "parRF", trControl = control, prox = TRUE, allowParallel = TRUE) #通過結果選擇引數 > print(rf_model) |
在以上的語句中,可以看到=“parRF”,這是隨機森林的並行實現。這個包讓你在計算隨機森林時花費較短的時間。或者,你也可以嘗試使用rf方法作為標準隨機森林的功能。從以上結果中我們選擇RMSE最小的即選擇mtry = 15,我們嘗試用1000棵樹做計算,如下:
1 2 3 4 5 |
> forest_model <- randomForest(Item_Outlet_Sales ~ ., data = new_train, mtry = 15, ntree = 1000) #隨機森林模型 > print(forest_model) > varImpPlot(forest_model) |
這個模型中可得出RMSE = 1132.04,並沒有改進決策樹模型。另外,隨機森林的一個功能是可以展示重要變數。我們通過下圖可以看到最重要的變數是Item_MRP(通過決策樹演算法也已經表示出來)。
顯然,這個模型可以進一步進行嘗試調優引數的。同時,讓我們用RMSE最好的決策樹來對測試集做擬合。如下所示:
1 2 3 4 5 |
>main_predict <- predict(main_tree, newdata = new_test, type = "vector") > sub_file <- data.frame(Item_Identifier = test$Item_Identifier, Outlet_Identifier = test$Outlet_Identifier, Item_Outlet_Sales = main_predict) > write.csv(sub_file, 'Decision_tree_sales.csv') |
當預測其他樣本外資料,我們可以得出RMSE是1174.33,這個模型是也可以通過調引數達到更優的,以下列出幾種方法:
本例我們沒有使用標籤編碼和獨熱編碼,希望你可以嘗試以下編碼來做隨機森林模型。
調整相關的引數。
使用Gradient Boosting來做模型。
建立一個新的整體模型。
大家有什麼新的能使模型預測更優的方法都可以一起來討論,期待與大家的思維的碰撞。