sessionInfo()
## R version 4.5.0 (2025-04-11 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
## 
## Matrix products: default
##   LAPACK version 3.12.1
## 
## locale:
## [1] LC_COLLATE=English_United Kingdom.utf8 
## [2] LC_CTYPE=English_United Kingdom.utf8   
## [3] LC_MONETARY=English_United Kingdom.utf8
## [4] LC_NUMERIC=C                           
## [5] LC_TIME=English_United Kingdom.utf8    
## 
## time zone: Europe/Berlin
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] maps_3.4.3              scatterpie_0.2.6        rnaturalearthdata_1.0.0
##  [4] rnaturalearth_1.2.0     ggforce_0.5.0           sf_1.0-24              
##  [7] ggrepel_0.9.6           reshape2_1.4.5          lubridate_1.9.5        
## [10] forcats_1.0.1           stringr_1.6.0           dplyr_1.2.1            
## [13] purrr_1.2.1             readr_2.1.6             tidyr_1.3.2            
## [16] tibble_3.3.1            ggplot2_4.0.2           tidyverse_2.0.0        
## 
## loaded via a namespace (and not attached):
##  [1] gtable_0.3.6       xfun_0.61          bslib_0.10.0       tzdb_0.5.0        
##  [5] vctrs_0.7.3        tools_4.5.0        generics_0.1.4     yulab.utils_0.2.4 
##  [9] proxy_0.4-29       pkgconfig_2.0.3    KernSmooth_2.23-26 RColorBrewer_1.1-3
## [13] S7_0.2.1           lifecycle_1.0.5    compiler_4.5.0     farver_2.1.2      
## [17] ggfun_0.2.1        htmltools_0.5.8.1  class_7.3-23       sass_0.4.10       
## [21] yaml_2.3.10        pillar_1.11.1      jquerylib_0.1.4    MASS_7.3-65       
## [25] classInt_0.4-11    cachem_1.1.0       tidyselect_1.2.1   digest_0.6.37     
## [29] stringi_1.8.7      polyclip_1.10-7    fastmap_1.2.0      grid_4.5.0        
## [33] cli_3.6.5          magrittr_2.0.3     dichromat_2.0-0.1  e1071_1.7-17      
## [37] withr_3.0.2        scales_1.4.0       rappdirs_0.3.4     timechange_0.4.0  
## [41] rmarkdown_2.30     otel_0.2.0         hms_1.1.4          evaluate_1.0.5    
## [45] knitr_1.51         rlang_1.2.0        Rcpp_1.1.0         glue_1.8.0        
## [49] DBI_1.3.0          tweenr_2.0.3       rstudioapi_0.18.0  jsonlite_2.0.0    
## [53] R6_2.6.1           plyr_1.8.9         fs_2.1.0           units_1.0-0

Determinung number of K

cv <- read.table("admixture/cv_errors.txt", header=FALSE)
colnames(cv) <- c("K", "CV")

cv_df <- data.frame(K = cv$K,CV = cv$CV)

ggplot(cv_df, aes(x = K, y = CV)) +
  geom_line(linewidth = 0.8, color = "#009988") +
  geom_point(size = 3, shape = 21, fill = "white", colour = "#009988", stroke = 1) +
  scale_x_continuous(breaks = cv_df$K) +
  labs(x = "K (number of clusters)",
    y = "CV error") +
  theme_minimal(base_size = 14) +
  theme(
    panel.grid.minor = element_blank(),
    plot.title = element_text(hjust = 0.5, face = "bold"),
    axis.title = element_text(face = "bold"))

qpath <- "admixture/" 
famfile <- "admixture/md6ct95ms200_numeric.fam"      

samples <- read.table(famfile, stringsAsFactors = FALSE)
sample_ids <- samples$V2   # V2 = individual IDs


readQ <- function(K) {
  qfile <- paste0(qpath, "md6ct95ms200_numeric.", K, ".Q")
  q <- read.table(qfile, stringsAsFactors = FALSE)
  colnames(q) <- paste0("Pop", 1:K)
  q$SampleID <- sample_ids
  q$K <- K
  q }


