Last updated: 2026-03-10

Checks: 6 1

Knit directory: HonorsThesis/

This reproducible R Markdown analysis was created with workflowr (version 1.7.2). The Checks tab describes the reproducibility checks that were applied when the results were created. The Past versions tab lists the development history.


The R Markdown file has unstaged changes. To know which version of the R Markdown file created these results, you’ll want to first commit it to the Git repo. If you’re still working on the analysis, you can ignore this warning. When you’re finished, you can run wflow_publish to commit the R Markdown file and build the HTML.

Great job! The global environment was empty. Objects defined in the global environment can affect the analysis in your R Markdown file in unknown ways. For reproduciblity it’s best to always run the code in an empty environment.

The command set.seed(20260127) was run prior to running the code in the R Markdown file. Setting a seed ensures that any results that rely on randomness, e.g. subsampling or permutations, are reproducible.

Great job! Recording the operating system, R version, and package versions is critical for reproducibility.

Nice! There were no cached chunks for this analysis, so you can be confident that you successfully produced the results during this run.

Great job! Using relative paths to the files within your workflowr project makes it easier to run your code on other machines.

Great! You are using Git for version control. Tracking code development and connecting the code version to the results is critical for reproducibility.

The results in this page were generated with repository version 8386437. See the Past versions tab to see a history of the changes made to the R Markdown and HTML files.

Note that you need to be careful to ensure that all relevant files for the analysis have been committed to Git prior to generating the results (you can use wflow_publish or wflow_git_commit). workflowr only checks the R Markdown file, but you know if there are other scripts or data files that it depends on. Below is the status of the Git repository when the results were generated:


Ignored files:
    Ignored:    .DS_Store
    Ignored:    .claude/
    Ignored:    renv/library/
    Ignored:    renv/staging/

Unstaged changes:
    Modified:   analysis/cups_and_skulls_analysis.Rmd
    Modified:   renv.lock

Note that any generated files, e.g. HTML, png, CSS, etc., are not included in this status report because it is ok for generated content to have uncommitted changes.


These are the previous versions of the repository in which changes were made to the R Markdown (analysis/cups_and_skulls_analysis.Rmd) and HTML (docs/cups_and_skulls_analysis.html) files. If you’ve configured a remote Git repository (see ?wflow_git_remote), click on the hyperlinks in the table below to view the files as they were in that past version.

File Version Author Date Message
html 352636f tinalasisi 2026-02-17 Build site.
Rmd 27b3161 tinalasisi 2026-02-17 Add comprehensive guiding comments for student customization
html 01b0d46 tinalasisi 2026-02-17 Build site.
Rmd 2410c8d tinalasisi 2026-02-17 Add dictionary loading template and switch to grouped bar plots
html 8d3e1c7 tinalasisi 2026-02-17 Build site.
Rmd d689566 tinalasisi 2026-02-17 Add color customization and region-based faceted plots
html 2f9f6dc tinalasisi 2026-02-17 Build site.
Rmd c592bd3 tinalasisi 2026-02-17 Add data dictionary worksheet and chamber space analysis

About This Analysis

This analysis examines funerary assemblage patterns in Laconia and Messinia, including:

  • Distribution comparisons between Laconia and Messenia
  • Temporal trends in burial practices across periods
  • Hair pin presence and its relationship to secondary burial, drinking vessels, and tomb size
  • Regional differences in how graves are used
  • Relationships between chamber size, drinking vessels, skeletal remains, and hair pins

Introduction

This analysis examines the relationship between drinking vessels, human skeletal remains, and hair pins in Laconian and Messinian tholos and chamber tombs. The data focuses on grave contexts where drinking vessels and hair pins are recorded alongside information about primary and secondary burials.

Load Libraries

knitr::opts_chunk$set(warning = FALSE, message = FALSE)
library(readr)
library(dplyr)
library(ggplot2)
library(tidyr)
library(stringr)
library(janitor)
library(knitr)
library(ggrepel)

Color Customization

YOU CAN EDIT THIS SECTION: Customize colors for the analyses. Edit the hex codes below to use your preferred colors.

All plots throughout this analysis will use these colors, so if you change them here, the change applies everywhere automatically.

Color choices affect: - Regional comparisons (Laconia vs Messenia) - Visualization clarity and accessibility - How different variables stand out in plots

# Set your color palette here
colors <- list(
  laconia = "#1B9E77",      # Laconia color (default: teal/green)
  messenia = "#D95F02",     # Messenia color (default: orange)
  drinking_vessel = "#7570B3",  # Drinking vessels (default: purple)
  skulls = "#E7298A",        # Skulls (default: pink)
  pin = "#E6AB02",            # Hair pins (default: gold/amber)
  background = "#FFFFCC"     # Background/neutral (default: light yellow)
)

# These colors will be used throughout all plots
# Change the hex codes to your preferred colors

# Set global ggplot theme with larger text
theme_set(theme_minimal(base_size = 16))
theme_update(
  plot.title = element_text(size = 18, face = "bold"),
  plot.subtitle = element_text(size = 14),
  axis.title = element_text(size = 14),
  axis.text = element_text(size = 12),
  legend.text = element_text(size = 12),
  legend.title = element_text(size = 13),
  strip.text = element_text(size = 13)
)

Load Data

# Load the CSV data
data_path <- here::here("data", "laconia_messinia_cups_skulls_pins.csv")
cups_skulls <- read_csv(data_path)

# Display dimensions
cat("Dataset dimensions:", nrow(cups_skulls), "graves ×", ncol(cups_skulls), "columns\n")
Dataset dimensions: 296 graves × 15 columns
# Show column names
glimpse(cups_skulls)
Rows: 296
Columns: 15
$ `Grave ID`                  <chr> "vapheio_tholos", "arkynes_tholos_A", "ark…
$ site                        <chr> "Vapheio", "Arkynes", "Arkynes", "Arkynes"…
$ type                        <chr> "tholos", "tholos", "tholos", "tholos", "t…
$ period                      <chr> "LHIIA-LHIIIA1", "LHIIIA- LHIIIB", "LHIII"…
$ secondary                   <chr> "absent", "absent", "absent", "present", "…
$ primary                     <chr> "absent", "absent", "present", "absent", "…
$ dimensions                  <chr> "83.28m^2. dromos: L 29.8m x w3.18-3.45, s…
$ orientation                 <chr> "NE-SW", "SE-NW", NA, NA, "E-W", "N-S", "W…
$ `drinking vessel`           <chr> "yes", "no", "no", "no", "no", "no", "no",…
$ remains                     <chr> "yes", "yes", "yes", "yes", "yes", "no", "…
$ `drinking vessel number`    <dbl> 6, NA, NA, NA, NA, NA, NA, 4, NA, NA, 5, N…
$ `skulls number`             <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ `area of chamber estimated` <dbl> 84.130, 17.350, NA, 28.274, 7.793, NA, 18.…
$ region                      <chr> "laconia", "laconia", "laconia", "laconia"…
$ `hair pin`                  <chr> "yes", "no", "no", "yes", "no", "no", "no"…

Data Cleaning

NOTE: The code below simplifies the complex grave type names into broader categories. If you want to group grave types differently, you can edit the case_when() statements in this chunk. For example, you might want to keep “Pit Grave” and “Cist Grave” separate, or group them together.

cups_skulls_clean <- cups_skulls %>%
  # Clean column names
  janitor::clean_names() %>%

  # Trim whitespace from character columns
  mutate(across(where(is.character), str_trim)) %>%

  # Standardize yes/no values in drinking vessel column
  mutate(
    has_drinking_vessel = case_when(
      tolower(drinking_vessel) == "yes" ~ TRUE,
      tolower(drinking_vessel) == "no" ~ FALSE,
      TRUE ~ NA
    ),
    has_remains = case_when(
      tolower(remains) == "yes" ~ TRUE,
      tolower(remains) == "no" ~ FALSE,
      TRUE ~ NA
    ),
    primary_burial = case_when(
      tolower(primary) == "present" ~ TRUE,
      tolower(primary) == "absent" ~ FALSE,
      TRUE ~ NA
    ),
    secondary_burial = case_when(
      tolower(secondary) == "present" ~ TRUE,
      tolower(secondary) == "absent" ~ FALSE,
      TRUE ~ NA
    ),
    has_pin = case_when(
      tolower(hair_pin) == "yes" ~ TRUE,
      tolower(hair_pin) == "no" ~ FALSE,
      TRUE ~ NA
    )
  ) %>%

  # Create burial category
  mutate(
    burial_category = case_when(
      primary_burial & secondary_burial ~ "Both Primary & Secondary",
      primary_burial & !secondary_burial ~ "Primary Only",
      !primary_burial & secondary_burial ~ "Secondary Only",
      TRUE ~ "Unknown/Not Recorded"
    )
  ) %>%

  # Simplify grave type
  # ** YOU CAN CUSTOMIZE THIS SECTION **
  # If you want different groupings, edit the patterns here
  # Current grouping is based on archaeological complexity
  mutate(
    grave_type_simple = case_when(
      str_detect(tolower(type), "tholos") ~ "Tholos",
      str_detect(tolower(type), "chamber") ~ "Chamber Tomb",
      str_detect(tolower(type), "cist") ~ "Cist Grave",
      str_detect(tolower(type), "pit") ~ "Pit Grave",
      str_detect(tolower(type), "dromos") ~ "Dromos Only",
      TRUE ~ "Other"
    )
  )

# Show cleaned data structure
glimpse(cups_skulls_clean)
Rows: 296
Columns: 22
$ grave_id                  <chr> "vapheio_tholos", "arkynes_tholos_A", "arkyn…
$ site                      <chr> "Vapheio", "Arkynes", "Arkynes", "Arkynes", …
$ type                      <chr> "tholos", "tholos", "tholos", "tholos", "tho…
$ period                    <chr> "LHIIA-LHIIIA1", "LHIIIA- LHIIIB", "LHIII", …
$ secondary                 <chr> "absent", "absent", "absent", "present", "pr…
$ primary                   <chr> "absent", "absent", "present", "absent", "pr…
$ dimensions                <chr> "83.28m^2. dromos: L 29.8m x w3.18-3.45, sto…
$ orientation               <chr> "NE-SW", "SE-NW", NA, NA, "E-W", "N-S", "W-E…
$ drinking_vessel           <chr> "yes", "no", "no", "no", "no", "no", "no", "…
$ remains                   <chr> "yes", "yes", "yes", "yes", "yes", "no", "no…
$ drinking_vessel_number    <dbl> 6, NA, NA, NA, NA, NA, NA, 4, NA, NA, 5, NA,…
$ skulls_number             <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ area_of_chamber_estimated <dbl> 84.130, 17.350, NA, 28.274, 7.793, NA, 18.32…
$ region                    <chr> "laconia", "laconia", "laconia", "laconia", …
$ hair_pin                  <chr> "yes", "no", "no", "yes", "no", "no", "no", …
$ has_drinking_vessel       <lgl> TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FAL…
$ has_remains               <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, …
$ primary_burial            <lgl> FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALS…
$ secondary_burial          <lgl> FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, FALS…
$ has_pin                   <lgl> TRUE, FALSE, FALSE, TRUE, FALSE, FALSE, FALS…
$ burial_category           <chr> "Unknown/Not Recorded", "Unknown/Not Recorde…
$ grave_type_simple         <chr> "Tholos", "Tholos", "Tholos", "Tholos", "Tho…

Import and Process Data Dictionaries

This section loads the period and tomb type dictionaries and applies chronological and complexity orderings to the data.

# NOTE: Set eval=TRUE once the student has completed the dictionaries

# Load period dictionary
period_dict <- read_csv(here::here("data", "period_dictionary.csv")) %>%
  mutate(across(where(is.character), str_trim))

# Load tomb type dictionary
tomb_dict <- read_csv(here::here("data", "tomb_type_dictionary.csv")) %>%
  mutate(tomb_type = str_trim(tomb_type))

