R語言 CART算法和C4.5算法(決策樹)

關注微信號:小程在線

關注CSDN博客:程志偉的博客

R版本:3.4.4

最新的R官網取消了mvpart包,有需要的可以留言或者加微信,我用R3.6.1版本的顯示這個包不能使用。

還需要安裝java環境,下載jdk,配置環境變量。

draw.tree函數:繪製樹狀圖

J48函數:實現C4.5算法

maptree包:提供draw.tree函數

mvpart包:提供數據集car.test.frame

post函數:對rpart()結果繪製演示圖

prune.rpart():對rpart()的結果進行剪枝

rpart包:提供函數rpart()、prune.rpart()、post()

rpart.plot包提供rpart.plot函數,繪製決策時

RWeka包:提供函數J48

sampling包:strata函數用於分層抽樣

 

#設置工作路徑

> setwd('G:\\R語言\\大三下半年\\數據挖掘:R語言實戰\\')
Warning message:
file ‘.RData’ has magic number 'RDX3'
  Use of save versions prior to 2 is deprecated 
> library(mvpart)
> data("car.test.frame")
> head(car.test.frame)
                 Price   Country Reliability Mileage  Type Weight Disp.  HP
Eagle Summit 4    8895       USA           4      33 Small   2560    97 113
Ford Escort   4   7402       USA           2      33 Small   2345   114  90
Ford Festiva 4    6319     Korea           4      37 Small   1845    81  63
Honda Civic 4     6635 Japan/USA           5      32 Small   2260    91  92
Mazda Protege 4   6599     Japan           5      32 Small   2440   113 103
Mercury Tracer 4  8672    Mexico           4      26 Small   2285    97  82
> car.test.frame$Mileage <- 100*4.546/(1.6*car.test.frame$Mileage)
> names(car.test.frame) <- c("價格","產地","可靠性","油耗","類型","車重","發動機功率","淨馬力")
> head(car.test.frame)
                            價格      產地   可靠性   油耗    類型 車重 發動機功率 淨馬力
Eagle Summit 4   8895       USA      4  8.609848 Small 2560         97    113
Ford Escort   4  7402       USA      2  8.609848 Small 2345        114     90
Ford Festiva 4   6319     Korea      4  7.679054 Small 1845         81     63
Honda Civic 4    6635 Japan/USA      5  8.878906 Small 2260         91     92
Mazda Protege 4  6599     Japan      5  8.878906 Small 2440        113    103
Mercury Tracer 4 8672    Mexico      4 10.927885 Small 2285         97     82

#str()函數顯示該數據集有60行8列
> str(car.test.frame)
'data.frame':    60 obs. of  8 variables:
 $ 價格      : int  8895 7402 6319 6635 6599 8672 7399 7254 9599 5866 ...
 $ 產地      : Factor w/ 8 levels "France","Germany",..: 8 8 5 4 3 6 4 5 3 3 ...
 $ 可靠性    : int  4 2 4 5 5 4 5 1 5 NA ...
 $ 油耗      : num  8.61 8.61 7.68 8.88 8.88 ...
 $ 類型      : Factor w/ 6 levels "Compact","Large",..: 4 4 4 4 4 4 4 4 4 4 ...
 $ 車重      : int  2560 2345 1845 2260 2440 2285 2275 2350 2295 1900 ...
 $ 發動機功率: int  97 114 81 91 113 97 97 98 109 73 ...
 $ 淨馬力    : int  113 90 63 92 103 82 90 74 90 73 ...


> summary(car.test.frame)
      價格              產地        可靠性           油耗             類型   
 Min.   : 5866   USA      :26   Min.   :1.000   Min.   : 7.679   Compact:15  
 1st Qu.: 9932   Japan    :19   1st Qu.:2.000   1st Qu.:10.523   Large  : 3  
 Median :12216   Japan/USA: 7   Median :3.000   Median :12.353   Medium :13  
 Mean   :12616   Korea    : 3   Mean   :3.388   Mean   :11.962   Small  :13  
 3rd Qu.:14933   Germany  : 2   3rd Qu.:5.000   3rd Qu.:13.530   Sporty : 9  
 Max.   :24760   France   : 1   Max.   :5.000   Max.   :15.785   Van    : 7  
                 (Other)  : 2   NA's   :11                                   
      車重        發動機功率        淨馬力     
 Min.   :1845   Min.   : 73.0   Min.   : 63.0  
 1st Qu.:2571   1st Qu.:113.8   1st Qu.:101.5  
 Median :2885   Median :144.5   Median :111.5  
 Mean   :2901   Mean   :152.1   Mean   :122.3  
 3rd Qu.:3231   3rd Qu.:180.0   3rd Qu.:142.8  
 Max.   :3855   Max.   :305.0   Max.   :225.0  
                                               
