mydata <- read_excel("C:/Users/Eneja/Desktop/MVA/Electircal_Cars.xlsx")
head(mydata)
## # A tibble: 6 × 9
##   Brand   Model                       `Capacity (kWh)` `Acceleration (sec)` TopSpeed (km…¹ Range…² Effic…³ Drive Price…⁴
##   <chr>   <chr>                                  <dbl>                <dbl>          <dbl>   <dbl>   <dbl> <chr>   <dbl>
## 1 Opel    Ampera-e                                58                    7.3            150     335     173 Fron…   42990
## 2 Renault Kangoo Maxi ZE 33                       31                   22.4            130     160     194 Fron…       0
## 3 Nissan  Leaf                                    36                    7.9            144     220     164 Fron…   29990
## 4 Audi    e-tron Sportback 55 quattro             86.5                  5.7            200     375     231 All …       0
## 5 Porsche Taycan Turbo S                          83.7                  2.8            260     390     215 All …  186336
## 6 Nissan  e-NV200 Evalia                          36                   14              123     165     218 Fron…   43433
## # … with abbreviated variable names ¹​`TopSpeed (km/h)`, ²​`Range (km)`, ³​`Efficiency (Wh/km)`, ⁴​`PriceinGer (€)`
colnames(mydata) <- c("Brand","Model","Capacity","Acceleration","Top_Speed","Range","Efficiency","Drive", "Price_Germany", "ID")
mydata$DriveF <- factor(mydata$Drive,
                              levels = c( "Front Wheel Drive", "Rear Wheel Drive", "All Wheel Drive"),
                              labels = c("Front","Rear","All"))
mydata1 <- mydata %>%
  replace_with_na(replace = list(Price_Germany = c(0)))
mydata1 <- tidyr::drop_na(mydata1)
mydata1$ID <- 1:nrow(mydata1)
mydata1 <- mydata1[c(11, 3, 4, 6, 7, 8 ,9 , 10)]
mydata1 <- mydata1[c(-17, -55), ]

Description: -Brand: Vehicle manufacturer -Model: Vehicle model -Capacity: Battery storage capacity in kWh -Acceleration: time to reach 100 km/h in seconds -Top_Speed: Maximum travel speed in km/h -Range: Nominal value of maximal traveled distance in km -Efficiency: Ratio between capacity and range in Wh/km -Drive: type of drive front/rear/all wheel drive -Price_Germany: Estimated value of vehicle in Germany in € in 2022

Unit of observation: BEV car model in Germany in 2022

Sample size: 168 (without missing values)

Source of the data: Kaggle- Cheapest Electric Cars

The main goal of the data analysis is to see if there is a possibility to cluster 168 BEV var vehicles based on 4 characteristics and find out the differences.

psych::describeBy(mydata1[c(-1,-6,-8)])
## Warning in psych::describeBy(mydata1[c(-1, -6, -8)]): no grouping variable requested
##               vars   n     mean       sd  median  trimmed      mad     min    max    range  skew kurtosis      se
## Capacity         1 166    63.95    20.57    66.5    64.21    24.98    16.7    120    103.3 -0.08    -0.46    1.60
## Acceleration     2 166     7.84     2.98     7.5     7.75     3.04     2.1     15     12.9  0.31    -0.75    0.23
## Range            3 166   332.11   105.95   340.0   329.63   103.78    95.0    660    565.0  0.24     0.19    8.22
## Efficiency       4 166   194.26    32.73   188.5   191.90    30.39   104.0    281    177.0  0.61    -0.27    2.54
## Price_Germany    5 166 57685.93 30544.33 50207.5 52512.41 18558.45 18460.0 187746 169286.0  1.92     4.17 2370.70
mydata1$Price_z <- scale(mydata1$Price_Germany)
mydata1$Range_z   <- scale(mydata1$Range)
mydata1$Acceleration_z <- scale(mydata1$Acceleration)
mydata1$Capacity_z <- scale(mydata1$Capacity)
library(Hmisc)
rcorr(as.matrix(mydata1[, c("Price_z", "Range_z", "Acceleration_z", "Capacity_z")]), 
      type = "pearson")
##                Price_z Range_z Acceleration_z Capacity_z
## Price_z           1.00    0.57          -0.55       0.64
## Range_z           0.57    1.00          -0.73       0.86
## Acceleration_z   -0.55   -0.73           1.00      -0.64
## Capacity_z        0.64    0.86          -0.64       1.00
## 
## n= 166 
## 
## 
## P
##                Price_z Range_z Acceleration_z Capacity_z
## Price_z                 0       0              0        
## Range_z         0               0              0        
## Acceleration_z  0       0                      0        
## Capacity_z      0       0       0