# Join period dictionary and extract period components
cups_skulls_clean <- cups_skulls_clean %>%
  left_join(
    period_dict %>% select(period_code, chronological_order, earliest_component, latest_component),
    by = c("period" = "period_code")
  ) %>%

  # Get ordinal values for earliest component
  left_join(
    period_dict %>% select(period_code, chronological_order) %>% rename(start_order = chronological_order),
    by = c("earliest_component" = "period_code")
  ) %>%

  # Get ordinal values for latest component
  left_join(
    period_dict %>% select(period_code, chronological_order) %>% rename(end_order = chronological_order),
    by = c("latest_component" = "period_code")
  ) %>%

  # For single periods (where start = end), use that value
  # For ranges, create midpoint
  mutate(
    period_start_order = start_order,
    period_end_order = end_order,
    period_midpoint_order = (start_order + end_order) / 2
  )

# Calendar year lookup for each single-component period code
# Sources: user-supplied dates; sub-phases (MHIIIB, LHIIIB1, LHIIIC1) estimated
component_cal <- tribble(
  ~component,      ~cal_start_bce, ~cal_end_bce,
  "MH",            2000,           1600,
  "MHII",          1850,           1700,
  "MHIII",         1700,           1600,
  "MHIIIB",        1650,           1600,
  "LH",            1600,           1070,
  "LHI",           1600,           1500,
  "LHII",          1500,           1390,
  "LHIIA",         1500,           1440,
  "LHIIB",         1440,           1390,
  "LHIII",         1390,           1070,
  "LHIIIA",        1390,           1300,
  "LHIIIA1",       1390,           1370,
  "LHIIIA2",       1370,           1300,
  "LHIIIB",        1300,           1190,
  "LHIIIB1",       1300,           1250,
  "LHIIIC",        1190,           1070,
  "LHIIIC1",       1190,           1130,
  "submycenaean",  1070,           1015
)

# Add calendar year columns: start = start of earliest component,
# end = end of latest component
cups_skulls_clean <- cups_skulls_clean %>%
  left_join(
    component_cal %>% select(component, cal_start_bce),
    by = c("earliest_component" = "component")
  ) %>%
  rename(period_start_cal = cal_start_bce) %>%
  left_join(
    component_cal %>% select(component, cal_end_bce),
    by = c("latest_component" = "component")
  ) %>%
  rename(period_end_cal = cal_end_bce) %>%
  mutate(period_midpoint_cal = (period_start_cal + period_end_cal) / 2)

# Broad phase groupings with calendar spans for temporal plots
phase_cal <- tribble(
  ~broad_phase,  ~phase_start_bce, ~phase_end_bce,
  "MH",          2000,             1600,
  "LH I",        1600,             1500,
  "LH II",       1500,             1390,
  "LH IIIA",     1390,             1300,
  "LH IIIB",     1300,             1190,
  "LH IIIC",     1190,             1070,
  "SM",          1070,             1015
) %>% mutate(
  phase_mid_bce = (phase_start_bce + phase_end_bce) / 2,
  phase_duration = phase_start_bce - phase_end_bce,
  broad_phase = factor(broad_phase,
    levels = c("MH", "LH I", "LH II", "LH IIIA", "LH IIIB", "LH IIIC", "SM"),
    ordered = TRUE)
)

# Assign each grave to broadest phase based on its earliest component
cups_skulls_clean <- cups_skulls_clean %>%
  mutate(
    broad_phase = case_when(
      earliest_component %in% c("MH", "MHII", "MHIII", "MHIIIB") ~ "MH",
      earliest_component %in% c("LH", "LHI")                      ~ "LH I",
      earliest_component %in% c("LHII", "LHIIA", "LHIIB")         ~ "LH II",
      earliest_component %in% c("LHIII", "LHIIIA", "LHIIIA1", "LHIIIA2") ~ "LH IIIA",
      earliest_component %in% c("LHIIIB", "LHIIIB1")              ~ "LH IIIB",
      earliest_component %in% c("LHIIIC", "LHIIIC1")              ~ "LH IIIC",
      earliest_component == "submycenaean"                          ~ "SM",
      TRUE ~ NA_character_
    ),
    broad_phase = factor(broad_phase,
      levels = c("MH", "LH I", "LH II", "LH IIIA", "LH IIIB", "LH IIIC", "SM"),
      ordered = TRUE)
  )

# Map grave_type_simple names to tomb_type dictionary names
grave_to_tomb_map <- tibble(
  grave_type_simple = c("Tholos", "Chamber Tomb", "Cist Grave", "Pit Grave", "Dromos Only", "Other"),
  tomb_type_dict = c("tholos", "chamber tomb", "cist", "pit", "dromos of tholos", NA)
)

# Join tomb type dictionary via mapping
cups_skulls_clean <- cups_skulls_clean %>%
  left_join(grave_to_tomb_map, by = "grave_type_simple") %>%
  left_join(
    tomb_dict %>% select(tomb_type, tomb_type_order),
    by = c("tomb_type_dict" = "tomb_type")
  ) %>%
  select(-tomb_type_dict) %>%

  # Convert to ordered factors
  mutate(
    tomb_type_ordered = factor(
      grave_type_simple,
      levels = grave_to_tomb_map$grave_type_simple[
        order(tomb_dict$tomb_type_order[match(grave_to_tomb_map$tomb_type_dict, tomb_dict$tomb_type)])
      ],
      ordered = TRUE
    ),
    period_ordered = factor(
      period,
      levels = arrange(period_dict, chronological_order) %>% pull(period_code),
      ordered = TRUE
    )
  )

cat("✓ Dictionaries successfully loaded and applied!\n")
✓ Dictionaries successfully loaded and applied!
cat("New columns created:\n")
New columns created:
cat("  - period_start_order: chronological order of earliest period component\n")
  - period_start_order: chronological order of earliest period component
cat("  - period_end_order: chronological order of latest period component\n")
  - period_end_order: chronological order of latest period component
cat("  - period_midpoint_order: midpoint between start and end\n")
  - period_midpoint_order: midpoint between start and end
cat("  - tomb_type_ordered: ordered factor for tomb types\n")
  - tomb_type_ordered: ordered factor for tomb types
cat("  - period_ordered: ordered factor for periods\n")
  - period_ordered: ordered factor for periods

Data Overview

Basic Statistics

These are descriptive statistics of your dataset. They won’t change based on your dictionaries, but they provide context for all analyses below.

cat("=== Dataset Summary ===\n\n")
=== Dataset Summary ===
cat("Total graves:", nrow(cups_skulls_clean), "\n")
Total graves: 296 
cat("Graves with drinking vessels recorded:", sum(cups_skulls_clean$has_drinking_vessel == TRUE, na.rm = TRUE), "\n")
Graves with drinking vessels recorded: 80 
cat("Graves with human remains:", sum(cups_skulls_clean$has_remains == TRUE, na.rm = TRUE), "\n")
Graves with human remains: 226 
cat("Graves with hair pins:", sum(cups_skulls_clean$has_pin == TRUE, na.rm = TRUE), "\n")
Graves with hair pins: 16 
cat("\n")
# Missing data summary
cat("=== Missing Data ===\n")
=== Missing Data ===
cups_skulls_clean %>%
  summarise(across(everything(), ~sum(is.na(.)))) %>%
  pivot_longer(everything(), names_to = "column", values_to = "missing_count") %>%
  dplyr::filter(missing_count > 0) %>%
  arrange(desc(missing_count)) %>%
  kable()
column missing_count
orientation 262
drinking_vessel_number 222
skulls_number 213
area_of_chamber_estimated 197
dimensions 171
end_order 108
period_end_order 108
period_midpoint_order 108
chronological_order 98
earliest_component 98
latest_component 98
start_order 98
period_start_order 98
period_start_cal 98
period_end_cal 98
period_midpoint_cal 98
broad_phase 98
period_ordered 98
period 90
tomb_type_order 37
type 12

Sites Represented

sites_summary <- cups_skulls_clean %>%
  count(site, region, name = "n_graves") %>%
  arrange(desc(n_graves))

cat("Number of sites:", n_distinct(cups_skulls_clean$site), "\n")
Number of sites: 50 
cat("Sites represented:\n\n")
Sites represented:
print(sites_summary, n = 30)
# A tibble: 50 × 3
   site                                      region   n_graves
   <chr>                                     <chr>       <int>
 1 Agios Stephanos                           laconia        63
 2 volimidhia                                messenia       35
 3 Xerokambi: Agios Vasileios North Cemetery laconia        25
 4 Ayos Ioannis Papoulia                     messenia       23
 5 Koukounara Gouvalari                      messenia       14
 6 Epidavros Limera:PAL1                     laconia        10
 7 Peristeria & kokorakou                    messenia        9
 8 Voidhokoloa                               messenia        9
 9 Nihoria                                   messenia        8
10 Handhrinou Kissos                         messenia        7
11 Kaminia                                   messenia        7
12 Pellana                                   laconia         7
13 Englianos                                 messenia        6
14 Skoura: malathria                         laconia         6
15 Pellana: pelekete and Palaiokastro        laconia         5
16 Peristeri: Solakos                        laconia         5
17 Routsi                                    messenia        4
18 Sparti: Polydendron                       laconia         4
19 Sykea                                     laconia         4
20 Amyklai: Spelakia                         laconia         3
21 Amyklaion                                 laconia         3
22 Arkynes                                   laconia         3
23 Geraki: Acropolis                         laconia         3
24 Daphi: Louria                             laconia         2
25 Daphni: Louria 3                          laconia         2
26 Koukounara Livadhiti to Fities            messenia        2
27 Koukounara akones                         messenia        2
28 Psari Metsiki                             messenia        2
29 Tragana                                   messenia        2
30 Agios Georgios Voion                      laconia         1
# ℹ 20 more rows

Grave Type Distribution

HOW YOUR CHOICES AFFECT THIS: - The groupings in the “Data Cleaning” section determine what you see here - If you edit grave_type_simple to create different categories, these plots will change - Once you complete tomb_type_dictionary.csv, you can run additional analyses on tomb complexity

grave_type_summary <- cups_skulls_clean %>%
  count(region, grave_type_simple, name = "count") %>%
  arrange(region, desc(count))

print(grave_type_summary)
# A tibble: 10 × 3
   region   grave_type_simple count
   <chr>    <chr>             <int>
 1 laconia  Cist Grave           63
 2 laconia  Chamber Tomb         44
 3 laconia  Pit Grave            22
 4 laconia  Other                18
 5 laconia  Tholos                6
 6 messenia Tholos               50
 7 messenia Chamber Tomb         38
 8 messenia Pit Grave            25
 9 messenia Other                19
10 messenia Cist Grave           11
ggplot(grave_type_summary, aes(x = reorder(grave_type_simple, -count), y = count, fill = region)) +
  geom_col(position = "dodge", width = 0.7) +
  scale_fill_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  geom_text(aes(label = count), position = position_dodge(width = 0.7), vjust = -0.3, size = 4) +
  labs(
    title = "Distribution of Grave Types by Region",
    x = "Grave Type",
    y = "Number of Graves",
    fill = "Region"
  ) +
  theme_minimal(base_size = 16) +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 45, hjust = 1))

Version Author Date
01b0d46 tinalasisi 2026-02-17
8d3e1c7 tinalasisi 2026-02-17
2f9f6dc tinalasisi 2026-02-17

Burial Types

Primary vs Secondary Burials