> #2. 數據預處理
> #下面我們着重看油耗變量,因爲在以下的建模過程中,將以油耗作爲目標變量
> #一個數據集來分別構建出以離散型和連續型變量爲各自目標變量的分類樹和迴歸樹,考慮添加一列變量——分組油耗,即將油耗變量劃分爲三個組別,A:11.6~15.8個油、B:9~11.6個油、C:7.7~9個油,成爲含有3個水平的A、B、C的因子變量。
> Group_Mileage=matrix(0,60,1) #設矩陣Group_Mileage用於存放新變量
> Group_Mileage[which(car.test.frame$"油耗">=11.6)]="A"
> Group_Mileage[which(car.test.frame$"油耗"<=9)]="C"   #將油耗在7.7~9區間的樣本Group_Mileage值取C
> Group_Mileage[which(Group_Mileage==0)]="B" #將油耗不在組A、C的樣本Group_Mileage值取B
> car.test.frame$"分組油耗"=Group_Mileage  #在數據集中添加新變量分組油耗
> car.test.frame[1:10,c(4,9)]
                      油耗 分組油耗
Eagle Summit 4    8.609848        C
Ford Escort   4   8.609848        C
Ford Festiva 4    7.679054        C
Honda Civic 4     8.878906        C
Mazda Protege 4   8.878906        C
Mercury Tracer 4 10.927885        B
Nissan Sentra 4   8.609848        C
Pontiac LeMans 4 10.147321        B
Subaru Loyale 4  11.365000        B
Subaru Justy 3    8.356618        C
> a=round(1/4*sum(car.test.frame$"分組油耗"=="A"))
> b=round(1/4*sum(car.test.frame$"分組油耗"=="B"))
> c=round(1/4*sum(car.test.frame$"分組油耗"=="C"))
> #分別計算A、B、C組中應抽取測試集樣本數,記爲a、b、c
> a;b;c
[1] 9
[1] 4
[1] 2

 

#使用strata()函數對car.test.frame中的“分組油耗”變量進行分層抽樣
> library(sampling)
> sub=strata(car.test.frame, stratanames="分組油耗", size=c(c,b,a), method="srswor")

> sub
   分組油耗 ID_unit      Prob Stratum
4         C       4 0.2222222       1
7         C       7 0.2222222       1
6         B       6 0.2500000       2
8         B       8 0.2500000       2
17        B      17 0.2500000       2
21        B      21 0.2500000       2
20        A      20 0.2571429       3
38        A      38 0.2571429       3
42        A      42 0.2571429       3
47        A      47 0.2571429       3
51        A      51 0.2571429       3
52        A      52 0.2571429       3
56        A      56 0.2571429       3
57        A      57 0.2571429       3
60        A      60 0.2571429       3
> Train_Car=car.test.frame[-sub$ID_unit,] #生成訓練集
> Test_Car=car.test.frame[sub$ID_unit,]   #生成測試集
> nrow(Train_Car);nrow(Test_Car) #顯示訓練集、測試集行數
[1] 45
[1] 15


> ##################應用案例##################
> library('rpart')

載入程輯包:‘rpart’

The following object is masked _by_ ‘.GlobalEnv’:

    car.test.frame

The following objects are masked from ‘package:mvpart’:

    meanvar, na.rpart, path.rpart, plotcp, post, printcp, prune, prune.rpart,
    rpart, rpart.control, rsq.rpart, snip.rpart, xpred.rpart

> #1. 對油耗變量建立迴歸樹——數字結果

#按照公式對訓練集構建迴歸樹method="anova"
> formula_Car_Reg=油耗~價格+產地+可靠性+類型+車重+發動機功率+淨馬力 #設定模型公式
> rp_Car_Reg=rpart(formula_Car_Reg,Train_Car,method="anova")

