21 min read

Metallurgical compositional analysis

https://doi.org/10.1016/j.dib.2018.11.111

divide01 <- function(x){
  return(x/100)
}

data <- read.csv("hardness.csv") %>% 
  mutate(Al=divide01(Al),
         Co=divide01(Co),
         Cr=divide01(Cr),
         Cu=divide01(Cu),
         Fe=divide01(Fe),
         Ni=divide01(Ni))
data_ordered <- read.csv("hardness.csv") %>% 
  mutate(Al=divide01(Al),
         Co=divide01(Co),
         Cr=divide01(Cr),
         Cu=divide01(Cu),
         Fe=divide01(Fe),
         Ni=divide01(Ni)) %>% 
  arrange(desc(hardness))

closed <- clo(data, parts = 1:6,total=100)
cx <- acomp(data, c("Al","Co","Cr"))
plot(cx)

xc <- acomp(data, c("Al","Co","Cr","Cu","Fe","Ni"))
xc_ordered <- acomp(data_ordered, c("Al","Co","Cr","Cu","Fe","Ni"))
plot(xc)

barplot.acomp(xc)

Order of hardness desc

barplot.acomp(xc_ordered)

Possibly drop copper and nickel

library(httr)
raw_dd <- read.table(text=content(GET("https://gist.githubusercontent.com/MrFlick/c1183c911bc5398105d4/raw/715868fba2d0d17a61a8081de17c468bbc525ab1/elements.txt")), sep=",", header=TRUE) %>%
  mutate(ElementName=ifelse(ElementName=="Sulfer","Sulfur",ElementName),
         ElementName=ifelse(ElementName=="Phosphorous","Phosphorus",ElementName),
         ElementName=ifelse(ElementName=="Flourine","Fluorine",ElementName))
data <- read_xlsx("melting_points.xlsx", sheet = 'Sheet2')
colnames(raw_dd) <- c(colnames(raw_dd)[1:7],"element",colnames(raw_dd)[9:13])
dd <- raw_dd %>%
  left_join(data, by = "element") %>%
  mutate(CTE_ppm=CTE*1000000,
         CTE_ppm=as.numeric(CTE_ppm),
         scaled_cte=CTE_ppm,
         scaled_cte=ifelse(scaled_cte>8,8,scaled_cte),
         scaled_cte=ifelse(scaled_cte<4.5,4.4,scaled_cte),
         new_structure=ifelse(crystal_structure %in% c("Body-centered Cubic","Face-centered Cubic","Simple Hexagonal"),crystal_structure,NA))
dd %>%
  ggplot(aes(Column,-Row)) +
  geom_tile(aes(fill=crystal_structure), color="black") +
  geom_text(aes(Column, -Row,label= symbol))+
  theme_void()+
  labs(
    fill = "Crystal Structure"
  ) +
  annotate("text", x = 7, y = 0.2, label = "Periodic Table of the elements", size = 5)

(p<-dd %>%
  filter(!is.na(symbol)) %>%
  #filter(!crystal_structure %in% c("Base-centered Monoclinic","Centered Tetragonal")) %>%
  ggplot(aes(Column, -Row))+
  geom_tile(aes(fill=scaled_cte), color="black",size=1) +
  geom_rect(aes(xmin=Column-0.3,xmax=Column+0.3,ymin=-Row-0.2,ymax=-Row+0.3,colour=new_structure),size=1,lty=1,fill="black")+
    #geom_text(aes(label="",colour=new_structure), na.rm = T,size=12,nudge_y = 0.3)+
  geom_text(aes(label=symbol),colour="yellow",nudge_y = +0.05)+
  geom_text(aes(label=CTE_ppm),colour="black",nudge_y = -0.35)+
  scale_fill_gradient(low = "red",high = "green")+
  labs(fill = expression(paste("CTE", 10^6)),
    colour="") +
  annotate("text", x = 7, y = 0.2, label = "Periodic Table of the elements", size = 5))+
  theme_void()+
  theme(legend.position="bottom")+
  scale_colour_discrete(na.translate = F)

f1 <- function(x){
  return(1/(16.6*3*(x+273)))
}