burial_type_summary <- cups_skulls_clean %>%
  summarise(
    total = n(),
    with_primary = sum(primary_burial == TRUE, na.rm = TRUE),
    with_secondary = sum(secondary_burial == TRUE, na.rm = TRUE),
    both = sum(primary_burial == TRUE & secondary_burial == TRUE, na.rm = TRUE),
    neither = sum(primary_burial == FALSE & secondary_burial == FALSE, na.rm = TRUE),
    unknown = sum(is.na(primary_burial) | is.na(secondary_burial))
  ) %>%
  mutate(
    pct_primary = round(with_primary / total * 100, 1),
    pct_secondary = round(with_secondary / total * 100, 1),
    pct_both = round(both / total * 100, 1)
  )

print(burial_type_summary)
# A tibble: 1 × 9
  total with_primary with_secondary  both neither unknown pct_primary
  <int>        <int>          <int> <int>   <int>   <int>       <dbl>
1   296          135            118    57     100       0        45.6
# ℹ 2 more variables: pct_secondary <dbl>, pct_both <dbl>
burial_category_data_region <- cups_skulls_clean %>%
  count(region, burial_category) %>%
  arrange(region, desc(n))

ggplot(burial_category_data_region, aes(x = reorder(burial_category, -n), y = n, fill = region)) +
  geom_col(position = "dodge", width = 0.7) +
  scale_fill_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  geom_text(aes(label = n), position = position_dodge(width = 0.7), vjust = -0.3, size = 4) +
  labs(
    title = "Distribution of Burial Types by Region",
    x = "Burial Type",
    y = "Count",
    fill = "Region"
  ) +
  theme_minimal(base_size = 16) +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 45, hjust = 1))

Version Author Date
01b0d46 tinalasisi 2026-02-17
8d3e1c7 tinalasisi 2026-02-17
2f9f6dc tinalasisi 2026-02-17

Drinking Vessels Analysis

Overall Drinking Vessel Presence

dv_summary <- cups_skulls_clean %>%
  summarise(
    total_graves = n(),
    with_vessels = sum(has_drinking_vessel == TRUE, na.rm = TRUE),
    without_vessels = sum(has_drinking_vessel == FALSE, na.rm = TRUE),
    unknown_vessel_status = sum(is.na(has_drinking_vessel))
  ) %>%
  mutate(
    pct_with = round(with_vessels / total_graves * 100, 1),
    avg_vessels_recorded = round(mean(cups_skulls_clean$drinking_vessel_number, na.rm = TRUE), 2)
  )

print(dv_summary)
# A tibble: 1 × 6
  total_graves with_vessels without_vessels unknown_vessel_status pct_with
         <int>        <int>           <int>                 <int>    <dbl>
1          296           80             216                     0       27
# ℹ 1 more variable: avg_vessels_recorded <dbl>

Drinking Vessels by Grave Type

dv_by_type <- cups_skulls_clean %>%
  group_by(grave_type_simple) %>%
  summarise(
    total = n(),
    with_vessels = sum(has_drinking_vessel == TRUE, na.rm = TRUE),
    pct_with_vessels = round(with_vessels / total * 100, 1),
    avg_vessel_count = round(mean(drinking_vessel_number, na.rm = TRUE), 2)
  ) %>%
  arrange(desc(with_vessels))

print(dv_by_type)
# A tibble: 5 × 5
  grave_type_simple total with_vessels pct_with_vessels avg_vessel_count
  <chr>             <int>        <int>            <dbl>            <dbl>
1 Chamber Tomb         82           38             46.3             2.62
2 Tholos               56           21             37.5             2.65
3 Pit Grave            47            8             17               1.14
4 Other                37            7             18.9             1.29
5 Cist Grave           74            6              8.1             1.33
dv_by_type_region <- cups_skulls_clean %>%
  group_by(region, grave_type_simple) %>%
  summarise(
    total = n(),
    with_vessels = sum(has_drinking_vessel == TRUE, na.rm = TRUE),
    pct_with_vessels = round(with_vessels / total * 100, 1),
    avg_vessel_count = round(mean(drinking_vessel_number, na.rm = TRUE), 2),
    .groups = "drop"
  ) %>%
  arrange(region, desc(with_vessels))

ggplot(dv_by_type_region, aes(x = reorder(grave_type_simple, -with_vessels), y = with_vessels, fill = region)) +
  geom_col(position = "dodge", width = 0.7) +
  scale_fill_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  geom_text(aes(label = paste0(with_vessels, " (", pct_with_vessels, "%)")),
            position = position_dodge(width = 0.7), vjust = -0.3, size = 3.5) +
  labs(
    title = "Graves with Drinking Vessels by Grave Type and Region",
    x = "Grave Type",
    y = "Number of Graves with Drinking Vessels",
    fill = "Region"
  ) +
  theme_minimal(base_size = 16) +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 45, hjust = 1))

Version Author Date
01b0d46 tinalasisi 2026-02-17
8d3e1c7 tinalasisi 2026-02-17
2f9f6dc tinalasisi 2026-02-17

Drinking Vessels by Burial Type

dv_by_burial <- cups_skulls_clean %>%
  group_by(burial_category) %>%
  summarise(
    total = n(),
    with_vessels = sum(has_drinking_vessel == TRUE, na.rm = TRUE),
    pct_with_vessels = round(with_vessels / total * 100, 1),
    avg_vessel_count = round(mean(drinking_vessel_number, na.rm = TRUE), 2)
  ) %>%
  arrange(desc(with_vessels))

print(dv_by_burial)
# A tibble: 4 × 5
  burial_category          total with_vessels pct_with_vessels avg_vessel_count
  <chr>                    <int>        <int>            <dbl>            <dbl>
1 Both Primary & Secondary    57           32             56.1             3.1 
2 Secondary Only              61           20             32.8             1.83
3 Unknown/Not Recorded       100           19             19               1.88
4 Primary Only                78            9             11.5             1   
dv_by_burial_region <- cups_skulls_clean %>%
  group_by(region, burial_category) %>%
  summarise(
    total = n(),
    with_vessels = sum(has_drinking_vessel == TRUE, na.rm = TRUE),
    pct_with_vessels = round(with_vessels / total * 100, 1),
    avg_vessel_count = round(mean(drinking_vessel_number, na.rm = TRUE), 2),
    .groups = "drop"
  ) %>%
  arrange(region, desc(with_vessels))

ggplot(dv_by_burial_region, aes(x = reorder(burial_category, -with_vessels), y = with_vessels, fill = region)) +
  geom_col(position = "dodge", width = 0.7) +
  scale_fill_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  geom_text(aes(label = paste0(with_vessels, " (", pct_with_vessels, "%)")),
            position = position_dodge(width = 0.7), vjust = -0.3, size = 3.5) +
  labs(
    title = "Graves with Drinking Vessels by Burial Type and Region",
    x = "Burial Type",
    y = "Number of Graves with Drinking Vessels",
    fill = "Region"
  ) +
  theme_minimal(base_size = 16) +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 45, hjust = 1))

Version Author Date
01b0d46 tinalasisi 2026-02-17
8d3e1c7 tinalasisi 2026-02-17
2f9f6dc tinalasisi 2026-02-17

Hair Pin Analysis

Overall Hair Pin Presence

pin_summary <- cups_skulls_clean %>%
  summarise(
    total_graves = n(),
    with_pins = sum(has_pin == TRUE, na.rm = TRUE),
    without_pins = sum(has_pin == FALSE, na.rm = TRUE),
    unknown_pin_status = sum(is.na(has_pin))
  ) %>%
  mutate(pct_with = round(with_pins / total_graves * 100, 1))

print(pin_summary)
# A tibble: 1 × 5
  total_graves with_pins without_pins unknown_pin_status pct_with
         <int>     <int>        <int>              <int>    <dbl>
1          296        16          280                  0      5.4

Hair Pins by Region

pin_by_region <- cups_skulls_clean %>%
  group_by(region) %>%
  summarise(
    total = n(),
    with_pins = sum(has_pin == TRUE, na.rm = TRUE),
    pct_with_pins = round(with_pins / total * 100, 1),
    .groups = "drop"
  )

ggplot(pin_by_region, aes(x = region, y = with_pins, fill = region)) +
  geom_col(width = 0.6) +
  scale_fill_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  geom_text(aes(label = paste0(with_pins, " (", pct_with_pins, "%)")), vjust = -0.3, size = 4) +
  labs(
    title = "Graves with Hair Pins by Region",
    x = "Region",
    y = "Number of Graves with Hair Pins",
    fill = "Region"
  ) +
  theme_minimal(base_size = 16) +
  theme(legend.position = "bottom")

Secondary Entombment and Hair Pins

Secondary Burial x Hair Pin Presence by Region

Note: Hair pins are present in only a small number of graves (n=16), so results should be interpreted with caution.

secondary_pins <- cups_skulls_clean %>%
  dplyr::filter(!is.na(secondary_burial) & !is.na(has_pin)) %>%
  group_by(region, secondary_burial, has_pin) %>%
  summarise(count = n(), .groups = "drop") %>%
  mutate(
    secondary_label = ifelse(secondary_burial, "Secondary Present", "Secondary Absent"),
    pin_label = ifelse(has_pin, "With Pin", "No Pin")
  )

# Summary table
secondary_pins_wide <- secondary_pins %>%
  select(region, secondary_label, pin_label, count) %>%
  pivot_wider(names_from = pin_label, values_from = count, values_fill = 0)

kable(secondary_pins_wide)
region secondary_label No Pin With Pin
laconia Secondary Absent 93 4
laconia Secondary Present 53 3
messenia Secondary Absent 76 5
messenia Secondary Present 58 4
secondary_pins_pct <- cups_skulls_clean %>%
  dplyr::filter(!is.na(secondary_burial) & !is.na(has_pin)) %>%
  group_by(region, secondary_burial) %>%
  summarise(
    total = n(),
    with_pin = sum(has_pin == TRUE),
    pct_with_pin = round(with_pin / total * 100, 1),
    .groups = "drop"
  ) %>%
  mutate(secondary_label = ifelse(secondary_burial, "Secondary Present", "Secondary Absent"))

ggplot(secondary_pins_pct, aes(x = secondary_label, y = pct_with_pin, fill = region)) +
  geom_col(position = "dodge", width = 0.7) +
  scale_fill_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  geom_text(aes(label = paste0(with_pin, "/", total, "\n(", pct_with_pin, "%)")),
            position = position_dodge(width = 0.7), vjust = -0.3, size = 3.5) +
  labs(
    title = "Hair Pin Presence by Secondary Burial Status and Region",
    x = "Secondary Burial Status",
    y = "% of Graves with Hair Pins",
    fill = "Region"
  ) +
  theme_minimal(base_size = 16) +
  theme(legend.position = "bottom")

# Fisher's exact test by region (appropriate for small cell counts)
for (reg in unique(cups_skulls_clean$region)) {
  region_data <- cups_skulls_clean %>%
    dplyr::filter(region == reg & !is.na(secondary_burial) & !is.na(has_pin))

  contingency <- table(region_data$secondary_burial, region_data$has_pin)

  cat("\n===", toupper(reg), "===\n")
  print(contingency)

  if (all(dim(contingency) == c(2, 2))) {
    fisher_result <- fisher.test(contingency)
    cat("Fisher's exact test p-value:", round(fisher_result$p.value, 4), "\n")
    cat("Odds ratio:", round(fisher_result$estimate, 3), "\n")
  } else {
    cat("Insufficient variation for statistical test\n")
  }
}

=== LACONIA ===
       
        FALSE TRUE
  FALSE    93    4
  TRUE     53    3
Fisher's exact test p-value: 0.7071 
Odds ratio: 1.314 

=== MESSENIA ===
       
        FALSE TRUE
  FALSE    76    5
  TRUE     58    4
Fisher's exact test p-value: 1 
Odds ratio: 1.048 

Drinking Vessels, Secondary Burial, and Hair Pins

Three-Way Cross-Tabulation by Region