> print(rp_Car_Reg)   #導出迴歸樹基本信息
n= 45 

node), split, n, deviance, yval
      * denotes terminal node

1) root 45 186.172800 11.845280  
  2) 發動機功率< 134 19  32.628130  9.977205 *
  3) 發動機功率>=134 26  38.786870 13.210420  
    6) 價格< 11522 7   3.003835 11.877100 *
    7) 價格>=11522 19  18.754370 13.701630 *
#按照節點層次以不同縮進量列出,並在每條節點信息後以星號*標示出是否爲葉節點。


> printcp(rp_Car_Reg)  #導出迴歸樹的cp表格

Regression tree:
rpart(formula = formula_Car_Reg, data = Train_Car, method = "anova")

Variables actually used in tree construction:
[1] 發動機功率 價格      

Root node error: 186.17/45 = 4.1372

n= 45 

        CP nsplit rel error  xerror     xstd
1 0.616405      0   1.00000 1.04401 0.182755
2 0.091467      1   0.38360 0.46353 0.075249
3 0.010000      2   0.29213 0.42704 0.063786

#獲取決策樹rp_Car_Reg詳細信息,Variable importance變量的最要程度、improve對分支的提升程度
> summary(rp_Car_Reg)
Call:
rpart(formula = formula_Car_Reg, data = Train_Car, method = "anova")
  n= 45 

          CP nsplit rel error    xerror       xstd
1 0.61640483      0 1.0000000 1.0440087 0.18275540
2 0.09146694      1 0.3835952 0.4635295 0.07524941
3 0.01000000      2 0.2921282 0.4270404 0.06378585

Variable importance
發動機功率       車重       價格     淨馬力       類型       產地 
        26         20         14         14         13         12 

Node number 1: 45 observations,    complexity param=0.6164048
  mean=11.84528, MSE=4.137174 
  left son=2 (19 obs) right son=3 (26 obs)
  Primary splits:
      發動機功率 < 134    to the left,  improve=0.6164048, (0 missing)
      價格       < 9446.5 to the left,  improve=0.5563919, (0 missing)
      車重       < 2567.5 to the left,  improve=0.5509927, (0 missing)
      類型       splits as  RRRLRR,     improve=0.4392540, (0 missing)
      淨馬力     < 109    to the left,  improve=0.3982844, (0 missing)
  Surrogate splits:
      車重   < 2747.5 to the left,  agree=0.889, adj=0.737, (0 split)
      淨馬力 < 109    to the left,  agree=0.800, adj=0.526, (0 split)
      類型   splits as  RRRLRR,     agree=0.778, adj=0.474, (0 split)
      價格   < 9446.5 to the left,  agree=0.756, adj=0.421, (0 split)
      產地   splits as  LLLLR-RR,   agree=0.756, adj=0.421, (0 split)

Node number 2: 19 observations
  mean=9.977205, MSE=1.71727 

Node number 3: 26 observations,    complexity param=0.09146694
  mean=13.21042, MSE=1.491803 
  left son=6 (7 obs) right son=7 (19 obs)
  Primary splits:
      價格       < 11522  to the left,  improve=0.4390315, (0 missing)
      車重       < 3087.5 to the left,  improve=0.3622234, (0 missing)
      類型       splits as  LRL-RR,     improve=0.3121080, (0 missing)
      發動機功率 < 185.5  to the left,  improve=0.1511378, (0 missing)
      淨馬力     < 148.5  to the left,  improve=0.1511378, (0 missing)
  Surrogate splits:
      車重       < 2757.5 to the left,  agree=0.846, adj=0.429, (0 split)
      產地       splits as  --RLL-RR,   agree=0.808, adj=0.286, (0 split)
      類型       splits as  LRR-RR,     agree=0.808, adj=0.286, (0 split)
      淨馬力     < 103.5  to the left,  agree=0.808, adj=0.286, (0 split)
      發動機功率 < 142    to the left,  agree=0.769, adj=0.143, (0 split)

Node number 6: 7 observations
  mean=11.8771, MSE=0.4291194 

Node number 7: 19 observations
  mean=13.70163, MSE=0.9870723 

> #下面我們嘗試改變rpart()函數的若干參數值,minsplit=10,將分支包含最小樣本數minsplit從默認值20改爲10,新的迴歸樹記爲rp_Car_Reg1。
> rp_Car_Reg1=rpart(formula_Car_Reg,Train_Car,method="anova",minsplit=10)