library(plotly)
library(ggrepel)
data %>% 
  ggplot(aes(mp, CTE, label=element))+
  geom_point()+
  stat_function(fun=f1)+
  lims(
    x=c(1500,4000),
    y=c(0,0.000025)
  )+
  theme(legend.position = "none")+
  ggrepel::geom_text_repel()+
  theme_bw()

Predictive modelling

Compositional data can be transformed to satisfy linearity by using logratio transformations for representation in a Euclidean space. Additive, isometric, and centre logratio transformations are available.

The transformations give the same coefficients for the multi-variate regressions so we will stick with the additive logratio defined as: \[alr(x) = \left [\log\frac{x_1}{x_n},\ldots,\log\frac{x_{n-1}}{x_n}\right]\]

These transformation functions are available within the ‘compositions’ R package. Ratio transformations have issues with zero-value elements.

PRINCIPLES OF COMPOSITIONAL DATA ANALYSIS BY JOHN AITCHISON

library(compositions)

library(caret)

library(glmnet)
hard <- read_csv("hardness.csv")
#hard %>% arrange(-hardness)
# Filtering out zero values for logratio transformations
hard_ <- hard %>% 
  filter(Al > 0, Co > 0, Cr > 0, Cu > 0, Fe > 0, Ni > 0)
  #mutate(Al = ifelse(Al == 0, 1, Al),
  #        Co = ifelse(Co == 0, 1, Co),
  #        Cr = ifelse(Cr == 0, 1, Cr),
  #        Cu = ifelse(Cu == 0, 1, Cu),
  #        Fe = ifelse(Fe == 0, 1, Fe),
  #        Ni = ifelse(Ni == 0, 1, Ni))
# Setting x, y


y <- hard_$hardness
x <- hard_[,1:ncol(hard)-1]
hard.clr <- x %>% 
  clr()
hard.ilr <- x %>% 
  ilr()
hard.alr <- x %>% 
  alr()
corrplot::corrplot(cor(hard))

#predict(hard.clr.lm,clr(v))

hard.clr.lm <- lm(y ~ ., cbind(y,hard.clr) %>% data.frame())
hard.ilr.lm <- lm(y ~ ., cbind(y,hard.ilr) %>% data.frame())
hard.alr.lm <- lm(y ~ ., cbind(y,hard.alr) %>% data.frame())
hard.ilr.glm.model <- cv.glmnet(hard.ilr, y, alpha = 1)
hard.ilr.glm <- glmnet(hard.ilr, y, alpha = 1, lambda = hard.ilr.glm.model$lambda.min)
dataset <- cbind(data.frame(hardness = y),hard.clr) %>% data.frame()

hard.clr.gam <- gam(hardness ~ Al+ Co + Co + Cr + Cu + Fe + Ni, data = dataset)
predictions.full <- data.frame(hard_) %>% 
  mutate(prediction.clr.lm = predict(hard.clr.lm, newx = hard_),
         prediction.ilr.lm = predict(hard.ilr.lm, newx = hard.ilr),
         #prediction.alr.lm = predict(hard.alr.lm, newx = hard.alr),
         prediction.ilr.glm = predict(hard.ilr.glm, best_lambda = hard.ilr.glm.model$lambda.min, newx = hard.ilr),
         prediction.clr.gam = predict(hard.clr.gam, newx = hard.clr),
         pre = predict(hard.clr.lm, newx = row()))
# predictions.full %>% colnames

predictions.full %>% 
  arrange(hardness) %>% 
  mutate(row_name = row_number()) %>% 
  pivot_longer(c(hardness, prediction.clr.lm,prediction.ilr.glm, prediction.clr.gam), names_to = "variable",values_to = "value") %>% 
  ggplot(aes(row_name, value, colour = variable, shape = variable))+
  geom_point(alpha = 0.8)+
  labs(title = "Hardness prediction",
       subtitle = "Comparing hardness prediction.clr.lm and actual hardness",
       y = "hardness",
       x = "rank ordered by ascending hardness")+
  #annotate("text", y = 200, x = 25,label = paste("RMSE = ",
   # round(RMSE(predictions.full$prediction.clr.lm, predictions.full$hardness),2)
  #))+
  theme_bw()+
  theme(legend.position = c(0.8,0.2),
        legend.background = element_rect(fill = "white", color = "black"))+
  scale_color_discrete(name = paste("RMSE = ", round(RMSE(predictions.full$prediction.clr.lm, predictions.full$hardness),2)))+
  scale_shape_discrete(name = paste("RMSE = ", round(RMSE(predictions.full$prediction.clr.lm, predictions.full$hardness),2)))