three_way <- cups_skulls_clean %>%
  dplyr::filter(!is.na(has_drinking_vessel) & !is.na(secondary_burial) & !is.na(has_pin)) %>%
  group_by(region, has_drinking_vessel, secondary_burial, has_pin) %>%
  summarise(count = n(), .groups = "drop") %>%
  mutate(
    vessel_label = ifelse(has_drinking_vessel, "Vessel Present", "No Vessel"),
    secondary_label = ifelse(secondary_burial, "Secondary Present", "Secondary Absent"),
    pin_label = ifelse(has_pin, "With Pin", "No Pin")
  )

kable(three_way %>% select(region, vessel_label, secondary_label, pin_label, count))
region vessel_label secondary_label pin_label count
laconia No Vessel Secondary Absent No Pin 84
laconia No Vessel Secondary Absent With Pin 2
laconia No Vessel Secondary Present No Pin 36
laconia No Vessel Secondary Present With Pin 2
laconia Vessel Present Secondary Absent No Pin 9
laconia Vessel Present Secondary Absent With Pin 2
laconia Vessel Present Secondary Present No Pin 17
laconia Vessel Present Secondary Present With Pin 1
messenia No Vessel Secondary Absent No Pin 63
messenia No Vessel Secondary Absent With Pin 1
messenia No Vessel Secondary Present No Pin 28
messenia Vessel Present Secondary Absent No Pin 13
messenia Vessel Present Secondary Absent With Pin 4
messenia Vessel Present Secondary Present No Pin 30
messenia Vessel Present Secondary Present With Pin 4
three_way_summary <- cups_skulls_clean %>%
  dplyr::filter(!is.na(has_drinking_vessel) & !is.na(secondary_burial) & !is.na(has_pin)) %>%
  mutate(
    vessel_label = ifelse(has_drinking_vessel, "Vessel Present", "No Vessel"),
    secondary_label = ifelse(secondary_burial, "Secondary Present", "Secondary Absent"),
    pin_label = ifelse(has_pin, "With Pin", "No Pin")
  ) %>%
  count(region, vessel_label, secondary_label, pin_label)

ggplot(three_way_summary, aes(x = secondary_label, y = n, fill = pin_label)) +
  geom_col(position = "dodge", width = 0.7) +
  scale_fill_manual(values = c("With Pin" = colors$pin, "No Pin" = "grey70")) +
  facet_grid(vessel_label ~ region) +
  geom_text(aes(label = n), position = position_dodge(width = 0.7), vjust = -0.3, size = 3) +
  labs(
    title = "Hair Pins by Secondary Burial Status and Drinking Vessel Presence",
    subtitle = "Rows: Drinking Vessel status | Columns: Region",
    x = "Secondary Burial Status",
    y = "Count",
    fill = "Hair Pin"
  ) +
  theme_minimal(base_size = 16) +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 45, hjust = 1))

Hair Pins and Chamber Size

Do graves with hair pins tend to be larger?

pins_area_data <- cups_skulls_clean %>%
  dplyr::filter(!is.na(area_of_chamber_estimated) & !is.na(has_pin))

cat("Graves with area AND pin status:", nrow(pins_area_data), "\n")
Graves with area AND pin status: 99 
pins_area_data %>%
  group_by(has_pin) %>%
  summarise(
    n = n(),
    mean_area = round(mean(area_of_chamber_estimated, na.rm = TRUE), 2),
    median_area = round(median(area_of_chamber_estimated, na.rm = TRUE), 2),
    sd_area = round(sd(area_of_chamber_estimated, na.rm = TRUE), 2),
    .groups = "drop"
  ) %>%
  mutate(pin_label = ifelse(has_pin, "With Pin", "No Pin")) %>%
  kable()
has_pin n mean_area median_area sd_area pin_label
FALSE 92 18.41 12.26 17.89 No Pin
TRUE 7 27.57 22.90 26.72 With Pin
ggplot(pins_area_data, aes(x = ifelse(has_pin, "With Pin", "No Pin"),
                            y = area_of_chamber_estimated, fill = region)) +
  geom_boxplot(alpha = 0.7, position = "dodge") +
  scale_fill_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  labs(
    title = "Chamber Area by Hair Pin Presence and Region",
    x = "Hair Pin Status",
    y = "Chamber Area Estimated (m\u00b2)",
    fill = "Region"
  ) +
  theme_minimal(base_size = 16) +
  theme(legend.position = "bottom")

# Wilcoxon test (non-parametric, appropriate for potentially non-normal area data)
for (reg in unique(pins_area_data$region)) {
  region_data <- pins_area_data %>% dplyr::filter(region == reg)
  with_pin <- region_data %>% dplyr::filter(has_pin == TRUE) %>% pull(area_of_chamber_estimated)
  without_pin <- region_data %>% dplyr::filter(has_pin == FALSE) %>% pull(area_of_chamber_estimated)

  cat("\n===", toupper(reg), "===\n")
  cat("With pin: n =", length(with_pin), ", median =", round(median(with_pin), 2), "\n")
  cat("Without pin: n =", length(without_pin), ", median =", round(median(without_pin), 2), "\n")

  if (length(with_pin) >= 2 & length(without_pin) >= 2) {
    test_result <- wilcox.test(with_pin, without_pin)
    cat("Wilcoxon rank-sum test p-value:", round(test_result$p.value, 4), "\n")
  } else {
    cat("Too few observations for statistical test\n")
  }
}

=== LACONIA ===
With pin: n = 2 , median = 56.2 
Without pin: n = 29 , median = 8.58 
Wilcoxon rank-sum test p-value: 0.0258 

=== MESSENIA ===
With pin: n = 5 , median = 19.64 
Without pin: n = 63 , median = 12.57 
Wilcoxon rank-sum test p-value: 0.8786 

Skeletal Remains Analysis

Remains Distribution

remains_summary <- cups_skulls_clean %>%
  summarise(
    total_graves = n(),
    with_remains = sum(has_remains == TRUE, na.rm = TRUE),
    without_remains = sum(has_remains == FALSE, na.rm = TRUE),
    unknown_remains = sum(is.na(has_remains))
  ) %>%
  mutate(pct_with_remains = round(with_remains / total_graves * 100, 1))

print(remains_summary)
# A tibble: 1 × 5
  total_graves with_remains without_remains unknown_remains pct_with_remains
         <int>        <int>           <int>           <int>            <dbl>
1          296          226              70               0             76.4

Skulls Recorded

skulls_stats <- cups_skulls_clean %>%
  dplyr::filter(!is.na(skulls_number)) %>%
  summarise(
    graves_with_skull_count = n(),
    total_skulls = sum(skulls_number, na.rm = TRUE),
    avg_skulls = round(mean(skulls_number, na.rm = TRUE), 2),
    min_skulls = min(skulls_number, na.rm = TRUE),
    max_skulls = max(skulls_number, na.rm = TRUE)
  )

print(skulls_stats)
# A tibble: 1 × 5
  graves_with_skull_count total_skulls avg_skulls min_skulls max_skulls
                    <int>        <dbl>      <dbl>      <dbl>      <dbl>
1                      83          350       4.22          1         47
skulls_by_count <- cups_skulls_clean %>%
  dplyr::filter(!is.na(skulls_number)) %>%
  count(skulls_number, name = "graves") %>%
  arrange(skulls_number)

print(skulls_by_count)
# A tibble: 16 × 2
   skulls_number graves
           <dbl>  <int>
 1             1     48
 2             2     10
 3             3      5
 4             4      2
 5             5      1
 6             6      3
 7             7      1
 8             9      2
 9            10      2
10            12      2
11            14      1
12            15      1
13            20      2
14            24      1
15            27      1
16            47      1

Cross-Analysis: Drinking Vessels and Remains

Do graves with drinking vessels tend to have remains?

vessels_remains_cross <- cups_skulls_clean %>%
  dplyr::filter(!is.na(has_drinking_vessel) & !is.na(has_remains)) %>%
  count(has_drinking_vessel, has_remains) %>%
  pivot_wider(names_from = has_remains, values_from = n, values_fill = 0) %>%
  rename(no_remains = `FALSE`, with_remains = `TRUE`)

rownames(vessels_remains_cross) <- c("No Drinking Vessels", "With Drinking Vessels")
print(vessels_remains_cross)
# A tibble: 2 × 3
  has_drinking_vessel no_remains with_remains
* <lgl>                    <int>        <int>
1 FALSE                       55          161
2 TRUE                        15           65
# Calculate cross-tabulation with percentages
cross_analysis <- cups_skulls_clean %>%
  dplyr::filter(!is.na(has_drinking_vessel) & !is.na(has_remains)) %>%
  group_by(has_drinking_vessel) %>%
  summarise(
    total = n(),
    with_remains = sum(has_remains == TRUE, na.rm = TRUE),
    pct_with_remains = round(with_remains / total * 100, 1)
  ) %>%
  mutate(vessel_status = ifelse(has_drinking_vessel, "With Drinking Vessels", "Without Drinking Vessels"))

kable(select(cross_analysis, vessel_status, total, with_remains, pct_with_remains))
vessel_status total with_remains pct_with_remains
Without Drinking Vessels 216 161 74.5
With Drinking Vessels 80 65 81.2

Drinking Vessels and Skull Count

vessels_skulls_data <- cups_skulls_clean %>%
  dplyr::filter(!is.na(drinking_vessel_number) & !is.na(skulls_number)) %>%
  mutate(has_vessels_label = ifelse(has_drinking_vessel, "With Vessels", "No Vessels"))

ggplot(vessels_skulls_data, aes(x = drinking_vessel_number, y = skulls_number, color = has_vessels_label)) +
  geom_jitter(size = 3, alpha = 0.6, width = 0.2, height = 0.2) +
  geom_smooth(method = "lm", se = FALSE, alpha = 0.2) +
  labs(
    title = "Relationship: Drinking Vessels vs Skull Count",
    x = "Number of Drinking Vessels Recorded",
    y = "Number of Skulls",
    color = ""
  ) +
  theme_minimal(base_size = 16) +
  theme(legend.position = "bottom")

Version Author Date
2f9f6dc tinalasisi 2026-02-17

Regional Analysis

Laconia vs Other Regions

regional_summary <- cups_skulls_clean %>%
  group_by(region) %>%
  summarise(
    total_graves = n(),
    with_drinking_vessels = sum(has_drinking_vessel == TRUE, na.rm = TRUE),
    pct_with_vessels = round(with_drinking_vessels / total_graves * 100, 1),
    with_remains = sum(has_remains == TRUE, na.rm = TRUE),
    pct_with_remains = round(with_remains / total_graves * 100, 1),
    avg_skulls = round(mean(skulls_number, na.rm = TRUE), 2)
  )

print(regional_summary)
# A tibble: 2 × 7
  region   total_graves with_drinking_vessels pct_with_vessels with_remains
  <chr>           <int>                 <int>            <dbl>        <int>
1 laconia           153                    29             19            127
2 messenia          143                    51             35.7           99
# ℹ 2 more variables: pct_with_remains <dbl>, avg_skulls <dbl>

Chronological Overview

Periods Represented

period_summary <- cups_skulls_clean %>%
  mutate(
    period_category = case_when(
      str_detect(tolower(period), "mh") ~ "Middle Helladic",
      str_detect(tolower(period), "lh\\s*i(?!i)")  | str_detect(tolower(period), "lh i-") ~ "LH I-II",
      str_detect(tolower(period), "lh\\s*ii") & !str_detect(tolower(period), "lh\\s*ii") ~ "LH II",
      str_detect(tolower(period), "lh\\s*iii") ~ "LH III",
      str_detect(tolower(period), "lh") ~ "Late Helladic (general)",
      is.na(period) | period == "" ~ "Unrecorded",
      TRUE ~ "Other"
    )
  ) %>%
  count(period_category, name = "graves") %>%
  arrange(desc(graves))

print(period_summary)
# A tibble: 5 × 2
  period_category         graves
  <chr>                    <int>
1 Unrecorded                  90
2 LH III                      88
3 LH I-II                     45
4 Middle Helladic             39
5 Late Helladic (general)     34