There are high correlations, however we will still continue with analysis. We will now calculate dissimilarities.

mydata1$Dissimilarity <- sqrt(mydata1$Price_z^2 +mydata1$Range_z^2 + mydata1$Acceleration_z^2 + mydata1$Capacity_z^2) #Finding outliers
head(mydata1[order(-mydata1$Dissimilarity), ], 10) 
## # A tibble: 10 × 13
##       ID Capacity Acceleration Range Efficiency Drive            Price_…¹ DriveF Price…² Range…³ Accel…⁴ Capac…⁵ Dissi…⁶
##    <int>    <dbl>        <dbl> <dbl>      <dbl> <chr>               <dbl> <fct>    <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
##  1    83    110            3.2   660        167 All Wheel Drive    140000 All       2.69   3.09   -1.56    2.24     4.93
##  2   126     83.7          2.9   380        220 All Wheel Drive    187746 All       4.26   0.452  -1.66    0.960    4.69
##  3     3     83.7          2.8   390        215 All Wheel Drive    186336 All       4.21   0.546  -1.69    0.960    4.67
##  4   140    108.           4.3   610        177 All Wheel Drive    135529 All       2.55   2.62   -1.19    2.13     4.40
##  5   139    108.           6.2   640        168 Rear Wheel Drive   106374 Rear      1.59   2.91   -0.551   2.13     3.98
##  6    16     60           10     575        104 All Wheel Drive    149000 All       2.99   2.29    0.726  -0.192    3.84
##  7    38     16.7         12.7    95        176 Rear Wheel Drive    19120 Rear     -1.26  -2.24    1.63   -2.30     3.81
##  8   115     90            2.1   535        168 All Wheel Drive    126990 All       2.27   1.91   -1.93    1.27     3.76
##  9   125     83.7          3.3   385        217 All Wheel Drive    154444 All       3.17   0.499  -1.53    0.960    3.68
## 10    37     16.7         11.9    95        176 Rear Wheel Drive    21720 Rear     -1.18  -2.24    1.36   -2.30     3.68
## # … with abbreviated variable names ¹​Price_Germany, ²​Price_z[,1], ³​Range_z[,1], ⁴​Acceleration_z[,1], ⁵​Capacity_z[,1],
## #   ⁶​Dissimilarity[,1]
library(factoextra)
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
#Calculating Euclidean distances
distance <- get_dist(mydata1[c("Price_z", "Range_z", "Acceleration_z", "Capacity_z")], 
                     method = "euclidian")

distance2 <- distance^2

fviz_dist(distance2) 

get_clust_tendency(mydata1[c("Price_z", "Range_z", "Acceleration_z", "Capacity_z")], #Hopkins statistics
                   n = nrow(mydata1) - 1, 
                   graph = FALSE)
## $hopkins_stat
## [1] 0.8700753
## 
## $plot
## NULL

Based on Graph above and Hopkins statistics the data is clusterable. Hopkins statistics need to be bigger than 0.5.

library(dplyr)

WARD <- mydata1[c("Price_z", "Range_z", "Acceleration_z", "Capacity_z")] %>%
  get_dist(method = "euclidean") %>%  
  hclust(method = "ward.D2") 

WARD
## 
## Call:
## hclust(d = ., method = "ward.D2")
## 
## Cluster method   : ward.D2 
## Distance         : euclidean 
## Number of objects: 166
library(factoextra)
fviz_dend(WARD)
## Warning: The `<scale>` argument of `guides()` cannot be `FALSE`. Use "none" instead as of ggplot2 3.3.4.
## ℹ The deprecated feature was likely used in the factoextra package.
##   Please report the issue at <]8;;https://github.com/kassambara/factoextra/issueshttps://github.com/kassambara/factoextra/issues]8;;>.

library(NbClust)
OptNumber <- mydata1[c("Price_z", "Range_z", "Acceleration_z", "Capacity_z")] %>%
  NbClust(distance = "euclidean",
          min.nc = 2, max.nc = 10, 
          method = "ward.D2", 
          index = "all") 

## *** : The Hubert index is a graphical method of determining the number of clusters.
##                 In the plot of Hubert index, we seek a significant knee that corresponds to a 
##                 significant increase of the value of the measure i.e the significant peak in Hubert
##                 index second differences plot. 
## 