# Linear regression on ILR logratio transformation  
RMSE(predictions.full$prediction.clr.lm, predictions.full$hardness)
## [1] 63.40838
# Lasso regression on ILR logratio transformation
RMSE(predictions.full$prediction.ilr.glm, predictions.full$hardness)
## [1] 65.79862
# generalised additive model
RMSE(predictions.full$prediction.clr.gam, predictions.full$hardness)
## [1] 63.40838
# library(robCompositions)
# aDist(x = hard_[,1:ncol(hard)-1])
#Compositional::alfa.ridge(as.matrix(y), as.matrix(x),a = 0.01, lambda = 1)

Principal component analysis for compositional data vectors

https://link.springer.com/article/10.1007/s00180-015-0570-1

Artificial Neural Network (ANN) approach

scale01 <- function(x){
  (x - min(x)) / (max(x) - min(x))
}


scale02 <- function(y){
  y * (max(hard_$hardness) - min(hard_$hardness)) + min(hard_$hardness)
}

scale03 <- function(i){
  i * (max(i) - min(i)) + min(i)
}

hard_ann <- cbind(hard_$hardness,as_tibble(hard.clr)) %>% 
  mutate_all(scale01) %>% rename("hardness" = "hard_$hardness")

set.seed(1)
hard_ann_train <- sample_frac(tbl = hard_ann, replace = FALSE, size = 0.80)
hard_ann_test <- anti_join(hard_ann, hard_ann_train)
hard_ann_model1 <- neuralnet(hardness ~ Al + Co + 
                         Cr + Cu + Fe +
                         Ni, data = hard_ann_train)
hard_ann_model2 <- neuralnet(hardness ~ Al + Co + 
                         Cr + Cu + Fe +
                         Ni, data = hard_ann_train,
                         hidden = c(4, 1), 
                         act.fct = 'logistic',
                         rep = 10)
hard_ann_test_output1 <- compute(hard_ann_model1, hard_ann_test[, 2:7])$net.result
hard_ann_test_output2 <- compute(hard_ann_model2, hard_ann_test[, 2:7])$net.result


hard_ann_sse1 <- sum((hard_ann_test_output1 - hard_ann_test[, 1])^2)/2
hard_ann_sse2 <- sum((hard_ann_test_output2 - hard_ann_test[, 1])^2)/2


hard_ann_sse1
## [1] 0.01869546
hard_ann_sse2 # model 2 is more accurate
## [1] 0.01810635
dat <- cbind(p = hard_ann_test_output2, hard_ann_test) %>%
  arrange(hardness) %>%
  mutate(row = row_number()) %>% 
  mutate(prediction = scale02(p),
         hardness = scale02(hardness))

dat %>% 
  pivot_longer(c("prediction","hardness")) %>% 
  mutate(row = as.integer((row_number()-1)/2)) %>% 
  ggplot(aes(row, value, colour = name, shape = name)) + 
  geom_point()+
  scale_color_discrete(name = paste("RMSE = ", round(RMSE(dat$prediction,dat$hardness),2)))+
  scale_shape_discrete(name = paste("RMSE = ", round(RMSE(dat$prediction,dat$hardness),2)))+
  labs(title = "Unseen data observations ANN",
       subtitle = "Comparing hardness artificial neural network regression and actual hardness on unseen data")+
  theme_bw()+
  theme(legend.position = c(0.8,0.2),
        legend.background = element_rect(fill = "white", color = "black"))

hard_ann_results <- compute(hard_ann_model2, hard_ann[, 2:7])$net.result
dat1 <- cbind(predictions = hard_ann_results, hard_ann) %>% 
  mutate("ANN prediction" = scale02(predictions),
         hardness = scale02(hardness),
         distance = `ANN prediction` - hardness) %>% 
  arrange(hardness) 
dat2 <- dat1 %>% 
  pivot_longer(c("ANN prediction","hardness"), values_to = "val", names_to = "var") %>% 
  mutate(row = as.integer((row_number()-1)/2))
g <- cbind(data.frame(p = hard_ann_results) %>% mutate(p = scale02(p)),hard_ann) %>% 
  mutate(hardness = scale02(hardness))