> print(rp_Car_Reg1) #導出迴歸樹基本信息
n= 45 

node), split, n, deviance, yval
      * denotes terminal node

 1) root 45 186.172800 11.845280  
   2) 發動機功率< 134 19  32.628130  9.977205  
     4) 價格< 9504.5 8   2.649246  8.582424 *
     5) 價格>=9504.5 11   3.096802 10.991590 *
   3) 發動機功率>=134 26  38.786870 13.210420  
     6) 價格< 11522 7   3.003835 11.877100 *
     7) 價格>=11522 19  18.754370 13.701630  
      14) 類型=Compact,Medium 12   2.880021 13.081890 *
      15) 類型=Large,Sporty,Van 7   3.364168 14.764060 *
> printcp(rp_Car_Reg1)  #導出迴歸樹的cp表格

Regression tree:
rpart(formula = formula_Car_Reg, data = Train_Car, method = "anova", 
    minsplit = 10)

Variables actually used in tree construction:
[1] 發動機功率 價格       類型      

Root node error: 186.17/45 = 4.1372

n= 45 

        CP nsplit rel error  xerror     xstd
1 0.616405      0  1.000000 1.04535 0.183175
2 0.144393      1  0.383595 0.46279 0.074342
3 0.091467      2  0.239202 0.36798 0.080715
4 0.067197      3  0.147735 0.38852 0.111059
5 0.010000      4  0.080538 0.32070 0.111356

 

#cp值表示可以使模型擬合程度提高的節點,剪去不重要的分支

> rp_Car_Reg2=rpart(formula_Car_Reg,Train_Car,method="anova",cp=0.1)
> #將CP值從默認的0.01改爲0.1,新的迴歸樹記爲rp_Car_Reg2
> print(rp_Car_Reg2) #導出迴歸樹基本信息
n= 45 

node), split, n, deviance, yval
      * denotes terminal node

1) root 45 186.17280 11.845280  
  2) 發動機功率< 134 19  32.62813  9.977205 *
  3) 發動機功率>=134 26  38.78687 13.210420 *
> printcp(rp_Car_Reg2)  #導出迴歸樹的cp表格

Regression tree:
rpart(formula = formula_Car_Reg, data = Train_Car, method = "anova", 
    cp = 0.1)

Variables actually used in tree construction:
[1] 發動機功率

Root node error: 186.17/45 = 4.1372

n= 45 

      CP nsplit rel error  xerror     xstd
1 0.6164      0    1.0000 1.07992 0.189818
2 0.1000      1    0.3836 0.47707 0.078252
> #剪枝函數也可以實現同樣的效果
> rp_Car_Reg3=prune.rpart(rp_Car_Reg,cp=0.1)
> print(rp_Car_Reg3)
n= 45 

node), split, n, deviance, yval
      * denotes terminal node

1) root 45 186.17280 11.845280  
  2) 發動機功率< 134 19  32.62813  9.977205 *
  3) 發動機功率>=134 26  38.78687 13.210420 *
> printcp(rp_Car_Reg3)

Regression tree:
rpart(formula = formula_Car_Reg, data = Train_Car, method = "anova")

Variables actually used in tree construction:
[1] 發動機功率

Root node error: 186.17/45 = 4.1372

n= 45 

      CP nsplit rel error  xerror     xstd
1 0.6164      0    1.0000 1.04401 0.182755
2 0.1000      1    0.3836 0.46353 0.075249
> #對所生成樹的大小也可以通過深度函數maxdepth來控制
> rp_Car_Reg4=rpart(formula_Car_Reg,Train_Car,method="anova",maxdepth=1)
> print(rp_Car_Reg4)
n= 45 

node), split, n, deviance, yval
      * denotes terminal node

1) root 45 186.17280 11.845280  
  2) 發動機功率< 134 19  32.62813  9.977205 *
  3) 發動機功率>=134 26  38.78687 13.210420 *
> printcp(rp_Car_Reg4)

Regression tree:
rpart(formula = formula_Car_Reg, data = Train_Car, method = "anova", 
    maxdepth = 1)

Variables actually used in tree construction:
[1] 發動機功率

Root node error: 186.17/45 = 4.1372