Ks <- c(2,3,4)
Qlist <- lapply(Ks, readQ)
Qdata <- bind_rows(Qlist)

# Reshape to long format for ggplot
Qlong <- melt(Qdata, id.vars = c("SampleID", "K"),
              variable.name = "Cluster", value.name = "Ancestry")

length(unique(Qlong$SampleID))
## [1] 311
# To sort samples consistently (optional: here by SampleID)
Qlong$SampleID <- factor(Qlong$SampleID, levels = unique(Qlong$SampleID))

palette <- c("#E69F00","#009E73","#0072B2" ,"#F0E442") 

ggplot(Qlong, aes(x = SampleID, y = Ancestry, fill = Cluster)) +
  geom_bar(stat = "identity", width = 1) +
  facet_wrap(~K, ncol = 1, scales = "free_x") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 95, size = 4)) +
  labs(title = "ADMIXTURE ancestry proportions",
       x = "Individuals",
       y = "Ancestry proportion")+
  scale_fill_manual(values = palette) 
## Warning: Removed 933 rows containing missing values or values outside the scale range
## (`geom_bar()`).

Prepare coordinates

Coordinates_pops <- read_delim("Coordinates_pops.txt", delim = "\t", escape_double = FALSE, trim_ws = TRUE)
## Rows: 310 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: "\t"
## chr (1): SampleID
## dbl (2): Latitude, Longitude
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Coordinates_pops$Population <- sub("-.*", "", Coordinates_pops$SampleID)
Qwide <- Qdata %>% left_join(Coordinates_pops, by = c("SampleID"))

Qlong <- Qdata %>%
  pivot_longer(cols = matches("^Pop\\d+$"),   
               names_to = "Cluster", 
               values_to = "Ancestry")

Qlong <- Qlong %>%
  left_join(Coordinates_pops, by = "SampleID")
Qlong <- Qlong %>%
  filter(!is.na(Longitude))


pop_means_long <- Qlong %>%
  group_by(Population, K, Cluster) %>%
  summarise(Ancestry = mean(Ancestry, na.rm = TRUE))
## `summarise()` has regrouped the output.
## ℹ Summaries were computed grouped by Population, K, and Cluster.
## ℹ Output is grouped by Population and K.
## ℹ Use `summarise(.groups = "drop_last")` to silence this message.
## ℹ Use `summarise(.by = c(Population, K, Cluster))` for per-operation grouping
##   (`?dplyr::dplyr_by`) instead.
pop_coords <- Coordinates_pops %>%
  group_by(Population) %>%
  summarise(Longitude = mean(Longitude, na.rm = TRUE),
            Latitude  = mean(Latitude,  na.rm = TRUE))


pop_means_wide <- pop_means_long %>%
  pivot_wider(names_from = Cluster, values_from = Ancestry) %>%
  left_join(pop_coords, by = "Population")


head(pop_means_wide)
## # A tibble: 6 × 8
## # Groups:   Population, K [6]
##   Population     K    Pop1    Pop2      Pop3      Pop4 Longitude Latitude
##   <chr>      <dbl>   <dbl>   <dbl>     <dbl>     <dbl>     <dbl>    <dbl>
## 1 DE1            2 1.000   0.00001 NaN       NaN            7.89     51.8
## 2 DE1            3 0.00001 0.00001   1.000   NaN            7.89     51.8
## 3 DE1            4 0.00001 1.000     0.00001   0.00001      7.89     51.8
## 4 DE10           2 0.577   0.423   NaN       NaN            9.74     48.5
## 5 DE10           3 0.190   0.289     0.520   NaN            9.74     48.5
## 6 DE10           4 0.112   0.414     0.0286    0.445        9.74     48.5

Admixture on the map K = 2

The pie locations are slightly jittered for visualisation purpose

Kplot <- 2
cols <- paste0("Pop", 1:Kplot)   # required columns for that K

# Keep only relevant columns and K = 2
plot_data <- pop_means_wide %>%
  dplyr::filter(K == Kplot) %>%
  dplyr::select(any_of(c("Population","Pop1", "Pop2","K", "Longitude", "Latitude")), all_of(cols))