Period Span Analysis

The following chart shows how each period code in the dataset maps onto the Bronze Age calendar. The x-axis is proportional to actual calendar years (BCE), so visually wider rows represent longer date ranges.

  • Range periods (e.g., “LHIIA–LHIIIB”) appear as horizontal segments. These indicate that the scholarly record only places the grave within a span of phases — the grave could date to any point within that range.
  • Single-period codes (e.g., “LHIIIA2”) appear as single points — more precisely dated graves.
  • The dashed vertical lines mark the start of each major phase. Tick labels that combine multiple codes (e.g., “LHIII / LHIIIA / LHIIIA1”) indicate phase boundaries that coincide on the calendar.

For temporal analyses: Graves with wide-spanning period codes (long bars) are assigned to the broad phase of their earliest endpoint. This is the most conservative choice but means a grave recorded as “MH–LHIIIC” (spanning nearly 1,000 years) is counted as an MH-phase grave, even though it may represent a much later context.

# Identify single-component periods and attach calendar years
single_periods <- period_dict %>%
  dplyr::filter(earliest_component == latest_component) %>%
  select(period_code, chronological_order) %>%
  arrange(chronological_order) %>%
  distinct(period_code, .keep_all = TRUE) %>%
  dplyr::filter(period_code != "LH II") %>%
  left_join(component_cal %>% select(component, cal_start_bce),
            by = c("period_code" = "component"))

# Get all period codes in the data with their calendar year spans
period_spans <- cups_skulls_clean %>%
  dplyr::filter(!is.na(period_start_cal) & !is.na(period_end_cal)) %>%
  distinct(period, period_start_cal, period_end_cal) %>%
  mutate(
    is_range = period_start_cal != period_end_cal,
    span_width = period_start_cal - period_end_cal  # BCE: larger start = earlier
  ) %>%
  arrange(desc(period_start_cal), period_end_cal)

# Count graves per period code
period_counts <- cups_skulls_clean %>%
  dplyr::filter(!is.na(period_start_cal)) %>%
  count(period, name = "n_graves")

period_spans <- period_spans %>%
  left_join(period_counts, by = "period") %>%
  mutate(period_label = paste0(period, " (n=", n_graves, ")")) %>%
  mutate(period_label = factor(period_label,
    levels = rev(mutate(arrange(., desc(period_start_cal), period_end_cal),
                        period_label = paste0(period, " (n=", n_graves, ")"))$period_label)))

# Collapse period codes that share the same calendar position into one label
# (e.g. LH and LHI both start at 1600 BCE — combine as "LH / LHI")
single_periods_unique <- dplyr::filter(single_periods, !is.na(cal_start_bce)) %>%
  group_by(cal_start_bce) %>%
  summarise(period_code = paste(period_code, collapse = " / "), .groups = "drop")

ggplot(period_spans) +
  geom_segment(
    data = dplyr::filter(period_spans, is_range),
    aes(x = period_start_cal, xend = period_end_cal,
        y = period_label, yend = period_label),
    linewidth = 2, color = colors$drinking_vessel, alpha = 0.7
  ) +
  geom_point(
    aes(x = period_start_cal, y = period_label),
    size = 3, color = colors$drinking_vessel
  ) +
  geom_point(
    data = dplyr::filter(period_spans, is_range),
    aes(x = period_end_cal, y = period_label),
    size = 3, color = colors$drinking_vessel
  ) +
  geom_vline(
    data = single_periods_unique,
    aes(xintercept = cal_start_bce),
    linetype = "dashed", alpha = 0.3
  ) +
  scale_x_reverse(
    breaks = single_periods_unique$cal_start_bce,
    labels = single_periods_unique$period_code
  ) +
  labs(
    title = "Period Spans in the Dataset",
    subtitle = "Each row is a period code used in the data. X-axis is proportional calendar time (BCE).",
    x = "Calendar Date (BCE)",
    y = "Period Code (n = number of graves)"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5, size = 10),
    axis.text.y = element_text(size = 9),
    panel.grid.major.y = element_line(color = "grey90"),
    panel.grid.minor = element_blank()
  )

cat("=== Individual chronological components (in order) ===\n\n")
=== Individual chronological components (in order) ===
for (i in 1:nrow(single_periods)) {
  cat(sprintf("  %2d. %s\n", i, single_periods$period_code[i]))
}
   1. MH
   2. MHII
   3. MHIII
   4. MHIIIB
   5. LH
   6. LHI
   7. LHII
   8. LHIIA
   9. LHIIB
  10. LHIII
  11. LHIIIA
  12. LHIIIA1
  13. LHIIIA2
  14. LHIIIB
  15. LHIIIC
cat("\n=== Summary ===\n")

=== Summary ===
cat("Single-period codes:", sum(!period_spans$is_range), "\n")
Single-period codes: 0 
cat("Range-period codes:", sum(period_spans$is_range), "\n")
Range-period codes: 53 
cat("Total period codes in data:", nrow(period_spans), "\n")
Total period codes in data: 53 
cat("\n=== Range periods spanning 3+ components ===\n")

=== Range periods spanning 3+ components ===
wide_spans <- period_spans %>%
  dplyr::filter(span_width > 10) %>%
  arrange(desc(span_width))
if (nrow(wide_spans) > 0) {
  for (i in 1:nrow(wide_spans)) {
    cat(sprintf("  %s: spans from order %g to %g (width = %g, n = %d graves)\n",
        wide_spans$period[i], wide_spans$period_start_order[i],
        wide_spans$period_end_order[i], wide_spans$span_width[i],
        wide_spans$n_graves[i]))
  }
}

Individual Graves by Period and Region

Each line represents a single grave, spanning its period range. Graves are colored by whether hair pins were found. This shows how graves cluster across time and whether pin-bearing graves concentrate in particular periods.

# Prepare individual grave data with calendar year spans
graves_time <- cups_skulls_clean %>%
  dplyr::filter(!is.na(period_start_cal) & !is.na(period_end_cal) & !is.na(has_pin)) %>%
  mutate(pin_label = ifelse(has_pin, "With Pin", "No Pin")) %>%
  # Order graves: earlier start first (higher BCE = earlier), then narrower range
  arrange(region, desc(period_start_cal), period_end_cal) %>%
  group_by(region) %>%
  mutate(grave_rank = row_number()) %>%
  ungroup()

# Use single_periods with cal years (already built in period-span-chart chunk)
sp_cal <- dplyr::filter(single_periods, !is.na(cal_start_bce))

ggplot(graves_time) +
  geom_segment(
    aes(x = period_start_cal, xend = period_end_cal,
        y = grave_rank, yend = grave_rank,
        color = pin_label),
    linewidth = 0.8, alpha = 0.7
  ) +
  geom_point(
    data = dplyr::filter(graves_time, period_start_cal == period_end_cal),
    aes(x = period_start_cal, y = grave_rank, color = pin_label),
    size = 1.5, alpha = 0.7
  ) +
  scale_color_manual(values = c("With Pin" = colors$pin, "No Pin" = colors$drinking_vessel)) +
  geom_vline(
    data = sp_cal,
    aes(xintercept = cal_start_bce),
    linetype = "dashed", alpha = 0.2
  ) +
  scale_x_reverse(
    breaks = sp_cal$cal_start_bce,
    labels = sp_cal$period_code
  ) +
  facet_wrap(~region, ncol = 1, scales = "free_y") +
  labs(
    title = "Individual Graves by Period Span and Region",
    subtitle = "Each line = one grave. X-axis is proportional calendar time (BCE). Colored by hair pin presence.",
    x = "Calendar Date (BCE)",
    y = "Graves (ordered by period)",
    color = "Hair Pin"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    legend.position = "bottom",
    axis.text.x = element_text(angle = 45, hjust = 1, size = 11),
    axis.text.y = element_blank(),
    axis.ticks.y = element_blank(),
    panel.grid.major.y = element_blank(),
    panel.grid.minor.y = element_blank()
  )

Temporal Analyses

The purpose of this section is to examine whether funerary practices — burial type, presence of drinking vessels, hair pins, and tomb size — changed over the course of the Bronze Age and whether those changes differed between Laconia and Messenia.

How Graves Are Assigned to Phases

Graves in this dataset are often given date ranges rather than a single period (e.g., a grave might be recorded as “LHIIA–LHIIIB,” meaning it could date anywhere within that span). For the temporal plots below, each grave is assigned to the 7-phase broad phase scheme (MH → SM) based on its earliest period component. A grave dated LHIIA–LHIIIB, for instance, is counted in “LH II” — its earliest possible phase.

What this means for interpretation: A grave assigned to an early phase may actually be a later burial; the assignment is conservative (if anything, it understates change over time). Graves spanning many centuries are the most ambiguous, and their placement in the earliest phase is a simplification.

Excluded graves: Graves whose period code could not be matched to any known component (i.e., those with unusual or incomplete period designations) receive NA for broad_phase and are excluded from all temporal plots. The table below shows how many graves fall into each phase, including the NA count. Additionally, graves recorded only as the generic undifferentiated “LH” code are assigned to “LH I” (the earliest LH sub-phase), which may slightly inflate that category.

Two display options are shown for each plot:

  • Option A — Ordered categorical x-axis (7 broad phases as labels, equal spacing)
  • Option B — Proportional calendar x-axis (BCE years, bar widths scaled to actual phase duration)
cat("Graves assigned to broad phases:\n")
Graves assigned to broad phases:
print(table(cups_skulls_clean$broad_phase, useNA = "ifany"))

     MH    LH I   LH II LH IIIA LH IIIB LH IIIC      SM    <NA> 
     37      47      46      60       2       6       0      98 

Burial Type Composition Over Time

These charts show whether the mix of primary and secondary burials changed over time within each region. Primary burial refers to an intact, articulated interment of a body; secondary burial refers to the later re-deposition of skeletal remains (typically disarticulated bones moved to make room for new burials). A single grave can contain both.

Each grave is classified into one of four mutually exclusive categories: - Primary only — evidence for primary burial, no secondary - Both — evidence for both primary and secondary burials - Secondary only — evidence for secondary burial only - Neither/Unknown — no burial type recorded

Bars show 100% of graves dated to that phase in that region; n= total graves in the phase. The “Neither/Unknown” segment is important: it reflects gaps in the recording — graves where burial type was not noted, not necessarily graves without burials. A large “Neither/Unknown” segment in a phase indicates the available data for that period is limited.

Option A: Broad Phase Categories — Proportions

burial_cat_data <- cups_skulls_clean %>%
  dplyr::filter(!is.na(broad_phase)) %>%
  mutate(burial_cat = case_when(
    primary_burial == TRUE & secondary_burial == TRUE ~ "Both",
    primary_burial == TRUE                            ~ "Primary only",
    secondary_burial == TRUE                          ~ "Secondary only",
    TRUE                                              ~ "Neither/Unknown"
  ),
  burial_cat = factor(burial_cat,
    levels = c("Primary only", "Both", "Secondary only", "Neither/Unknown"))) %>%
  group_by(region, broad_phase, burial_cat) %>%
  summarise(n = n(), .groups = "drop") %>%
  group_by(region, broad_phase) %>%
  mutate(total = sum(n), pct = n / total * 100) %>%
  ungroup()

phase_totals_burial <- burial_cat_data %>%
  distinct(region, broad_phase, total)