dat2 %>% 
  ggplot(aes(row, val, shape = var, colour = var)) +
  geom_point()+
  scale_color_discrete(type = c("dodgerblue", "red"),name = paste("RMSE = ",round(RMSE(g$p, g$hardness),2)))+
  scale_shape_discrete(name = paste("RMSE = ",round(RMSE(g$p, g$hardness),2)))+
  labs(title = "Hardness prediction",
       subtitle = "Comparing hardness artificial neural network regression and actual hardness",
       y = "HV hardness",
       x = "rank ordered by ascending hardness")+
  #annotate("text", y = 200, x = 25,label = paste("RMSE = ",
  #  round(RMSE(hard$hardness, dat$predictions),2)
  #))+
  theme_bw()+
  theme(legend.position = c(0.8,0.2),
        legend.background = element_rect(fill = "white", color = "black"))

Exploring the compositional space

g <- hard_ann %>%
  arrange(desc(hardness)) %>%
  filter(hardness > 0.8) %>%
  pivot_longer(cols = colnames(hard_ann)[2:7])


#scale04("Co")
#lapply



g %>%
  ggplot(aes(hardness, value, colour = name))+
  geom_line(size = 2)+
  theme_classic()

g <- cbind(predictions = hard_ann_results, hard_ann) %>% 
  mutate(
    predictions = predictions * (max(hard_$hardness) - min(hard_$hardness)) + min(hard_$hardness),
    hardness = hardness * (max(hard_$hardness) - min(hard_$hardness)) + min(hard_$hardness),
    Al = Al * (max(hard_$Al) - min(hard_$Al)) + min(hard_$Al),
    Co = Co * (max(hard_$Co) - min(hard_$Co)) + min(hard_$Co),
    Cr = Cr * (max(hard_$Cr) - min(hard_$Cr)) + min(hard_$Cr),
    Cu = Cu * (max(hard_$Cu) - min(hard_$Cu)) + min(hard_$Cu),
    Fe = Fe * (max(hard_$Fe) - min(hard_$Fe)) + min(hard_$Fe),
    Ni = Ni * (max(hard_$Ni) - min(hard_$Ni)) + min(hard_$Ni)
  )

hard_ %>% arrange(desc(hardness))
## # A tibble: 36 x 7
##       Al    Co    Cr    Cu    Fe    Ni hardness
##    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>    <dbl>
##  1  40    13.3   6.7  13.3  20     6.7      768
##  2  43     6    33     6     6     6        764
##  3  37.5  12.5  12.5  12.5  12.5  12.5      735
##  4  42.9  14.3  14.3   7.1   7.1  14.3      720
##  5  46.2  15.4   7.7   7.7  15.4   7.7      702
##  6  42.9  21.4   7.1   7.1   7.1  14.3      701
##  7  42.9   7.1  14.3   7.1  14.3  14.3      694
##  8  35.9  12.8  12.8  12.8  12.8  12.8      655
##  9  33.3  13.3  13.3  13.3  13.3  13.3      625
## 10  30.8  15.4  15.4   7.7  15.4  15.4      609
## # ... with 26 more rows
hard %>% mutate(sum3 = Al + Co + Cr) %>% 
  ggplot(aes(sum3, hardness))+
  geom_line()

g %>% filter(hardness>650) %>% # high copper, low nickel, cobalt good/midrange, iron good 
  ggplot(aes(hardness, colour = Fe))+
  geom_point(aes(y=Al))