# Reproject both map and data
europe <- ne_countries(continent = "Europe", scale = "medium", returnclass = "sf")
europe_proj <- st_transform(europe, 3035)
plot_data_sf <- st_as_sf(plot_data, coords = c("Longitude", "Latitude"), crs = 4326)
plot_data_proj <- st_transform(plot_data_sf, 3035)

# Extract projected coordinates as plain data frame
plot_data_proj_df <- cbind(st_drop_geometry(plot_data_proj), st_coordinates(plot_data_proj))

# Make sure Pop columns are numeric
plot_data_proj_df <- plot_data_proj_df %>%
  mutate(across(all_of(cols), as.numeric))
palette <- c("#E69F00","#009E73") 

ggplot(data = europe_proj) +
  geom_sf(fill = "grey80", color = "grey60", linewidth = 0.8) +
  geom_scatterpie(
    data = plot_data_proj_df,
    aes(x = X, y = Y, r = 40000),
    cols = cols) +
  geom_text_repel(
    data = plot_data_proj_df,
    aes(x = X, y = Y, label = Population),
    size = 3, fontface = "bold", 
   segment.color = NA,
    max.overlaps = 20, box.padding = 0.5,       # push label away from itself
    point.padding = 0.2,     # push label away from pie center
    min.segment.length = 0) +
  coord_sf(xlim = c(3900000, 5700000), ylim = c(2500000, 4500000), expand = FALSE) +
  scale_fill_manual(values = palette[1:length(cols)]) +
  theme_minimal()

Admixture K = 3 on the map

The pie locations are slightly jittered for visualisation purpose

Kplot <- 3
cols <- paste0("Pop", 1:Kplot)   # required columns for that K

# Keep only relevant columns and K = 3
plot_data <- pop_means_wide %>%
  dplyr::filter(K == Kplot) %>%
  dplyr::select(any_of(c("Population","Pop1", "Pop2","Pop3","K", "Longitude", "Latitude")), all_of(cols))

# Reproject both map and data
europe <- ne_countries(continent = "Europe", scale = "medium", returnclass = "sf")
europe_proj <- st_transform(europe, 3035)
plot_data_sf <- st_as_sf(plot_data, coords = c("Longitude", "Latitude"), crs = 4326)
plot_data_proj <- st_transform(plot_data_sf, 3035)

# Extract projected coordinates as plain data frame
plot_data_proj_df <- cbind(st_drop_geometry(plot_data_proj), st_coordinates(plot_data_proj))

# Make sure Pop columns are numeric
plot_data_proj_df <- plot_data_proj_df %>%
  mutate(across(all_of(cols), as.numeric))
palette <- c("#0072B2","#009E73","#E69F00") 

ggplot(data = europe_proj) +
  geom_sf(fill = "grey80", color = "grey60", linewidth = 0.8) +
  geom_scatterpie(
    data = plot_data_proj_df,
    aes(x = X, y = Y, r = 40000),
    cols = cols) +
  geom_text_repel(
    data = plot_data_proj_df,
    aes(x = X, y = Y, label = Population),
    size = 3, fontface = "bold", 
    segment.color = NA,
    max.overlaps = 20, box.padding = 0.5,       # push label away from itself
    point.padding = 0.2,     # push label away from pie center
    min.segment.length = 0) +
  coord_sf(xlim = c(3900000, 5700000), ylim = c(2500000, 4500000), expand = FALSE) +
  scale_fill_manual(values = palette[1:length(cols)]) +
  theme_minimal()

Admixture K = 4 on the map

The pie locations are slightly jittered for visualisation purpose

Kplot <- 4
cols <- paste0("Pop", 1:Kplot)   # required columns for that K

# Keep only relevant columns and K = 4
plot_data <- pop_means_wide %>%
  dplyr::filter(K == Kplot) %>%
  dplyr::select(any_of(c("Population","Pop1", "Pop2","Pop3","Pop4","K", "Longitude", "Latitude")), all_of(cols))