ggplot(burial_cat_data, aes(x = broad_phase, y = pct, fill = burial_cat)) +
  geom_col(position = "stack", width = 0.7) +
  geom_text(aes(label = ifelse(pct >= 8, paste0(round(pct), "%"), "")),
            position = position_stack(vjust = 0.5), size = 3.5, color = "white") +
  geom_text(data = phase_totals_burial,
            aes(x = broad_phase, y = 104, label = paste0("n=", total)),
            inherit.aes = FALSE, vjust = 0, size = 3.5) +
  scale_fill_manual(values = c(
    "Primary only"    = colors$drinking_vessel,
    "Both"            = "#44AA99",
    "Secondary only"  = colors$skulls,
    "Neither/Unknown" = "grey80"
  )) +
  scale_y_continuous(limits = c(0, 113), labels = function(x) paste0(x, "%")) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Burial Type Composition Over Time by Region",
    subtitle = "Option A: each bar = 100% of graves in that phase; n= total graves",
    x = "Broad Phase", y = "% of Graves in Phase", fill = "Burial Type"
  ) +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 30, hjust = 1))

Option A: Raw Counts

ggplot(burial_cat_data, aes(x = broad_phase, y = n, fill = burial_cat)) +
  geom_col(position = "stack", width = 0.7) +
  geom_text(aes(label = ifelse(n >= 2, n, "")),
            position = position_stack(vjust = 0.5), size = 3.5, color = "white") +
  scale_fill_manual(values = c(
    "Primary only"    = colors$drinking_vessel,
    "Both"            = "#44AA99",
    "Secondary only"  = colors$skulls,
    "Neither/Unknown" = "grey80"
  )) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Burial Type Counts Over Time by Region",
    subtitle = "Option A: raw number of graves per burial type category per phase",
    x = "Broad Phase", y = "Number of Graves", fill = "Burial Type"
  ) +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 30, hjust = 1))

Option B: Proportional Calendar Time (BCE) — Proportions

burial_cat_b <- burial_cat_data %>%
  left_join(phase_cal, by = "broad_phase") %>%
  mutate(bar_width = phase_duration * 0.75)

phase_totals_burial_b <- burial_cat_b %>%
  distinct(region, broad_phase, total, phase_mid_bce)

ggplot(burial_cat_b, aes(x = phase_mid_bce, y = pct, fill = burial_cat, width = bar_width)) +
  geom_col(position = "stack") +
  geom_text(aes(label = ifelse(pct >= 8, paste0(round(pct), "%"), "")),
            position = position_stack(vjust = 0.5), size = 3.5, color = "white") +
  geom_text(data = phase_totals_burial_b,
            aes(x = phase_mid_bce, y = 104, label = paste0("n=", total)),
            inherit.aes = FALSE, vjust = 0, size = 3.5) +
  scale_fill_manual(values = c(
    "Primary only"    = colors$drinking_vessel,
    "Both"            = "#44AA99",
    "Secondary only"  = colors$skulls,
    "Neither/Unknown" = "grey80"
  )) +
  scale_y_continuous(limits = c(0, 113), labels = function(x) paste0(x, "%")) +
  scale_x_reverse(
    breaks = phase_cal$phase_start_bce,
    labels = paste0(phase_cal$phase_start_bce, "\n(", phase_cal$broad_phase, ")")
  ) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Burial Type Composition Over Time by Region",
    subtitle = "Option B: bar widths proportional to phase duration; each bar = 100% of graves in that phase",
    x = "Calendar Date BCE (phase start)", y = "% of Graves in Phase", fill = "Burial Type"
  ) +
  theme(legend.position = "bottom")

Option B: Raw Counts

ggplot(burial_cat_b, aes(x = phase_mid_bce, y = n, fill = burial_cat, width = bar_width)) +
  geom_col(position = "stack") +
  geom_text(aes(label = ifelse(n >= 2, n, "")),
            position = position_stack(vjust = 0.5), size = 3.5, color = "white") +
  scale_fill_manual(values = c(
    "Primary only"    = colors$drinking_vessel,
    "Both"            = "#44AA99",
    "Secondary only"  = colors$skulls,
    "Neither/Unknown" = "grey80"
  )) +
  scale_x_reverse(
    breaks = phase_cal$phase_start_bce,
    labels = paste0(phase_cal$phase_start_bce, "\n(", phase_cal$broad_phase, ")")
  ) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Burial Type Counts Over Time by Region",
    subtitle = "Option B: raw counts; bar widths proportional to phase duration",
    x = "Calendar Date BCE (phase start)", y = "Number of Graves", fill = "Burial Type"
  ) +
  theme(legend.position = "bottom")


Regional Comparison: Secondary Burial Over Time

These plots isolate the secondary burial rate — the proportion of graves in each phase with evidence for secondary burial — and compare Laconia and Messenia directly. The two regions are stacked top-to-bottom to make the x-axis comparable at a glance.

What to look for: Do the two regions track together (suggesting pan-regional practice) or diverge (suggesting regional differences in how and when secondary burial became common)? Phases with very small n (shown in the labels) should be interpreted cautiously — a single grave shifts the percentage dramatically.

Labels show “X% (N secondary of M total graves in that phase).”

Option A: Broad Phase Categories — Proportions

secondary_phase <- cups_skulls_clean %>%
  dplyr::filter(!is.na(broad_phase) & !is.na(secondary_burial)) %>%
  group_by(region, broad_phase) %>%
  summarise(
    n_graves    = n(),
    n_secondary = sum(secondary_burial == TRUE),
    pct_secondary = round(n_secondary / n_graves * 100, 1),
    .groups = "drop"
  )

ggplot(secondary_phase, aes(x = broad_phase, y = pct_secondary, fill = region)) +
  geom_col(width = 0.7) +
  geom_text(aes(label = paste0(round(pct_secondary), "%\n(", n_secondary, " of ", n_graves, ")")),
            vjust = -0.3, size = 3.5) +
  scale_fill_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  scale_y_continuous(limits = c(0, 130)) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Secondary Burial Frequency Over Time: Laconia vs Messenia",
    subtitle = "Option A: % of graves in each phase with secondary burial; label = secondary of total",
    x = "Broad Phase", y = "% of Graves with Secondary Burial", fill = "Region"
  ) +
  theme(legend.position = "none", axis.text.x = element_text(angle = 30, hjust = 1))

Option A: Raw Counts

secondary_raw_stack <- secondary_phase %>%
  mutate(n_other = n_graves - n_secondary) %>%
  pivot_longer(cols = c(n_secondary, n_other),
               names_to = "burial_type", values_to = "count") %>%
  mutate(burial_type = ifelse(burial_type == "n_secondary", "Secondary Burial", "Other Graves"))

ggplot(secondary_raw_stack, aes(x = broad_phase, y = count, fill = burial_type)) +
  geom_col(position = "stack", width = 0.7) +
  geom_text(aes(label = ifelse(count > 0, count, "")),
            position = position_stack(vjust = 0.5), size = 3.5, color = "white") +
  scale_fill_manual(values = c("Secondary Burial" = colors$skulls, "Other Graves" = "grey80")) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Secondary Burial Counts Over Time: Laconia vs Messenia",
    subtitle = "Option A: each bar = total graves in phase; highlighted segment = graves with secondary burial",
    x = "Broad Phase", y = "Number of Graves", fill = ""
  ) +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 30, hjust = 1))

Option B: Proportional Calendar Time (BCE) — Proportions

secondary_phase_b <- secondary_phase %>%
  left_join(phase_cal, by = "broad_phase") %>%
  mutate(bar_width = phase_duration * 0.8)

ggplot(secondary_phase_b,
       aes(x = phase_mid_bce, y = pct_secondary, fill = region, width = bar_width)) +
  geom_col(position = "identity") +
  geom_text(aes(label = paste0(round(pct_secondary), "%\n(", n_secondary, " of ", n_graves, ")")),
            vjust = -0.3, size = 3.5) +
  scale_fill_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  scale_y_continuous(limits = c(0, 130)) +
  scale_x_reverse(
    breaks = phase_cal$phase_start_bce,
    labels = paste0(phase_cal$phase_start_bce, "\n(", phase_cal$broad_phase, ")")
  ) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Secondary Burial Frequency Over Time: Laconia vs Messenia",
    subtitle = "Option B: bar widths proportional to phase duration; % of graves in that phase with secondary burial",
    x = "Calendar Date BCE (phase start)", y = "% of Graves with Secondary Burial", fill = "Region"
  ) +
  theme(legend.position = "none")

Option B: Raw Counts

secondary_raw_stack_b <- secondary_phase_b %>%
  mutate(n_other = n_graves - n_secondary) %>%
  pivot_longer(cols = c(n_secondary, n_other),
               names_to = "burial_type", values_to = "count") %>%
  mutate(burial_type = ifelse(burial_type == "n_secondary", "Secondary Burial", "Other Graves"))

ggplot(secondary_raw_stack_b,
       aes(x = phase_mid_bce, y = count, fill = burial_type, width = bar_width)) +
  geom_col(position = "stack") +
  geom_text(aes(label = ifelse(count > 0, count, "")),
            position = position_stack(vjust = 0.5), size = 3.5, color = "white") +
  scale_fill_manual(values = c("Secondary Burial" = colors$skulls, "Other Graves" = "grey80")) +
  scale_x_reverse(
    breaks = phase_cal$phase_start_bce,
    labels = paste0(phase_cal$phase_start_bce, "\n(", phase_cal$broad_phase, ")")
  ) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Secondary Burial Counts Over Time: Laconia vs Messenia",
    subtitle = "Option B: raw counts; bar widths proportional to phase duration",
    x = "Calendar Date BCE (phase start)", y = "Number of Graves", fill = ""
  ) +
  theme(legend.position = "bottom")


All Burial Variables Over Time

These line charts bring together the three main assemblage attributes — drinking vessels, secondary burial, and hair pins — on a single timeline, allowing comparison of their trends. Point size reflects the total number of graves in that phase and region, so visually smaller points signal phases with sparse data where percentages are less reliable.

Important: The three variables are not mutually exclusive — a grave may have drinking vessels and secondary burial and a hair pin. The lines therefore do not add up to 100%. Each line independently answers: “In what share of graves in this phase is this attribute present?”

Excluded graves: Graves with NA for broad_phase (unmatched period codes) are not plotted. This is typically a small number; see the phase coverage table above.

Interpreting the raw count companion: The raw count version makes the denominators visible. When a percentage looks high or low, checking the raw counts reveals whether it is based on 2 graves or 20. Phases where total graves (point size) are very small should be treated as preliminary observations, not firm patterns.

Option A: Broad Phase Categories — Proportions

multi_phase_data <- cups_skulls_clean %>%
  dplyr::filter(!is.na(broad_phase)) %>%
  group_by(region, broad_phase) %>%
  summarise(
    n_graves          = n(),
    n_drinking_vessel = sum(has_drinking_vessel == TRUE, na.rm = TRUE),
    n_secondary       = sum(secondary_burial    == TRUE, na.rm = TRUE),
    n_pin             = sum(has_pin             == TRUE, na.rm = TRUE),
    pct_drinking_vessel = round(n_drinking_vessel / n_graves * 100, 1),
    pct_secondary       = round(n_secondary       / n_graves * 100, 1),
    pct_pin             = round(n_pin             / n_graves * 100, 1),
    .groups = "drop"
  )

multi_phase_pct_long <- multi_phase_data %>%
  pivot_longer(cols = c(pct_drinking_vessel, pct_secondary, pct_pin),
               names_to = "variable", values_to = "percentage") %>%
  mutate(variable = case_when(
    variable == "pct_drinking_vessel" ~ "Drinking Vessel",
    variable == "pct_secondary"       ~ "Secondary Burial",
    variable == "pct_pin"             ~ "Hair Pin"
  ),
  variable = factor(variable, levels = c("Drinking Vessel", "Secondary Burial", "Hair Pin")))

dodge_a <- position_dodge(width = 0.4)