n= 45 

      CP nsplit rel error  xerror     xstd
1 0.6164      0    1.0000 1.09740 0.191557
2 0.0100      1    0.3836 0.52651 0.084732

 


#2. 對油耗變量建立迴歸樹——樹形結果
> rp_Car_Plot=rpart(formula_Car_Reg,Train_Car,method="anova",minsplit=10)
> #設置minsplit爲10,新的迴歸樹記爲rp_Car_Plot
> print(rp_Car_Plot)
n= 45 

node), split, n, deviance, yval
      * denotes terminal node

 1) root 45 186.172800 11.845280  
   2) 發動機功率< 134 19  32.628130  9.977205  
     4) 價格< 9504.5 8   2.649246  8.582424 *
     5) 價格>=9504.5 11   3.096802 10.991590 *
   3) 發動機功率>=134 26  38.786870 13.210420  
     6) 價格< 11522 7   3.003835 11.877100 *
     7) 價格>=11522 19  18.754370 13.701630  
      14) 類型=Compact,Medium 12   2.880021 13.081890 *
      15) 類型=Large,Sporty,Van 7   3.364168 14.764060 *
> library(rpart.plot)
> rpart.plot(rp_Car_Plot)            #繪製決策樹
> #相比於數字結果,從樹狀圖中我們可以更清晰的看到模型對於目標變量的預測過程


> rpart.plot(rp_Car_Plot,type=4)     #更改type參數爲類型4,繪製決策樹
> #每一分支的取值範圍也被標示出來,而且在每個節點的油耗的預測值也被標出
> #當樹的分支較多時,我們還可以選擇設置“分支”參數branch=1來獲得垂直枝幹形狀的決策
> #樹以減少圖形所佔空間,使得樹狀圖的枝幹不再顯得雜亂無章,更方便查看和分析。


> rpart.plot(rp_Car_Plot,type=4,branch=1)
> #參數fallen.leaves設置爲TRUE,即表示將所有葉節點一致的擺放在樹的最下端
> rpart.plot(rp_Car_Plot,type=4,branch=1,fallen.leaves=TRUE)
> library(maptree)
載入需要的程輯包:cluster
> draw.tree(rp_Car_Plot, col=rep(1,8), nodeinfo=TRUE)#利用draw.tree()繪製決策樹


> plot(rp_Car_Plot,uniform=TRUE,main="plot: Regression TRUE")
> text(rp_Car_Plot,use.n=TRUE,all=TRUE)#用plot()直接繪圖,並在圖中添加相關文字信息


> post(rp_Car_Plot,file="") #用post()函數繪製決策樹


> #3. 對分組油耗變量建立分類樹
> formula_Car_Cla=分組油耗~價格+產地+可靠性+類型+車重+發動機功率+淨馬力
> rp_Car_Cla=rpart(formula_Car_Cla, Train_Car, method="class", minsplit=5)
> #按公式formula_Car_Cla對訓練集創建分類樹
> print(rp_Car_Cla)
n= 45 

node), split, n, loss, yval, (yprob)
      * denotes terminal node

 1) root 45 19 A (0.57777778 0.26666667 0.15555556)  
   2) 發動機功率>=134 26  2 A (0.92307692 0.07692308 0.00000000)  
     4) 價格>=11222 20  0 A (1.00000000 0.00000000 0.00000000) *
     5) 價格< 11222 6  2 A (0.66666667 0.33333333 0.00000000)  
      10) 發動機功率< 152 4  0 A (1.00000000 0.00000000 0.00000000) *
      11) 發動機功率>=152 2  0 B (0.00000000 1.00000000 0.00000000) *
   3) 發動機功率< 134 19  9 B (0.10526316 0.52631579 0.36842105)  
     6) 價格>=9504.5 11  2 B (0.18181818 0.81818182 0.00000000) *
     7) 價格< 9504.5 8  1 C (0.00000000 0.12500000 0.87500000) *
> #以上輸出結果與迴歸樹類似,不同之處僅在於每個節點的預測值不再是具體數值,而是A、B、C,即分組油耗的三個取值水平。
> rpart.plot(rp_Car_Cla, type=4, fallen.leaves=TRUE)  #對rp_Car_Cla繪製分類樹

#發動機高於134的且價格高於1萬美元的車,屬於A類,高油耗;最左邊的分支,反之屬於C類,最右側分支。

 

 