## *** : The D index is a graphical method of determining the number of clusters. 
##                 In the plot of D index, we seek a significant knee (the significant peak in Dindex
##                 second differences plot) that corresponds to a significant increase of the value of
##                 the measure. 
##  
## ******************************************************************* 
## * Among all indices:                                                
## * 2 proposed 2 as the best number of clusters 
## * 11 proposed 3 as the best number of clusters 
## * 4 proposed 4 as the best number of clusters 
## * 2 proposed 6 as the best number of clusters 
## * 2 proposed 8 as the best number of clusters 
## * 2 proposed 10 as the best number of clusters 
## 
##                    ***** Conclusion *****                            
##  
## * According to the majority rule, the best number of clusters is  3 
##  
##  
## *******************************************************************

Based on the statistics and dendrogram we will use 3 clusters. Even majority of rules suggest that the best number of clusters is 3.

mydata1$ClusterWard <- cutree(WARD,  #Assigning each unit to a specific cluster
                             k = 3) 

head(mydata1[c("ID", "ClusterWard")])
## # A tibble: 6 × 2
##      ID ClusterWard
##   <int>       <int>
## 1     1           1
## 2     2           2
## 3     3           3
## 4     4           2
## 5     5           2
## 6     6           1
Leaders_initial <- aggregate(mydata1, 
                             by = list(mydata1$ClusterWard), 
                             FUN = mean)
## Warning in mean.default(X[[i]], ...): argument is not numeric or logical: returning NA

## Warning in mean.default(X[[i]], ...): argument is not numeric or logical: returning NA

## Warning in mean.default(X[[i]], ...): argument is not numeric or logical: returning NA

## Warning in mean.default(X[[i]], ...): argument is not numeric or logical: returning NA

## Warning in mean.default(X[[i]], ...): argument is not numeric or logical: returning NA

## Warning in mean.default(X[[i]], ...): argument is not numeric or logical: returning NA
Leaders_initial
##   Group.1       ID Capacity Acceleration    Range Efficiency Drive Price_Germany DriveF    Price_z    Range_z
## 1       1 93.70886 70.08608     6.943038 371.0127   188.9241    NA      49475.85     NA -0.2687925  0.3671971
## 2       2 71.75862 44.38793    10.800000 220.2586   200.0517    NA      41541.93     NA -0.5285434 -1.0556932
## 3       3 88.24138 86.34828     4.365517 449.8276   197.2069    NA     112339.34     NA  1.7893146  1.1110909
##   Acceleration_z Capacity_z Dissimilarity ClusterWard
## 1     -0.3014927  0.2984124      1.092086           1
## 2      0.9944122 -0.9509834      2.106822           2
## 3     -1.1675167  1.0890502      2.855088           3

K-means (non-hierarhical)

library(factoextra)

K_MEANS <- hkmeans(mydata1[c("Price_z", "Range_z", "Acceleration_z", "Capacity_z")], 
                   k = 3,
                   hc.metric = "euclidean",
                   hc.method = "ward.D2")

K_MEANS
## Hierarchical K-means clustering with 3 clusters of sizes 79, 63, 24
## 
## Cluster means:
##      Price_z    Range_z Acceleration_z Capacity_z
## 1 -0.1502435  0.4179738     -0.3788982  0.4317738
## 2 -0.5755210 -1.0071964      0.9512133 -0.9810338
## 3  2.0052940  1.2680601     -1.2497283  1.1539582
## 
## Clustering vector:
##   [1] 1 2 3 2 2 1 1 2 1 2 2 2 3 2 2 3 2 2 2 2 1 1 3 2 2 2 2 1 1 1 2 2 1 3 2 2 2 2 1 1 2 3 3 2 1 2 1 1 1 1 1 1 1 2 1 1 2
##  [58] 3 3 1 2 2 1 1 1 1 2 2 1 1 2 2 1 1 1 1 1 1 1 1 3 3 3 2 1 1 2 2 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 3 3 3 3
## [115] 3 2 2 3 1 2 1 3 3 3 1 1 3 1 1 1 1 1 1 1 1 1 3 3 1 1 1 1 2 1 1 1 1 1 1 1 2 2 2 2 1 1 1 1 1 1 2 2 1 1 1 1
## 
## Within cluster sum of squares by cluster:
## [1] 77.78146 79.07623 54.53236
##  (between_SS / total_SS =  68.0 %)
## 
## Available components:
## 
##  [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss" "betweenss"    "size"        
##  [8] "iter"         "ifault"       "data"         "hclust"

The ratio of variability between sum of squares vs total sum squares is in our case 68%. The higher the ratio, the more separated the groups.