# Reproject both map and data
europe <- ne_countries(continent = "Europe", scale = "medium", returnclass = "sf")
europe_proj <- st_transform(europe, 3035)
plot_data_sf <- st_as_sf(plot_data, coords = c("Longitude", "Latitude"), crs = 4326)
plot_data_proj <- st_transform(plot_data_sf, 3035)

# Extract projected coordinates as plain data frame
plot_data_proj_df <- cbind(st_drop_geometry(plot_data_proj), st_coordinates(plot_data_proj))

# Make sure Pop columns are numeric
plot_data_proj_df <- plot_data_proj_df %>%
  mutate(across(all_of(cols), as.numeric))
palette <- c("#009E73","#E69F00","#0072B2" ,"#5E4FA2") 
ggplot(data = europe_proj) +
  geom_sf(fill = "grey80", color = "grey60", linewidth = 0.8) +
  geom_scatterpie(
    data = plot_data_proj_df,
    aes(x = X, y = Y, r = 40000),
    cols = cols) +
  geom_text_repel(
    data = plot_data_proj_df,
    aes(x = X, y = Y, label = Population),
    size = 3, fontface = "bold", 
   segment.color = NA,
    max.overlaps = 20, box.padding = 0.5,       # push label away from itself
    point.padding = 0.2,     # push label away from pie center
    min.segment.length = 0) +
  coord_sf(xlim = c(3900000, 5700000), ylim = c(2500000, 4500000), expand = FALSE) +
  scale_fill_manual(values = palette[1:length(cols)]) +
  theme_minimal()

Extract population assignment for Stairway2 (K=3)

df_Stairway <- Qlong %>%
  dplyr::filter(K == 3) %>%              
  dplyr::group_by(SampleID) %>%
 slice_max(Ancestry, n = 1, with_ties = FALSE) %>%
 ungroup() %>%
  dplyr::select(SampleID, Cluster)

#Remove clones:
clones_all <- c(
  "DE1-4","DE1-7","DE1-5",
  "DE2-4",
  "DE3-9","DE3-10","DE3-11","DE3-5",
  "DE4-1","DE4-3","DE4-6","DE4-8",
  "DE5-3","DE5-2","DE5-7","DE5-9",
  "DE7-12","DE7-6","DE7-7","DE7-10","DE7-2","DE7-17","DE7-18","DE7-19","DE7-20","DE7-8",
  "DE9-13","DE9-2r",
  "DE10-11","DE10-12","DE10-2","DE10-5","DE10-7","DE10-8",
  "DE11-12","DE11-15","DE11-14r","DE12-5",
  "DK-4","DK-5",
  "EE-7",
  "FI1-7-1","FI5-2","FI5-10","FI5-8",
  "LT1-9",
  "LT2-3","LT2-4","LT2-5","LT2-6","LT2-7","LT2-1r",
  "LT3-11","LT3-12","LT3-5",
  "LV-12","LV-8",
  "PL2-4","PL2-6-2",
  "PL4-14","PL4-1","PL4-3","PL4-7",
  "PL5-20","PL5-21",
  "PL6-10","PL6-9",
  "PL7-12","PL7-5","PL7-18",
  "PL8-17","PL8-8","PL9-4","PL9-8","PL8-19-",
  "RU1-4","RU2-3","RU2-5","RU2-6",
  "SE3-4-2","SE3-4-2r","SE1-4","SE1-5","SE1-6","SE1-7","SE1-9",
  "UA1-13","UA1-14","UA1-15","UA1-17","UA1-16r",
  "UA2-1","UA2-12","UA2-18","UA2-3")


df_Stairway <-  df_Stairway[!df_Stairway$SampleID %in% clones_all, ]

write.table(df_Stairway, file = "df_Stairway.tsv", sep = "\t", row.names = FALSE, quote = FALSE)
df_Stairway %>% count(Cluster, name = "N_samples") # how many samples per cluster
## # A tibble: 3 × 2
##   Cluster N_samples
##   <chr>       <int>
## 1 Pop1          118
## 2 Pop2           81
## 3 Pop3           23
#Pop1 here is Cluster 3 (North)
#Pop2 is Cluster 2 (East)
#Pop3 is Cluster 1 (West)