#4. 對測試集Test_Car預測目標變量
> pre_Car_Cla=predict(rp_Car_Cla,Test_Car,type="class")
> #對測試集Test_Car中觀測樣本中的分組油耗指標進行預測
> pre_Car_Cla                                                          #顯示預測結果
       Honda Civic 4      Nissan Sentra 4     Mercury Tracer 4     Pontiac LeMans 4 
                   C                    C                    C                    C 
          Ford Probe       Plymouth Laser       Nissan 240SX 4      Acura Legend V6 
                   B                    B                    A                    A 
    Eagle Premier V6     Nissan Maxima V6    Buick Le Sabre V6 Chevrolet Caprice V8 
                   A                    A                    A                    A 
    Ford Aerostar V6         Mazda MPV V6         Nissan Van 4 
                   A                    A                    A 
Levels: A B C
> (p=sum(as.numeric(pre_Car_Cla!=Test_Car$"分組油耗"))/nrow(Test_Car)) #計算錯誤率
[1] 0.1333333
> table(Test_Car$"分組油耗", pre_Car_Cla)                              #獲取混淆矩陣
   pre_Car_Cla
    A B C
  A 9 0 0
  B 0 2 2
  C 0 0 2


 ########################    C4.5應用             ###################


> #C4.5算法僅適用離散變量,即構建分類樹,因此這裏我們就繼續沿用上面的數據集來對分組油
> #耗指標進行建樹。需要說明的是,用於實現C4.5算法的核心函數J48()對中文識別不太完善,因此我們將使用原
> #英文數據集中的變量名稱。#“價格(Price)”、產地(Country)、可靠性(Reliability)、英里數(Mileage)、類型(Type)、
> #車重(Weight)、發動機功率(Disp.),以及淨馬力(HP),分組油耗(Oil_Consumption)。
> #install.packages("rJava")
> library(RWeka)
> names(Train_Car)=c("Price","Country","Reliability","Mileage","Type","Weight","Disp.",
+                    "HP","Oil_Consumption") #更改爲英文變量名
> Train_Car$Oil_Consumption=as.factor(Train_Car$Oil_Consumption)
> #將分組喲好的變量類型改爲因子型,使J48()函數可識別
> formula=Oil_Consumption~Price+Country+Reliability+Type+Weight+Disp.+HP
> C45_0=J48(formula,Train_Car)    #在默認參數去之下,構建分類樹模型C45_0
> C45_0
J48 pruned tree
------------------

Price <= 9410: C (7.0/1.0)
Price > 9410
|   Disp. <= 132: B (6.0)
|   Disp. > 132
|   |   Price <= 10989
|   |   |   Reliability <= 1: B (2.0)
|   |   |   Reliability > 1: A (4.0/1.0)
|   |   Price > 10989: A (17.0)

Number of Leaves  :     5

Size of the tree :     9

> #共計10個葉節點,15個節點,最後括號中的數字表示有多少觀測樣本被歸入該分支,且其中有
> #幾個是被錯分的。
> summary(C45_0)

=== Summary ===

Correctly Classified Instances          34               94.4444 %
Incorrectly Classified Instances         2                5.5556 %
Kappa statistic                          0.9045
Mean absolute error                      0.0595
Root mean squared error                  0.1725
Relative absolute error                 15.067  %
Root relative squared error             39.0042 %
Total Number of Instances               36     

=== Confusion Matrix ===

  a  b  c   <-- classified as
 20  0  0 |  a = A
  1  8  1 |  b = B
  0  0  6 |  c = C
> #下面我們通過control參數控制分類樹的生成過程。參數M,即對每個葉節點設置最小觀測樣本
> #量來對樹進行剪枝。我們知道,M的默認值爲2,現在將其取值爲3來減去若干所含樣本量較小
> #的分支。
> C45_1=J48(formula,Train_Car,control=Weka_control(M=3))
> #取control參數的M值爲3,構建分類樹模型C45_1
> C45_1
J48 pruned tree
------------------

Price <= 9410: C (7.0/1.0)
Price > 9410
|   Disp. <= 132: B (6.0)
|   Disp. > 132: A (23.0/3.0)

Number of Leaves  :     3

Size of the tree :     5

> plot(C45_1)                           #對C45_1繪製分類樹

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章