fviz_cluster(K_MEANS, 
             palette = "set1", 
             repel = FALSE,
             ggtheme = theme_classic())

We had two outliers ID 17 and 55 in group 3- we removed them.

mydata1$ClusterK_Means <- K_MEANS$cluster
table(mydata1$ClusterWard)
## 
##  1  2  3 
## 79 58 29
table(mydata1$ClusterK_Means)
## 
##  1  2  3 
## 79 63 24
table(mydata1$ClusterWard, mydata1$ClusterK_Means)
##    
##      1  2  3
##   1 72  7  0
##   2  2 56  0
##   3  5  0 24

From the table1 above we can see what was the initial sample size in all three groups. Second table presents sample sizes after reclassification. Last table shows how the units were classified. From Group1 72 units stayed in the same group, 7 were reclassified to group 2. 56 units stayed in group 2 and 2 were classified in group 1. Lastly, 24 units stayed in group 3 and 5 were reclassified to group 1.

Centroids <- K_MEANS$centers
Centroids
##      Price_z    Range_z Acceleration_z Capacity_z
## 1 -0.1502435  0.4179738     -0.3788982  0.4317738
## 2 -0.5755210 -1.0071964      0.9512133 -0.9810338
## 3  2.0052940  1.2680601     -1.2497283  1.1539582
library(ggplot2)
library(tidyr)

Figure <- as.data.frame(Centroids)
Figure$id <- 1:nrow(Figure)
Figure <- pivot_longer(Figure, cols = c(Price_z, Range_z, Acceleration_z, Capacity_z))

Figure$Groups <- factor(Figure$id, 
                        levels = c(1, 2, 3), 
                        labels = c("1", "2", "3"))

Figure$nameFactor <- factor(Figure$name, 
                            levels = c("Price_z", "Range_z", "Acceleration_z", "Capacity_z"), 
                            labels = c("Price_z", "Range_z", "Acceleration_z", "Capacity_z"))

ggplot(Figure, aes(x = nameFactor, y = value)) +
  geom_hline(yintercept = 0) +
  theme_bw() +
  geom_point(aes(shape = Groups, col = Groups), size = 3) +
  geom_line(aes(group = id), linewidth = 1) +
  ylab("Averages") +
  xlab("Cluster variables")+
  ylim(-3,3)

From the chart above we can see that Group 2 presents BEV vehicles which have a price, available range and capacity below average. However, they have acceleration above average. Reasons for that is usually being that vehicles like that have smaller battery, which makes the vehicle much lighter in weight and also cheaper. We can also see that Group 1 is the closest to average value for all variables researched compared to other two group. Group 1 is below average in price and acceleration and above average in range and capacity. Group 3 has above average values for all variables researched, except acceleration.

Anova

fit <- aov(cbind(Price_z, Range_z, Acceleration_z, Capacity_z) ~ as.factor(ClusterK_Means), data = mydata1)
summary(fit)
##  Response 1 :
##                            Df  Sum Sq Mean Sq F value    Pr(>F)    
## as.factor(ClusterK_Means)   2 119.159  59.580  211.85 < 2.2e-16 ***
## Residuals                 163  45.841   0.281                      
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
##  Response 2 :
##                            Df  Sum Sq Mean Sq F value    Pr(>F)    
## as.factor(ClusterK_Means)   2 116.303  58.151  194.65 < 2.2e-16 ***
## Residuals                 163  48.697   0.299                      
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
##  Response 3 :
##                            Df  Sum Sq Mean Sq F value    Pr(>F)    
## as.factor(ClusterK_Means)   2 105.828  52.914  145.76 < 2.2e-16 ***
## Residuals                 163  59.172   0.363                      
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
##  Response 4 :
##                            Df Sum Sq Mean Sq F value    Pr(>F)    
## as.factor(ClusterK_Means)   2 107.32  53.660  151.64 < 2.2e-16 ***
## Residuals                 163  57.68   0.354                      
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

H0: µ(var, cluster1) = µ(var, cluster2) = µ(var, cluster3) H1: At least one µ is different

Based on Anova test we can reject null hypothesis at p < 0.001. Therefore, it means the mean values differ between all 3 clusters. Data is also standardized, which makes it comparable based on F-statistics. We found that Price seems to be the most successful with separating groups.

VALIDATION

Once we validated that clustering variables were well chosen, we use new variables to validate chosen variables and explain them even further.

aggregate(mydata1$Efficiency,
          by = list(mydata1$ClusterK_Means),
          FUN = "mean")