p <- g %>% 
  mutate(unseen = 0) %>% 
  add_row(predictions = 0, hardness = 0,Al = 40, Co = 20, Cr = 20, Cu = 0, Fe = 20, Ni = 0, unseen = 1) %>% 
  add_row(predictions = 0, hardness = 0,Al = 41, Co = 19, Cr = 20, Cu = 0, Fe = 20, Ni = 0, unseen = 1) %>% 
  add_row(predictions = 0, hardness = 0,Al = 42, Co = 18, Cr = 20, Cu = 0, Fe = 20, Ni = 0, unseen = 1) %>% 
  add_row(predictions = 0, hardness = 0,Al = 41, Co = 20, Cr = 19, Cu = 0, Fe = 20, Ni = 0, unseen = 1) %>% 
  add_row(predictions = 0, hardness = 0,Al = 42, Co = 20, Cr = 18, Cu = 0, Fe = 20, Ni = 0, unseen = 1) %>% 
  add_row(predictions = 0, hardness = 0,Al = 41, Co = 20, Cr = 20, Cu = 0, Fe = 19, Ni = 0, unseen = 1) %>% 
  add_row(predictions = 0, hardness = 0,Al = 42, Co = 20, Cr = 20, Cu = 0, Fe = 18, Ni = 0, unseen = 1) %>% 
    add_row(predictions = 0, hardness = 0,Al = 43, Co = 17, Cr = 20, Cu = 0, Fe = 20, Ni = 0, unseen = 1) %>% 
    add_row(predictions = 0, hardness = 0,Al = 44, Co = 16, Cr = 20, Cu = 0, Fe = 20, Ni = 0, unseen = 1) %>% 
    add_row(predictions = 0, hardness = 0,Al = 45, Co = 15, Cr = 20, Cu = 0, Fe = 20, Ni = 0, unseen = 1) %>% 
    add_row(predictions = 0, hardness = 0,Al = 46, Co = 18, Cr = 18, Cu = 0, Fe = 18, Ni = 0, unseen = 1) %>% 
    add_row(predictions = 0, hardness = 0,Al = 43, Co = 19, Cr = 19, Cu = 0, Fe = 19, Ni = 0, unseen = 1) %>% 
  add_row(predictions = 0, hardness = 0,Al = 49, Co = 17, Cr = 17, Cu = 0, Fe = 17, Ni = 0, unseen = 1) %>% 
  add_row(predictions = 0, hardness = 0,Al = 30:50, Co = 30:10, Cr = 20, Cu = 0, Fe = 20, Ni = 0, unseen = 1) %>% 
  add_row(predictions = 0, hardness = 0,Al = 30:50, Co = 20, Cr = 30:10, Cu = 0, Fe = 20, Ni = 0, unseen = 1) %>%
  add_row(predictions = 0, hardness = 0,Al = 30:50, Co = 20, Cr = 20, Cu = 0, Fe = 30:10, Ni = 0, unseen = 1) %>% 
  add_row(predictions = 0, hardness = 0,Al = 20:60, Co = 40:0, Cr = 20, Cu = 0, Fe = 20, Ni = 0, unseen = 1) 





predictions <- compute(hard_ann_model2, p[, 3:8])$net.result

t <- cbind(predictions,p[,2:9]) %>% 
    mutate(
    predictions = predictions * (max(hard_$hardness) - min(hard_$hardness)) + min(hard_$hardness)) %>% 
  distinct()


t_plot <- t %>% arrange(predictions) %>% 
  mutate(rank = row_number()) %>% 
  mutate(composition = paste("Al",round(Al),"Co",round(Co),"Cr",round(Cr),"Cu",round(Cu),"Fe",round(Fe),"Ni",round(Ni))) %>% 
  ggplot(aes(rank, predictions, colour = as.factor(unseen), shape = as.factor(unseen), group = composition))+
  geom_point()+
  scale_color_discrete(type = c("red", "dodgerblue"),name = "unseen")+
  scale_shape_discrete(name = "unseen")+
  labs(title = "Hardness prediction in a virtual space",
       subtitle = "Exploring a virtual compositional space with the ANN model",
       y = "HV hardness",
       x = "rank ordered by ascending hardness")+
  #annotate("text", y = 200, x = 25,label = paste("RMSE = ",
  #  round(RMSE(hard$hardness, dat$predictions),2)
  #))+
  theme_bw()+
  theme(legend.position = c(0.8,0.2),
        legend.background = element_rect(fill = "white", color = "black"))

plotly::ggplotly(t_plot)
hard %>% filter(Co<15,Al>40) %>% 
  arrange(desc(hardness))
## # A tibble: 5 x 7
##      Al    Co    Cr    Cu    Fe    Ni hardness
##   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>    <dbl>
## 1  43     6    33     6     6     6        764
## 2  42.9  14.3  14.3   0    14.3  14.3      740
## 3  42.9  14.3  14.3   7.1   7.1  14.3      720
## 4  42.9   7.1  14.3   7.1  14.3  14.3      694
## 5  42.9  14.3   7.1   7.1   7.1  21.4      591