This analysis focuses on the relationship between price and quality information of diamonds. The main objective is to create a model which predicts the price of a given diamond. The diamonds dataset is included in the ggplot2 package. Therefore it is necessary to load it.
Let’s take a quick view of the variables of the diamonds dataset. There are 53940 observations and 10 variables. I also stored the original data in raw_diamonds.
# In case I need the original data
raw_diamonds <- diamonds
# Check the variables and types
str(diamonds)
## tibble [53,940 × 10] (S3: tbl_df/tbl/data.frame)
## $ carat : num [1:53940] 0.23 0.21 0.23 0.29 0.31 0.24 0.24 0.26 0.22 0.23 ...
## $ cut : Ord.factor w/ 5 levels "Fair"<"Good"<..: 5 4 2 4 2 3 3 3 1 3 ...
## $ color : Ord.factor w/ 7 levels "D"<"E"<"F"<"G"<..: 2 2 2 6 7 7 6 5 2 5 ...
## $ clarity: Ord.factor w/ 8 levels "I1"<"SI2"<"SI1"<..: 2 3 5 4 2 6 7 3 4 5 ...
## $ depth : num [1:53940] 61.5 59.8 56.9 62.4 63.3 62.8 62.3 61.9 65.1 59.4 ...
## $ table : num [1:53940] 55 61 65 58 58 57 57 55 61 61 ...
## $ price : int [1:53940] 326 326 327 334 335 336 336 337 337 338 ...
## $ x : num [1:53940] 3.95 3.89 4.05 4.2 4.34 3.94 3.95 4.07 3.87 4 ...
## $ y : num [1:53940] 3.98 3.84 4.07 4.23 4.35 3.96 3.98 4.11 3.78 4.05 ...
## $ z : num [1:53940] 2.43 2.31 2.31 2.63 2.75 2.48 2.47 2.53 2.49 2.39 ...
The variables cut, color and clarity are ordinal categorical variables, which means they are categorical but they can be ordered as factors. Therefore their levels are written from worst to best.
Figure below gives some information on carat, cut, color and clarity.
url <- "https://github.com/pjournal/boun01-ilaydacelenk/raw/gh-pages/Assignment3/cut-color-clarity-carat.png"
Let’s see some statistics about the dataset diamonds. For each numerical attribute we can compare median and mean. If they are very far away from each other, then they cannot be normally distributed. It is better to have them close. For the categorical attributes, we can see how many diamonds are in each category. We also would want them to close to each other, in order to have a realiable model.
# Median/mean for numerical, count for categorical attributes
summary(diamonds)
## carat cut color clarity depth
## Min. :0.2000 Fair : 1610 D: 6775 SI1 :13065 Min. :43.00
## 1st Qu.:0.4000 Good : 4906 E: 9797 VS2 :12258 1st Qu.:61.00
## Median :0.7000 Very Good:12082 F: 9542 SI2 : 9194 Median :61.80
## Mean :0.7979 Premium :13791 G:11292 VS1 : 8171 Mean :61.75
## 3rd Qu.:1.0400 Ideal :21551 H: 8304 VVS2 : 5066 3rd Qu.:62.50
## Max. :5.0100 I: 5422 VVS1 : 3655 Max. :79.00
## J: 2808 (Other): 2531
## table price x y
## Min. :43.00 Min. : 326 Min. : 0.000 Min. : 0.000
## 1st Qu.:56.00 1st Qu.: 950 1st Qu.: 4.710 1st Qu.: 4.720
## Median :57.00 Median : 2401 Median : 5.700 Median : 5.710
## Mean :57.46 Mean : 3933 Mean : 5.731 Mean : 5.735
## 3rd Qu.:59.00 3rd Qu.: 5324 3rd Qu.: 6.540 3rd Qu.: 6.540
## Max. :95.00 Max. :18823 Max. :10.740 Max. :58.900
##
## z
## Min. : 0.000
## 1st Qu.: 2.910
## Median : 3.530
## Mean : 3.539
## 3rd Qu.: 4.040
## Max. :31.800
##
The following plot, shows linear relationships between price and numerical attributes.
# Linearity
diamonds %>% gather(-price, -cut, -color, -clarity, key = "var", value = "value") %>%
ggplot(aes(x = value, y = price)) + geom_point() + geom_smooth(method='lm', formula= y~x) + facet_wrap(~ var, scales = "free") + theme_bw()
The following plot, shows linear relationships between log(price) and numerical attributes.
# Linearity
diamonds %>% gather(-price, -cut, -color, -clarity, key = "var", value = "value") %>%
ggplot(aes(x = value, y = log(price))) + geom_point() + geom_smooth(method='lm', formula= y~x) + facet_wrap(~ var, scales = "free") + theme_bw()
This is the correlation matrix of numerical variables with significance levels according to the Pearson method. Since our objective is predicting the price, we focus on the relationship with price. For example, from the heatmap we can say that there is a significant relationship between price and carat, but price does not have a significant relationship with table and depth, therefore they are crossed on the map. Carat, x, y and z variables are highly correlated. This was expected since carat is the weight of the diamond and it has a positive relationship with volume which is described by x, y and z, which are length, width and depth. Above 0.8 means high correlation and below -0.8 means high anti-correlation.
# Select numerical attributes
diamonds_numerical <- diamonds %>% select(-cut, -color, -clarity)
# Plot correlation matrix for numerical attributes
corr <- round(cor(diamonds_numerical), 1) #correlation matrix
p_values_matrix <- cor_pmat(corr) #p-values to observe the significance of the variables
ggcorrplot(corr, hc.order = TRUE, lab = TRUE, p.mat = p_values_matrix)
The figure below shows that distribution of prices is right skewed. Since there are a lot of large values, we can take a look at logarithm of th price column. When we do that, we observe log(price) is more suitable since it is closer to being normal. Therefore, replacing price column with log(price) makes sense for the regression model.
# Right skewed
ggplot(diamonds, aes(x=price)) + geom_histogram(binwidth=500)
# More normal
ggplot(diamonds, aes(x=log(price))) + geom_histogram()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
# Use log(price) instead of price
diamonds$log.price = log(diamonds$price)
diamonds <- diamonds %>% select(-price)
# Count the duplicates
table(diamonds$cut)
##
## Fair Good Very Good Premium Ideal
## 1610 4906 12082 13791 21551
# Get proportions
plot_cut <- diamonds %>% group_by(cut) %>% summarise(Count = n()) %>% mutate(Proportion=round(Count/sum(Count),1))
Out of 53940 different diamonds in diamonds dataset, 21551 are classifies as Ideal, which corresponds to 0.3995365.
# Show proportions on a pie plot
ggplot(plot_cut, aes(x = "", y = Proportion, fill = cut)) + geom_bar(stat="identity", width=1) + coord_polar("y", start=0) + geom_text(aes(label = paste0(round(Proportion*100), "%")), position = position_stack(vjust = 0.5)) + labs(x = NULL, y = NULL, fill = NULL, title = "Proportion of Cut Types") + theme_classic() + theme(axis.line = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank(),
plot.title = element_text(hjust = 0.5))
Even though proportions of cut types are not close to each other, using log(price) instead of price is more preferable since price has normal distribution in each category. Using box plot, we see the distribution of log(price) for different cut types. The plot below shows the importance of cut types. If we plotted price instead of log(price) then the distribution would be very skewed.
diamonds %>% ggplot(aes(x = cut, y = log.price, fill=cut)) + geom_boxplot() +
xlab("Cut Types") + ylab("log(price)") + ggtitle("Cut vs log(price)")
Since we are trying to predict labeled column values, this is a supervised learning.
Sometimes machine learning models may memorize instead of learning. This is called overfitting. In order to understand such situation, we split the data into test and train data sets. We fit the model using train, predict and evaluate accuracy using test data. If the train prediction accuracy is much higher from test prediction accuracy then overfitting may have occurred and we may want to change the parameters of the model or use a different model.
For train data, we take a sample of 80% of the data without replacement and the rest 20% will be the test data.
# Split
train_indices <- sample(seq_len(nrow(diamonds)), size = 0.8*nrow(diamonds), replace = FALSE)
train <- diamonds[train_indices, ]
test <- diamonds[-train_indices, ]
According to p-values for t-tests and F-test, all of the attributes except have significant effect on log(price).
linear_model1<-lm(log.price ~ ., data=train)
summary(linear_model1)
##
## Call:
## lm(formula = log.price ~ ., data = train)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.2638 -0.0911 0.0015 0.0902 9.0693
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -2.5579352 0.0685251 -37.328 < 2e-16 ***
## carat -0.6093868 0.0084957 -71.729 < 2e-16 ***
## cut.L 0.1068633 0.0039515 27.044 < 2e-16 ***
## cut.Q -0.0383897 0.0031574 -12.158 < 2e-16 ***
## cut.C 0.0382466 0.0027078 14.125 < 2e-16 ***
## cut^4 0.0116628 0.0021604 5.399 6.75e-08 ***
## color.L -0.4535165 0.0030227 -150.039 < 2e-16 ***
## color.Q -0.1041008 0.0027459 -37.911 < 2e-16 ***
## color.C -0.0119221 0.0025639 -4.650 3.33e-06 ***
## color^4 0.0170633 0.0023579 7.237 4.68e-13 ***
## color^5 -0.0082932 0.0022247 -3.728 0.000193 ***
## color^6 0.0009673 0.0020218 0.478 0.632350
## clarity.L 0.8912203 0.0052705 169.096 < 2e-16 ***
## clarity.Q -0.2586442 0.0049184 -52.587 < 2e-16 ***
## clarity.C 0.1440099 0.0042061 34.238 < 2e-16 ***
## clarity^4 -0.0673837 0.0033572 -20.071 < 2e-16 ***
## clarity^5 0.0274296 0.0027393 10.013 < 2e-16 ***
## clarity^6 -0.0053161 0.0023854 -2.229 0.025842 *
## clarity^7 0.0284388 0.0021078 13.492 < 2e-16 ***
## depth 0.0524856 0.0007773 67.524 < 2e-16 ***
## table 0.0088021 0.0005084 17.315 < 2e-16 ***
## x 1.1697516 0.0053947 216.832 < 2e-16 ***
## y 0.0292519 0.0030215 9.681 < 2e-16 ***
## z 0.0378274 0.0052685 7.180 7.09e-13 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.176 on 43128 degrees of freedom
## Multiple R-squared: 0.97, Adjusted R-squared: 0.97
## F-statistic: 6.056e+04 on 23 and 43128 DF, p-value: < 2.2e-16
# Remove the one with highest p-value
linear_model2<-lm(log.price ~ . -color^6, data=train)
summary(linear_model2)
##
## Call:
## lm(formula = log.price ~ . - color^6, data = train)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.2822 -0.1232 0.0088 0.1281 9.3911
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -2.2674415 0.0854524 -26.535 < 2e-16 ***
## carat -0.7837840 0.0104911 -74.709 < 2e-16 ***
## cut.L 0.1000367 0.0049284 20.298 < 2e-16 ***
## cut.Q -0.0365415 0.0039388 -9.277 < 2e-16 ***
## cut.C 0.0416451 0.0033762 12.335 < 2e-16 ***
## cut^4 0.0136627 0.0026950 5.070 4.00e-07 ***
## clarity.L 0.8346509 0.0065322 127.774 < 2e-16 ***
## clarity.Q -0.2557648 0.0061263 -41.749 < 2e-16 ***
## clarity.C 0.1409132 0.0052450 26.866 < 2e-16 ***
## clarity^4 -0.0846360 0.0041839 -20.229 < 2e-16 ***
## clarity^5 0.0353468 0.0034149 10.351 < 2e-16 ***
## clarity^6 0.0006886 0.0029752 0.231 0.817
## clarity^7 0.0484121 0.0026244 18.447 < 2e-16 ***
## depth 0.0483248 0.0009689 49.877 < 2e-16 ***
## table 0.0082405 0.0006340 12.997 < 2e-16 ***
## x 1.1987336 0.0067195 178.397 < 2e-16 ***
## y 0.0303250 0.0037696 8.045 8.87e-16 ***
## z 0.0405558 0.0065725 6.171 6.87e-10 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.2196 on 43134 degrees of freedom
## Multiple R-squared: 0.9532, Adjusted R-squared: 0.9532
## F-statistic: 5.173e+04 on 17 and 43134 DF, p-value: < 2.2e-16
# Now all attributes are significant
linear_model3<-lm(log.price ~ . -color^6 -clarity^6, data=train)
summary(linear_model3)
##
## Call:
## lm(formula = log.price ~ . - color^6 - clarity^6, data = train)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2.1031 -0.1668 -0.0031 0.1603 8.8468
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.9506774 0.1066354 -8.915 < 2e-16 ***
## carat -0.7022745 0.0130724 -53.722 < 2e-16 ***
## cut.L 0.1830228 0.0061224 29.894 < 2e-16 ***
## cut.Q -0.0620637 0.0048934 -12.683 < 2e-16 ***
## cut.C 0.0719969 0.0042204 17.059 < 2e-16 ***
## cut^4 0.0289130 0.0033786 8.558 < 2e-16 ***
## depth 0.0370300 0.0012116 30.562 < 2e-16 ***
## table 0.0049572 0.0007955 6.231 4.67e-10 ***
## x 1.0966612 0.0083785 130.891 < 2e-16 ***
## y 0.0336991 0.0047332 7.120 1.10e-12 ***
## z 0.0550543 0.0082517 6.672 2.56e-11 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.2758 on 43141 degrees of freedom
## Multiple R-squared: 0.9263, Adjusted R-squared: 0.9262
## F-statistic: 5.419e+04 on 10 and 43141 DF, p-value: < 2.2e-16
Let’s get the predicted log(price) values for the test set in order to compute residuals.
predict_test = predict(object=linear_model1, newdata=test)
We can compare different models by their mean squared error(MSE). The model’s MSE is 0.0302056.
mse1 <- mean((predict_test-test$log.price)^2)
mse1
## [1] 0.03020564
diamonds dataset works better for log(price) instead of price.
To see all my work related to IE 48A course, visit my Progress Journal website. You can also visit my personal Github Website.