##   Group.1        x
## 1       1 194.1519
## 2       2 194.8095
## 3       3 193.1667
fit1 <- aov(Efficiency ~ as.factor(ClusterK_Means),
            data= mydata1)
summary(fit1)
##                            Df Sum Sq Mean Sq F value Pr(>F)
## as.factor(ClusterK_Means)   2     49    24.3   0.022  0.978
## Residuals                 163 176705  1084.1

Ho: µ(Group 1, Price) = µ(Group 2, Price) = µ(Group 3, Price) H1: µ(Group 1, Price) ≠ µ(Group 2, Price) ≠ µ(Group 3, Price)

Based on p-value we cannot reject null hypothesis. We need to find at least one variable that separates the groups.

#Categorical Variable

chi_square <- chisq.test(mydata1$DriveF, as.factor(mydata1$ClusterK_Means))
chi_square
## 
##  Pearson's Chi-squared test
## 
## data:  mydata1$DriveF and as.factor(mydata1$ClusterK_Means)
## X-squared = 86.622, df = 4, p-value < 2.2e-16
addmargins(chi_square$observed)
##               
## mydata1$DriveF   1   2   3 Sum
##          Front  16  48   0  64
##          Rear   26  15   3  44
##          All    37   0  21  58
##          Sum    79  63  24 166
addmargins(round(chi_square$expected, 2)) 
##               
## mydata1$DriveF     1     2     3 Sum
##          Front 30.46 24.29  9.25  64
##          Rear  20.94 16.70  6.36  44
##          All   27.60 22.01  8.39  58
##          Sum   79.00 63.00 24.00 166
round(chi_square$res, 2) 
##               
## mydata1$DriveF     1     2     3
##          Front -2.62  4.81 -3.04
##          Rear   1.11 -0.42 -1.33
##          All    1.79 -4.69  4.36
library(effectsize)
## 
## Attaching package: 'effectsize'
## The following object is masked from 'package:psych':
## 
##     phi
effectsize::cramers_v(mydata1$DriveF, mydata1$ClusterK_Means)
## Cramer's V (adj.) |       95% CI
## --------------------------------
## 0.50              | [0.40, 1.00]
## 
## - One-sided CIs: upper bound fixed at [1.00].

Ho: There is no association between clusters of the categorical variable H1: There is association between clusters of the categorical variable

Based on p-value (p < 0.001) we can reject null hypothesis. We found a new variable and we found differences in drive type, there is a relationship. With standardized residuals we can check the direction of the relationship. In group 1 we can see that there is less than expected amount of Front wheel drive type BEV vehicles (alpha = 0.01). We can also see that there is more than expected amount of Rear and All wheel drive type, however based on the value we cannot say it is statistically different from 0 (less than 1.96). In group 2 we can see that there is more than expected amount of Front wheel drive type BEV vehicles (alpha = 0.001) and less than expected amount of All wheel drive type BEV vehicles (alpha = 0.001). We can also see that there is less than expected amount of Rear wheel drive type, however based on the value we cannot say it is statistically different from 0 (less than 1.96). In group 3 we can see that there is We can also see that there is less than expected amount of Rear wheel drive type, however based on the value we cannot say it is statistically different from 0 (less than 1.96).

Conclusion

Classification of 166 BEV vehicles was based on four standardized variables (Price, Range, Acceleration, Capacity).

For hierarchical clustering, Ward’s algorithm was used and based on the analysis of the dendrogram and the indices analyzing the increase in heterogeneity, it was decised to classify BEV vehicles in 3 groups. The classification was further optimized using the K-means Clustering.

Group 1 contains the most BEV vehicles (47,6%), characterized as being closest to the average in all 4 variables compared to other groups. Nevertheless, Group 1 is below average in price and acceleration and above average in range and capacity. There is also less than expected amount of Front wheel drive type BEV vehicles (alpha = 0.01).

Group 2 is the second biggest group of BEV vehicles (37,9%), characterized as below average in price, available range and capacity. However, they have acceleration above average. There is also more than expected amount of Front wheel drive type BEV vehicles (alpha = 0.001) and less than expected amount of All wheel drive type BEV vehicles (alpha = 0.001). BEV vehicles in this group have the best efficiency on average.

Group 3 is the smallest group of BEV vehicles (14,5%) characterized as above average in values for all variables researched, except acceleration.There is also less than expected amount of Front wheel drive type BEV vehicles (alpha = 0.01) and more than expected amount of All wheel drive type BEV vehicles (alpha = 0.001). BEV vehicles in this group have the worst efficiency on average.