ggplot(multi_phase_pct_long,
       aes(x = broad_phase, y = percentage, color = variable, group = variable)) +
  geom_line(linewidth = 1.2, position = dodge_a) +
  geom_point(aes(size = n_graves), position = dodge_a) +
  geom_text_repel(aes(label = paste0(round(percentage), "%")),
                  size = 4, show.legend = FALSE,
                  bg.color = "white", bg.r = 0.15,
                  min.segment.length = 0, seed = 42,
                  box.padding = 0.4,
                  position = dodge_a) +
  scale_color_manual(values = c(
    "Drinking Vessel"  = colors$drinking_vessel,
    "Secondary Burial" = colors$skulls,
    "Hair Pin"         = colors$pin
  )) +
  scale_size_continuous(range = c(2, 8)) +
  scale_y_continuous(limits = c(0, 115)) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Burial Practices Over Time by Region",
    subtitle = "Option A: % of graves with each attribute per phase; point size = total graves\nNote: variables are not mutually exclusive — a grave may appear in multiple lines",
    x = "Broad Phase", y = "% of Graves in Phase",
    color = "Variable", size = "Total Graves"
  ) +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 30, hjust = 1))

Option A: Raw Counts

multi_phase_n_long <- multi_phase_data %>%
  pivot_longer(cols = c(n_drinking_vessel, n_secondary, n_pin),
               names_to = "variable", values_to = "count") %>%
  mutate(variable = case_when(
    variable == "n_drinking_vessel" ~ "Drinking Vessel",
    variable == "n_secondary"       ~ "Secondary Burial",
    variable == "n_pin"             ~ "Hair Pin"
  ),
  variable = factor(variable, levels = c("Drinking Vessel", "Secondary Burial", "Hair Pin")))

ggplot(multi_phase_n_long,
       aes(x = broad_phase, y = count, color = variable, group = variable)) +
  geom_line(linewidth = 1.2, position = dodge_a) +
  geom_point(aes(size = n_graves), position = dodge_a) +
  geom_text_repel(aes(label = paste0(count, " of ", n_graves)),
                  size = 4, show.legend = FALSE,
                  bg.color = "white", bg.r = 0.15,
                  min.segment.length = 0, seed = 42,
                  box.padding = 0.4,
                  position = dodge_a) +
  scale_color_manual(values = c(
    "Drinking Vessel"  = colors$drinking_vessel,
    "Secondary Burial" = colors$skulls,
    "Hair Pin"         = colors$pin
  )) +
  scale_size_continuous(range = c(2, 8)) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Burial Practice Counts Over Time by Region",
    subtitle = "Option A: raw count with each attribute per phase; label = count of total graves in phase\nNote: variables are not mutually exclusive — a grave may appear in multiple lines",
    x = "Broad Phase", y = "Number of Graves with Attribute",
    color = "Variable", size = "Total Graves"
  ) +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 30, hjust = 1))

Option B: Proportional Calendar Time (BCE) — Proportions

multi_phase_b_pct <- multi_phase_pct_long %>%
  left_join(phase_cal, by = "broad_phase") %>%
  mutate(x_plot = phase_mid_bce + case_when(
    variable == "Drinking Vessel"  ~ -8L,
    variable == "Secondary Burial" ~  0L,
    variable == "Hair Pin"         ~  8L
  ))

ggplot(multi_phase_b_pct,
       aes(x = x_plot, y = percentage, color = variable, group = variable)) +
  geom_line(linewidth = 1.2) +
  geom_point(aes(size = n_graves)) +
  geom_text_repel(aes(label = paste0(round(percentage), "%")),
                  size = 4, show.legend = FALSE,
                  bg.color = "white", bg.r = 0.15,
                  min.segment.length = 0, seed = 42,
                  box.padding = 0.4) +
  scale_color_manual(values = c(
    "Drinking Vessel"  = colors$drinking_vessel,
    "Secondary Burial" = colors$skulls,
    "Hair Pin"         = colors$pin
  )) +
  scale_size_continuous(range = c(2, 8)) +
  scale_y_continuous(limits = c(0, 115)) +
  scale_x_reverse(
    breaks = phase_cal$phase_mid_bce,
    labels = paste0(phase_cal$phase_mid_bce, "\n(", phase_cal$broad_phase, ")")
  ) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Burial Practices Over Time by Region",
    subtitle = "Option B: proportional calendar time; point size = total graves in phase; points offset ±8 yr for clarity\nNote: variables are not mutually exclusive — a grave may appear in multiple lines",
    x = "Calendar Date BCE (phase midpoint)", y = "% of Graves in Phase",
    color = "Variable", size = "Total Graves"
  ) +
  theme(legend.position = "bottom")

Option B: Raw Counts

multi_phase_b_n <- multi_phase_n_long %>%
  left_join(phase_cal, by = "broad_phase") %>%
  mutate(x_plot = phase_mid_bce + case_when(
    variable == "Drinking Vessel"  ~ -8L,
    variable == "Secondary Burial" ~  0L,
    variable == "Hair Pin"         ~  8L
  ))

ggplot(multi_phase_b_n,
       aes(x = x_plot, y = count, color = variable, group = variable)) +
  geom_line(linewidth = 1.2) +
  geom_point(aes(size = n_graves)) +
  geom_text_repel(aes(label = paste0(count, " of ", n_graves)),
                  size = 4, show.legend = FALSE,
                  bg.color = "white", bg.r = 0.15,
                  min.segment.length = 0, seed = 42,
                  box.padding = 0.4) +
  scale_color_manual(values = c(
    "Drinking Vessel"  = colors$drinking_vessel,
    "Secondary Burial" = colors$skulls,
    "Hair Pin"         = colors$pin
  )) +
  scale_size_continuous(range = c(2, 8)) +
  scale_x_reverse(
    breaks = phase_cal$phase_mid_bce,
    labels = paste0(phase_cal$phase_mid_bce, "\n(", phase_cal$broad_phase, ")")
  ) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Burial Practice Counts Over Time by Region",
    subtitle = "Option B: raw counts; proportional calendar time; points offset ±8 yr for clarity\nNote: variables are not mutually exclusive — a grave may appear in multiple lines",
    x = "Calendar Date BCE (phase midpoint)", y = "Number of Graves with Attribute",
    color = "Variable", size = "Total Graves"
  ) +
  theme(legend.position = "bottom")


Chamber Area Over Time

These boxplots show whether the physical size of tomb chambers changed over time within each region. Chamber area is recorded in estimated square meters. Only graves with area data are included — graves with NA for area_of_chamber_estimated are excluded; n= labels show how many graves contribute to each box. Phases with very few graves (n ≤ 3) produce unreliable boxplots where the median and quartiles are heavily influenced by individual outliers.

What to look for: An increase in median area over time could suggest elaboration of tomb architecture; a decrease or stasis might indicate continuity or economic contraction. Tomb type also matters — tholos tombs tend to be larger than cist or pit graves — so any apparent temporal trend should be considered alongside the tomb type distribution of each phase.

Option A: Broad Phase Categories

area_phase_data <- cups_skulls_clean %>%
  dplyr::filter(!is.na(broad_phase) & !is.na(area_of_chamber_estimated))

area_n <- area_phase_data %>%
  group_by(region, broad_phase) %>%
  summarise(n = n(), .groups = "drop")

ggplot(area_phase_data, aes(x = broad_phase, y = area_of_chamber_estimated, fill = region)) +
  geom_boxplot(alpha = 0.7) +
  geom_text(data = area_n,
            aes(x = broad_phase, y = Inf, label = paste0("n=", n)),
            inherit.aes = FALSE, vjust = 2, size = 3.5) +
  scale_fill_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Chamber Area Over Time by Region",
    subtitle = "Option A: boxplots per broad phase; n= graves with area data in that phase",
    x = "Broad Phase", y = "Chamber Area Estimated (m\u00b2)", fill = "Region"
  ) +
  theme(legend.position = "bottom", axis.text.x = element_text(angle = 30, hjust = 1))

Option B: Proportional Calendar Time (BCE)

area_phase_b <- area_phase_data %>%
  left_join(phase_cal, by = "broad_phase")

area_n_b <- area_phase_b %>%
  group_by(region, broad_phase, phase_mid_bce) %>%
  summarise(n = n(), .groups = "drop")

ggplot(area_phase_b, aes(x = phase_mid_bce, y = area_of_chamber_estimated,
                          group = interaction(broad_phase, region), fill = region)) +
  geom_boxplot(aes(width = phase_duration * 0.8), alpha = 0.7) +
  geom_text(data = area_n_b,
            aes(x = phase_mid_bce, y = Inf, label = paste0("n=", n)),
            inherit.aes = FALSE, vjust = 2, size = 3.5) +
  scale_fill_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  scale_x_reverse(
    breaks = phase_cal$phase_start_bce,
    labels = paste0(phase_cal$phase_start_bce, "\n(", phase_cal$broad_phase, ")")
  ) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Chamber Area Over Time by Region",
    subtitle = "Option B: box widths proportional to phase duration; n= graves with area data",
    x = "Calendar Date BCE (phase start)", y = "Chamber Area Estimated (m\u00b2)", fill = "Region"
  ) +
  theme(legend.position = "bottom")

Key Findings Summary

cat("=== KEY FINDINGS ===\n\n")
=== KEY FINDINGS ===
cat("1. Drinking Vessel Prevalence:\n")
1. Drinking Vessel Prevalence:
cat("   -", sum(cups_skulls_clean$has_drinking_vessel == TRUE, na.rm = TRUE),
    "graves (", round(sum(cups_skulls_clean$has_drinking_vessel == TRUE, na.rm = TRUE) / nrow(cups_skulls_clean) * 100, 1),
    "%) have drinking vessels recorded\n\n")
   - 80 graves ( 27 %) have drinking vessels recorded
cat("2. Skeletal Remains:\n")
2. Skeletal Remains:
cat("   -", sum(cups_skulls_clean$has_remains == TRUE, na.rm = TRUE),
    "graves (", round(sum(cups_skulls_clean$has_remains == TRUE, na.rm = TRUE) / nrow(cups_skulls_clean) * 100, 1),
    "%) have human remains\n")
   - 226 graves ( 76.4 %) have human remains
cat("   - Skull counts recorded in", sum(!is.na(cups_skulls_clean$skulls_number)), "graves\n\n")
   - Skull counts recorded in 83 graves
cat("3. Hair Pin Prevalence:\n")
3. Hair Pin Prevalence:
cat("   -", sum(cups_skulls_clean$has_pin == TRUE, na.rm = TRUE),
    "graves (", round(sum(cups_skulls_clean$has_pin == TRUE, na.rm = TRUE) / nrow(cups_skulls_clean) * 100, 1),
    "%) have hair pins recorded\n\n")
   - 16 graves ( 5.4 %) have hair pins recorded
cat("4. Burial Type Association:\n")
4. Burial Type Association:
burial_counts <- cups_skulls_clean %>%
  count(burial_category) %>%
  arrange(desc(n))
for (i in 1:nrow(burial_counts)) {
  cat("   -", burial_counts$burial_category[i], ":", burial_counts$n[i], "graves\n")
}
   - Unknown/Not Recorded : 100 graves
   - Primary Only : 78 graves
   - Secondary Only : 61 graves
   - Both Primary & Secondary : 57 graves

Chamber Space and Burial Goods

Do larger chambers have more skulls and drinking vessels?

This section explores whether the estimated chamber area relates to the number of skulls and drinking vessels found.

HOW YOUR PERIOD DICTIONARY WILL ENHANCE THIS: - Once you complete period_dictionary.csv and enable the dictionary loading code, we can add temporal layers - You’ll be able to see if these relationships change across periods - Time-based patterns can reveal ritual practice evolution

Data Availability

space_analysis_data <- cups_skulls_clean %>%
  dplyr::filter(!is.na(area_of_chamber_estimated))

cat("Graves with chamber area estimated:", nrow(space_analysis_data), "\n")
Graves with chamber area estimated: 99 
cat("Graves with area AND skull count:", sum(!is.na(space_analysis_data$skulls_number)), "\n")
Graves with area AND skull count: 22 
cat("Graves with area AND vessel count:", sum(!is.na(space_analysis_data$drinking_vessel_number)), "\n\n")
Graves with area AND vessel count: 41 
# Summary statistics for chamber area
cat("Chamber Area Summary (m²):\n")
Chamber Area Summary (m²):
print(space_analysis_data %>%
  pull(area_of_chamber_estimated) %>%
  summary())
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.419   7.069  12.566  19.056  23.330  88.250 

Skulls vs Chamber Area

skulls_area_data <- cups_skulls_clean %>%
  dplyr::filter(!is.na(area_of_chamber_estimated) & !is.na(skulls_number))

ggplot(skulls_area_data, aes(x = area_of_chamber_estimated, y = skulls_number, color = region)) +
  geom_point(size = 3, alpha = 0.6) +
  geom_smooth(method = "lm", se = TRUE, alpha = 0.2) +
  scale_color_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  coord_cartesian(ylim = c(0, NA)) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Relationship: Chamber Area vs Number of Skulls by Region",
    x = "Chamber Area Estimated (m²)",
    y = "Number of Skulls",
    color = "Region"
  ) +
  theme_minimal(base_size = 16) +
  theme(legend.position = "bottom")

Version Author Date
8d3e1c7 tinalasisi 2026-02-17
2f9f6dc tinalasisi 2026-02-17
# By region
for (reg in unique(skulls_area_data$region)) {
  region_data <- skulls_area_data %>% dplyr::filter(region == reg)
  cor_val <- cor(region_data$area_of_chamber_estimated, region_data$skulls_number, use = "complete.obs")
  lm_result <- lm(skulls_number ~ area_of_chamber_estimated, data = region_data)

  cat("\n===", toupper(reg), "===\n")
  cat("Pearson correlation (r):", round(cor_val, 3), "\n")
  print(summary(lm_result))
}

=== LACONIA ===
Pearson correlation (r): -0.267 

Call:
lm(formula = skulls_number ~ area_of_chamber_estimated, data = region_data)

Residuals:
      1       2       3       4 
  6.281  -8.208 -10.682  12.609 

Coefficients:
                          Estimate Std. Error t value Pr(>|t|)
(Intercept)                14.8606    11.0108   1.350    0.310
area_of_chamber_estimated  -0.3307     0.8450  -0.391    0.733

Residual standard error: 13.78 on 2 degrees of freedom
Multiple R-squared:  0.07112,   Adjusted R-squared:  -0.3933 
F-statistic: 0.1531 on 1 and 2 DF,  p-value: 0.7333


=== MESSENIA ===
Pearson correlation (r): 0.009 

Call:
lm(formula = skulls_number ~ area_of_chamber_estimated, data = region_data)

Residuals:
   Min     1Q Median     3Q    Max 
-9.542 -7.653 -3.103  1.409 36.389 

Coefficients:
                           Estimate Std. Error t value Pr(>|t|)  
(Intercept)               10.491027   4.206518   2.494    0.024 *
area_of_chamber_estimated  0.007211   0.189755   0.038    0.970  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 11.78 on 16 degrees of freedom
Multiple R-squared:  9.024e-05, Adjusted R-squared:  -0.0624 
F-statistic: 0.001444 on 1 and 16 DF,  p-value: 0.9702

Drinking Vessels vs Chamber Area

vessels_area_data <- cups_skulls_clean %>%
  dplyr::filter(!is.na(area_of_chamber_estimated) & !is.na(drinking_vessel_number))

ggplot(vessels_area_data, aes(x = area_of_chamber_estimated, y = drinking_vessel_number, color = region)) +
  geom_point(size = 3, alpha = 0.6) +
  geom_smooth(method = "lm", se = TRUE, alpha = 0.2) +
  scale_color_manual(values = c(laconia = colors$laconia, messenia = colors$messenia)) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Relationship: Chamber Area vs Number of Drinking Vessels by Region",
    x = "Chamber Area Estimated (m²)",
    y = "Number of Drinking Vessels",
    color = "Region"
  ) +
  theme_minimal(base_size = 16) +
  theme(legend.position = "bottom")

Version Author Date
8d3e1c7 tinalasisi 2026-02-17
2f9f6dc tinalasisi 2026-02-17
# By region
for (reg in unique(vessels_area_data$region)) {
  region_data <- vessels_area_data %>% dplyr::filter(region == reg)
  cor_val <- cor(region_data$area_of_chamber_estimated, region_data$drinking_vessel_number, use = "complete.obs")
  lm_result <- lm(drinking_vessel_number ~ area_of_chamber_estimated, data = region_data)

  cat("\n===", toupper(reg), "===\n")
  cat("Pearson correlation (r):", round(cor_val, 3), "\n")
  print(summary(lm_result))
}

=== LACONIA ===
Pearson correlation (r): 0.406 

Call:
lm(formula = drinking_vessel_number ~ area_of_chamber_estimated, 
    data = region_data)

Residuals:
    Min      1Q  Median      3Q     Max 
-2.4141 -1.4954 -0.6643  0.0692 10.5935 

Coefficients:
                          Estimate Std. Error t value Pr(>|t|)  
(Intercept)                2.72350    1.35122   2.016   0.0715 .
area_of_chamber_estimated  0.06679    0.04750   1.406   0.1900  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 3.643 on 10 degrees of freedom
Multiple R-squared:  0.1651,    Adjusted R-squared:  0.08157 
F-statistic: 1.977 on 1 and 10 DF,  p-value: 0.19


=== MESSENIA ===
Pearson correlation (r): 0 

Call:
lm(formula = drinking_vessel_number ~ area_of_chamber_estimated, 
    data = region_data)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.4161 -1.4137 -1.4127  0.5868  8.5861 

Coefficients:
                           Estimate Std. Error t value Pr(>|t|)   
(Intercept)               2.413e+00  7.226e-01   3.339  0.00247 **
area_of_chamber_estimated 7.082e-05  3.268e-02   0.002  0.99829   
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 2.253 on 27 degrees of freedom
Multiple R-squared:  1.739e-07, Adjusted R-squared:  -0.03704 
F-statistic: 4.695e-06 on 1 and 27 DF,  p-value: 0.9983

Both Skulls and Vessels vs Chamber Area

both_area_data <- cups_skulls_clean %>%
  dplyr::filter(!is.na(area_of_chamber_estimated) & !is.na(skulls_number) & !is.na(drinking_vessel_number))

ggplot(both_area_data, aes(x = area_of_chamber_estimated, y = skulls_number)) +
  geom_point(aes(color = drinking_vessel_number), size = 4, alpha = 0.7) +
  scale_color_gradient(name = "Drinking\nVessels", low = "#2166AC", high = "#B2182B") +
  coord_cartesian(ylim = c(0, NA)) +
  facet_wrap(~region, ncol = 1) +
  labs(
    title = "Chamber Area vs Skulls (colored by Drinking Vessels) by Region",
    x = "Chamber Area Estimated (m²)",
    y = "Number of Skulls"
  ) +
  theme_minimal(base_size = 16) +
  theme(legend.position = "bottom")

Version Author Date
8d3e1c7 tinalasisi 2026-02-17
2f9f6dc tinalasisi 2026-02-17

Session Info

sessionInfo()
R version 4.5.2 (2025-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Tahoe 26.3.1

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] en_US/en_US/en_US/C/en_US/en_US

time zone: America/Detroit
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] ggrepel_0.9.7   knitr_1.51      readr_2.2.0     workflowr_1.7.2
 [5] here_1.0.2      janitor_2.2.1   stringr_1.6.0   tidyr_1.3.2    
 [9] ggplot2_4.0.1   dplyr_1.1.4     readxl_1.4.5   

loaded via a namespace (and not attached):
 [1] gtable_0.3.6       xfun_0.56          bslib_0.10.0       processx_3.8.6    
 [5] lattice_0.22-7     callr_3.7.6        tzdb_0.5.0         vctrs_0.7.1       
 [9] tools_4.5.2        ps_1.9.1           generics_0.1.4     parallel_4.5.2    
[13] tibble_3.3.1       pkgconfig_2.0.3    Matrix_1.7-4       RColorBrewer_1.1-3
[17] S7_0.2.1           lifecycle_1.0.5    compiler_4.5.2     farver_2.1.2      
[21] git2r_0.36.2       getPass_0.2-4      snakecase_0.11.1   httpuv_1.6.16     
[25] htmltools_0.5.9    sass_0.4.10        yaml_2.3.12        crayon_1.5.3      
[29] later_1.4.5        pillar_1.11.1      jquerylib_0.1.4    whisker_0.4.1     
[33] cachem_1.1.0       nlme_3.1-168       tidyselect_1.2.1   digest_0.6.39     
[37] stringi_1.8.7      purrr_1.2.1        splines_4.5.2      labeling_0.4.3    
[41] rprojroot_2.1.1    fastmap_1.2.0      grid_4.5.2         cli_3.6.5         
[45] magrittr_2.0.4     utf8_1.2.6         withr_3.0.2        scales_1.4.0      
[49] promises_1.5.0     bit64_4.6.0-1      lubridate_1.9.4    timechange_0.4.0  
[53] rmarkdown_2.30     httr_1.4.7         bit_4.6.0          otel_0.2.0        
[57] cellranger_1.1.0   hms_1.1.4          evaluate_1.0.5     mgcv_1.9-3        
[61] rlang_1.1.7        Rcpp_1.1.1         glue_1.8.0         renv_1.1.6        
[65] vroom_1.7.0        rstudioapi_0.18.0  jsonlite_2.0.0     R6_2.6.1          
[69] fs_1.6.6          

sessionInfo()
R version 4.5.2 (2025-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Tahoe 26.3.1

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] en_US/en_US/en_US/C/en_US/en_US

time zone: America/Detroit
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] ggrepel_0.9.7   knitr_1.51      readr_2.2.0     workflowr_1.7.2
 [5] here_1.0.2      janitor_2.2.1   stringr_1.6.0   tidyr_1.3.2    
 [9] ggplot2_4.0.1   dplyr_1.1.4     readxl_1.4.5   

loaded via a namespace (and not attached):
 [1] gtable_0.3.6       xfun_0.56          bslib_0.10.0       processx_3.8.6    
 [5] lattice_0.22-7     callr_3.7.6        tzdb_0.5.0         vctrs_0.7.1       
 [9] tools_4.5.2        ps_1.9.1           generics_0.1.4     parallel_4.5.2    
[13] tibble_3.3.1       pkgconfig_2.0.3    Matrix_1.7-4       RColorBrewer_1.1-3
[17] S7_0.2.1           lifecycle_1.0.5    compiler_4.5.2     farver_2.1.2      
[21] git2r_0.36.2       getPass_0.2-4      snakecase_0.11.1   httpuv_1.6.16     
[25] htmltools_0.5.9    sass_0.4.10        yaml_2.3.12        crayon_1.5.3      
[29] later_1.4.5        pillar_1.11.1      jquerylib_0.1.4    whisker_0.4.1     
[33] cachem_1.1.0       nlme_3.1-168       tidyselect_1.2.1   digest_0.6.39     
[37] stringi_1.8.7      purrr_1.2.1        splines_4.5.2      labeling_0.4.3    
[41] rprojroot_2.1.1    fastmap_1.2.0      grid_4.5.2         cli_3.6.5         
[45] magrittr_2.0.4     utf8_1.2.6         withr_3.0.2        scales_1.4.0      
[49] promises_1.5.0     bit64_4.6.0-1      lubridate_1.9.4    timechange_0.4.0  
[53] rmarkdown_2.30     httr_1.4.7         bit_4.6.0          otel_0.2.0        
[57] cellranger_1.1.0   hms_1.1.4          evaluate_1.0.5     mgcv_1.9-3        
[61] rlang_1.1.7        Rcpp_1.1.1         glue_1.8.0         renv_1.1.6        
[65] vroom_1.7.0        rstudioapi_0.18.0  jsonlite_2.0.0     R6_2.6.1          
[69] fs_1.6.6