Θεματική χαρτογραφία: μεταβλητές, ταξινόμηση και χρωματικά σχήματα

Από τη φύση των δεδομένων στην κατάλληλη χαρτογραφική απόδοση με R και tmap

Χαρτογραφία
R
tmap
Συγγραφείς
Τροποποιημένο

31/07/2026

Τι θα μάθουμε

Ο οδηγός εστιάζει στις βασικές αποφάσεις της θεματικής χαρτογραφίας (thematic cartography). Θα δούμε πώς:

Σύνδεση με το πρώτο tutorial

Το παρόν αποτελεί συνέχεια του οδηγού «Δημιουργία θεματικών χαρτών στο περιβάλλον της R». Ο πρώτος οδηγός παρουσιάζει αναλυτικά τη ροή Eurostat → GISCO → sftmap. Εδώ εστιάζουμε στις αποφάσεις που μεσολαβούν ανάμεσα στα δεδομένα και στον χάρτη: ποια μεταβλητή απεικονίζουμε, πώς οργανώνουμε τις τιμές της και με ποια χρώματα.

1. Η αλυσίδα των χαρτογραφικών αποφάσεων

Η δημιουργία ενός θεματικού χάρτη δεν αρχίζει από την επιλογή χρωμάτων. Προηγούνται τέσσερις διαδοχικές αποφάσεις:

  1. Τι σημαίνει η μεταβλητή;
    Είναι κατηγορία, διατακτική βαθμίδα, συνολική ποσότητα, ποσοστό, λόγος, δείκτης ή απόκλιση από μια τιμή αναφοράς;

  2. Σε ποια γεωμετρία (geometry) αναφέρεται;
    Η πληροφορία αφορά θέση, γραμμή, περιοχή ή ροή;

  3. Ποια οπτική μεταβλητή (visual variable) είναι κατάλληλη;
    Απόχρωση (hue), φωτεινότητα (lightness), μέγεθος (size), σχήμα (shape), πάχος (width) ή προσανατολισμός (orientation);

  4. Ποια κλίμακα και ποιο χρωματικό σχήμα υπηρετούν το ερώτημα;
    Συνεχής ή ταξινομημένη κλίμακα; Ποιοτικό, διαδοχικό ή αποκλίνον σχήμα;

Η σειρά έχει σημασία. Αν αρχίσουμε από το χρώμα, μπορεί να καταλήξουμε σε έναν ελκυστικό χάρτη που όμως κωδικοποιεί λανθασμένα τη μεταβλητή.

Ερώτημα Επιλογή Χαρτογραφική συνέπεια
Ποια είναι η κλίμακα μέτρησης; ονομαστική, διατακτική, ποσοτική καθορίζει την οπτική μεταβλητή
Τι εκφράζει η τιμή; αθροιστική ποσότητα ή κανονικοποιημένη τιμή επηρεάζει τον τύπο χάρτη
Υπάρχει ουσιαστική σειρά; όχι ή ναι ποιοτικό ή διαδοχικό χρωματικό σχήμα
Υπάρχει κρίσιμο σημείο αναφοράς; όχι ή ναι διαδοχικό ή αποκλίνον χρωματικό σχήμα
Θέλουμε ακριβή συνέχεια ή απλούστευση; συνεχής κλίμακα ή κλάσεις μορφή κλίμακας και υπομνήματος (legend)
Θέλουμε σύγκριση μεταξύ ετών/χαρτών; κοινά ή διαφορετικά όρια συγκρισιμότητα των χαρτών

2. Κλίμακες μέτρησης και χωρική σημασία των μεταβλητών

Πριν από τη χαρτογράφηση χρειάζεται να διακρίνουμε δύο διαφορετικά ζητήματα, τα οποία συχνά συγχέονται:

  1. την κλίμακα μέτρησης, δηλαδή αν οι τιμές αποτελούν κατηγορίες, βαθμίδες ή αριθμητικές μετρήσεις, και
  2. τη χωρική/στατιστική σημασία, δηλαδή αν η τιμή εκφράζει συνολική ποσότητα, τιμή ανά μονάδα αναφοράς, λόγο, πυκνότητα ή απόκλιση από μια κεντρική τιμή.

Το ότι μια στήλη αποθηκεύεται ως αριθμητική δεν αρκεί για να αποφασίσουμε πώς θα χαρτογραφηθεί. Για παράδειγμα, οι αριθμητικοί κωδικοί χρήσεων γης παραμένουν ονομαστικές κατηγορίες, ενώ ένα ποσοστό και ένας συνολικός πληθυσμός είναι και τα δύο αριθμητικά μεγέθη αλλά απαιτούν διαφορετική χαρτογραφική μεταχείριση.

2.1 Ονομαστική ή κατηγορική κλίμακα (nominal/categorical scale)

Στην ονομαστική κλίμακα οι τιμές δηλώνουν διαφορετικές κατηγορίες χωρίς φυσική σειρά. Παραδείγματα είναι η χρήση γης, η διοικητική υπαγωγή ή ο λειτουργικός τύπος μιας περιφέρειας.

Για την απεικόνισή τους χρησιμοποιούμε διαφορετικές:

  • χρωματικές αποχρώσεις παρόμοιας οπτικής βαρύτητας,
  • μορφές συμβόλων για σημειακά δεδομένα, ή
  • υφές/μοτίβα, όταν το χρώμα δεν επαρκεί.

Δεν χρησιμοποιούμε διαβάθμιση από ανοιχτό σε σκούρο, επειδή αυτή δημιουργεί οπτικά την εντύπωση σειράς και μεγέθους.

2.2 Διατακτική ή διατεταγμένη κλίμακα (ordinal scale)

Στη διατακτική κλίμακα οι κατηγορίες έχουν φυσική σειρά, χωρίς να γνωρίζουμε ότι οι αποστάσεις μεταξύ διαδοχικών βαθμίδων είναι ίσες.

Παραδείγματα:

  • χαμηλή – μέση – υψηλή τρωτότητα,
  • κακή – μέτρια – καλή προσβασιμότητα,
  • 1η – 2η – 3η βαθμίδα ενός χωρικού κέντρου.

Η σειρά αποδίδεται συνήθως με μονοτονική μεταβολή φωτεινότητας ή/και χρωματικής έντασης: από ανοιχτές αποχρώσεις για τις χαμηλότερες βαθμίδες προς σκουρότερες για τις υψηλότερες.

2.3 Ποσοτικές κλίμακες (quantitative scales)

Οι ποσοτικές μεταβλητές επιτρέπουν αριθμητικές συγκρίσεις. Ανάλογα με την κλίμακα μέτρησης, μπορεί να έχουν:

  • κλίμακα διαστημάτων (interval scale), όπου οι διαφορές είναι ερμηνεύσιμες αλλά το μηδέν δεν αποτελεί απόλυτη απουσία του φαινομένου, όπως στη θερμοκρασία Κελσίου, ή
  • αναλογική κλίμακα (ratio scale), όπου υπάρχει ουσιαστικό μηδέν και έχουν νόημα οι αναλογίες, όπως στον πληθυσμό, στο εισόδημα ή στην έκταση.

Για τη χαρτογραφική απόδοση, όμως, είναι εξίσου κρίσιμη η επόμενη διάκριση.

2.4 Συνολικές ποσότητες και κανονικοποιημένες τιμές

Στη χαρτογραφική βιβλιογραφία συναντώνται συχνά τα ζεύγη απόλυτο–σχετικό (absolute–relative) και εκτατικό–εντατικό (extensive–intensive). Οι όροι, όμως, δεν είναι πάντοτε ταυτόσημοι ούτε αποτελούν αυτοτελείς κλίμακες μέτρησης. Για την επιλογή χάρτη είναι σαφέστερο να θέσουμε δύο πρακτικά ερωτήματα:

  1. μπορεί η τιμή να αθροιστεί με νόημα όταν συνενώνονται χωρικές ενότητες;
  2. έχει διαιρεθεί με πληθυσμό, έκταση ή άλλη βάση αναφοράς;

Συνολικές ή χωρικά αθροίσιμες ποσότητες είναι τιμές για τις οποίες το σύνολο μιας ευρύτερης περιοχής μπορεί, υπό συνεπείς ορισμούς, να προκύψει ως άθροισμα των επιμέρους περιοχών:

  • συνολικός πληθυσμός,
  • αριθμός επιχειρήσεων,
  • συνολικό ΑΕΠ,
  • συνολικός αριθμός συμβάντων.

Αυτές οι ποσότητες αποδίδονται συνήθως με αναλογικά σύμβολα, όπου το εμβαδόν του συμβόλου συνδέεται με την τιμή.

Κανονικοποιημένες ή σχετικές τιμές (normalized or relative values) προκύπτουν από διαίρεση με κατάλληλη βάση ή από σύγκριση με μια τιμή αναφοράς. Επιτρέπουν συνήθως δικαιότερη σύγκριση χωρικών ενοτήτων διαφορετικού πληθυσμού ή έκτασης:

  • ποσοστό ανεργίας,
  • πυκνότητα πληθυσμού (population density),
  • κατά κεφαλή ΑΕΠ,
  • λόγος θανάτων προς πληθυσμό,
  • δείκτης με τιμή αναφοράς.

Αυτές οι τιμές είναι συνήθως καταλληλότερες για χωροπληθείς χάρτες (choropleth maps). Δεν αθροίζονται απευθείας: για παράδειγμα, το ποσοστό μιας ευρύτερης περιοχής απαιτεί επανυπολογισμό από αριθμητή και παρονομαστή ή κατάλληλη στάθμιση.

Σε γενική μορφή:

\[ \text{ένταση ή λόγος}_i = \frac{\text{γεγονότα ή μέγεθος}_i} {\text{πληθυσμός ή άλλη κατάλληλη βάση έκθεσης}_i} \]

Κρίσιμη επισήμανση

Η απόδοση συνολικών ποσοτήτων με το γέμισμα διοικητικών πολυγώνων μπορεί να οδηγήσει σε παραπλανητικό χάρτη: οι μεγάλες ή πολυπληθείς ενότητες τείνουν να εμφανίζονται εντονότερες λόγω του μεγέθους τους. Πριν επιλέξουμε χωροπληθή χάρτη, εξετάζουμε αν χρειαζόμαστε ποσοστό, πυκνότητα ή κατά κεφαλή τιμή. Αν το σύνολο είναι το ίδιο το αντικείμενο της ανάλυσης, προτιμούμε συνήθως αναλογικά σύμβολα.

2.5 Μεταβλητές με ουσιαστικό σημείο αναφοράς (reference value)

Ορισμένες ποσοτικές μεταβλητές αποκτούν νόημα σε σχέση με μια κεντρική τιμή:

  • μεταβολή ως προς το μηδέν,
  • απόκλιση από τον μέσο όρο,
  • δείκτης με βάση 100,
  • διαφορά πριν και μετά από μια παρέμβαση,
  • θετικό και αρνητικό υπόλοιπο.

Σε αυτές τις περιπτώσεις χρησιμοποιούμε συνήθως αποκλίνον χρωματικό σχήμα: δύο διαδοχικοί χρωματικοί κλάδοι αναπτύσσονται εκατέρωθεν ενός ουδέτερου χρώματος, το οποίο αντιστοιχεί στην κρίσιμη τιμή αναφοράς.

2.6 Συνοπτική αντιστοίχιση

Κλίμακα ή χωρική σημασία Παράδειγμα Κατάλληλη απόδοση Χρωματικό σχήμα
Ονομαστική/κατηγορική τύπος περιφέρειας διαφορετικές αποχρώσεις ή μορφές συμβόλων ποιοτικό
Διατακτική/διατεταγμένη χαμηλή–μέση–υψηλή τρωτότητα μονοτονική μεταβολή φωτεινότητας διαδοχικό
Κανονικοποιημένη/σχετική τιμή ποσοστό ανεργίας, κατά κεφαλή ΑΕΠ χωροπληθής χάρτης διαδοχικό
Συνολική/χωρικά αθροίσιμη ποσότητα συνολικός πληθυσμός ή ΑΕΠ αναλογικά σύμβολα συνήθως σταθερό χρώμα
Απόκλιση/μεταβολή διαφορά από 0 ή δείκτη 100 χωροπληθής χάρτης με ουδέτερο κέντρο αποκλίνον
Κυκλική διεύθυνση ανέμου, ώρα κλειστή χρωματική ακολουθία κυκλικό

3. Προετοιμασία του παραδείγματος

Χρησιμοποιούμε το ίδιο θεματικό πεδίο με το πρώτο tutorial, αλλά διακρίνουμε τρεις διαφορετικές εκφράσεις του ΑΕΠ:

Έτσι αποφεύγουμε να χρησιμοποιούμε τον δείκτη EU27=100 ως γενική αριθμητική μεταβλητή, όταν η τιμή του κατά κεφαλή ΑΕΠ σε ΜΑΔ είναι σαφέστερη.

3.1 Πακέτα

rpackages <- c(
  "eurostat",
  "giscoR",
  "sf",
  "dplyr",
  "tmap",
  "classInt",
  "cols4all"
)

to_install <- setdiff(rpackages, rownames(installed.packages()))

if (length(to_install) > 0) {
  install.packages(to_install)
}

invisible(lapply(rpackages, library, character.only = TRUE))

Τα δύο πρόσθετα πακέτα σε σχέση με το πρώτο tutorial είναι:

  • classInt: υπολογίζει και επιθεωρεί όρια κλάσεων με διαφορετικές μεθόδους ταξινόμησης,
  • cols4all: οργανώνει και αξιολογεί χρωματικά σχήματα, μεταξύ άλλων ως προς τη διάκρισή τους από άτομα με διαφορετικές μορφές χρωματικής όρασης.

3.2 Στατιστικά και γεωχωρικά δεδομένα

options(eurostat_cache = TRUE)

indata <- "nama_10r_2gdp"
intime <- 2024
inname <- "Π. Πανταζής"

gdp_raw <- eurostat::get_eurostat(
  indata,
  time_format = "num"
)

indexed 0B in  0s, 0B/s
indexed 2.15GB in  0s, 2.15GB/s
                                                                              
# Το TIME_PERIOD μπορεί να επιστραφεί ως αριθμός ή ως ημερομηνία,
# ιδίως όταν χρησιμοποιείται ήδη υπάρχουσα cache.
gdp_raw <- gdp_raw |>
  dplyr::mutate(
    observation_year = as.integer(
      substr(as.character(TIME_PERIOD), 1, 4)
    ),
    geo = as.character(geo),
    values = as.numeric(values)
  )

# Κατά κεφαλή ΑΕΠ: κύρια κανονικοποιημένη τιμή
gdp_pc_pps <- gdp_raw |>
  dplyr::filter(
    unit == "PPS_EU27_2020_HAB",
    observation_year == intime
  ) |>
  dplyr::transmute(
    geo,
    gdp_pc_pps = values
  )

if (nrow(gdp_pc_pps) == 0) {
  stop(
    paste0(
      "Δεν βρέθηκαν τιμές PPS_EU27_2020_HAB για το ",
      intime,
      ". Διαθέσιμα έτη: ",
      paste(
        sort(unique(
          gdp_raw$observation_year[
            gdp_raw$unit == "PPS_EU27_2020_HAB"
          ]
        )),
        collapse = ", "
      )
    )
  )
}

# Δείκτης κατά κεφαλή ΑΕΠ: χρησιμοποιείται μόνο γύρω από το EU27_2020 = 100
gdp_pc_index <- gdp_raw |>
  dplyr::filter(
    unit == "PPS_HAB_EU27_2020",
    observation_year == intime
  ) |>
  dplyr::transmute(
    geo,
    gdp_pc_index = values
  )

# Συνολικό ΑΕΠ: χωρικά αθροίσιμη ποσότητα, εκατ. ΜΑΔ
gdp_total <- gdp_raw |>
  dplyr::filter(
    unit == "MIO_PPS_EU27_2020",
    observation_year == intime
  ) |>
  dplyr::transmute(
    geo,
    gdp_total = values
  )

nuts2 <- eurostat::get_eurostat_geospatial(
  output_class = "sf",
  resolution = "60",
  nuts_level = 2,
  year = 2024,
  crs = 3035
)

nuts0 <- eurostat::get_eurostat_geospatial(
  output_class = "sf",
  resolution = "60",
  nuts_level = 0,
  year = 2024,
  crs = 3035
)

eu27_codes <- eurostat::eu_countries$code

nuts0_eu27 <- nuts0 |>
  dplyr::filter(CNTR_CODE %in% eu27_codes)

nuts2_map <- nuts2 |>
  dplyr::mutate(NUTS_ID = as.character(NUTS_ID)) |>
  dplyr::left_join(gdp_pc_pps, by = c("NUTS_ID" = "geo")) |>
  dplyr::left_join(gdp_pc_index, by = c("NUTS_ID" = "geo")) |>
  dplyr::left_join(gdp_total, by = c("NUTS_ID" = "geo")) |>
  dplyr::filter(substr(NUTS_ID, 1, 2) %in% eu27_codes) |>
  dplyr::mutate(
    gdp_index_gap = gdp_pc_index - 100
  )

matched_gdp <- sum(is.finite(nuts2_map$gdp_pc_pps))

if (matched_gdp == 0) {
  stop(
    paste0(
      "Βρέθηκαν ",
      nrow(gdp_pc_pps),
      " τιμές PPS_EU27_2020_HAB για το ",
      intime,
      ", αλλά καμία δεν αντιστοιχίστηκε στις γεωμετρίες NUTS-2 του 2024. ",
      "Ελέγξτε τους κωδικούς geo και NUTS_ID."
    )
  )
}

matched_gdp_index <- sum(is.finite(nuts2_map$gdp_pc_index))

if (matched_gdp_index == 0) {
  stop(
    paste0(
      "Δεν αντιστοιχίστηκαν τιμές PPS_HAB_EU27_2020 για το ",
      intime,
      " στις γεωμετρίες NUTS-2 του 2024."
    )
  )
}

matched_gdp_total <- sum(
  is.finite(nuts2_map$gdp_total) &
    nuts2_map$gdp_total >= 0
)

if (matched_gdp_total == 0) {
  stop(
    paste0(
      "Δεν αντιστοιχίστηκαν τιμές MIO_PPS_EU27_2020 για το ",
      intime,
      " στις γεωμετρίες NUTS-2 του 2024."
    )
  )
}

neigh_all <- giscoR::gisco_get_countries(
  resolution = "60",
  epsg = 3035
)

bg_non_eu <- neigh_all |>
  dplyr::filter(!(CNTR_ID %in% eu27_codes)) |>
  sf::st_make_valid()

Ορίζουμε κοινό παράθυρο προβολής για όλους τους ευρωπαϊκούς χάρτες:

eu_bbox <- sf::st_as_sfc(
  sf::st_bbox(
    c(
      xmin = 2440000,
      xmax = 6500000,
      ymin = 1100000,
      ymax = 5700000
    ),
    crs = sf::st_crs(nuts0)
  )
)

4. Πριν από τον χάρτη: εξετάζουμε την κατανομή (distribution)

Η μέθοδος ταξινόμησης δεν πρέπει να επιλέγεται χωρίς προηγούμενο έλεγχο των τιμών. Εξετάζουμε:

summary(nuts2_map$gdp_pc_pps)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  12000   28175   34650   37555   44100  107200 
quantile(
  nuts2_map$gdp_pc_pps,
  probs = seq(0, 1, 0.1),
  na.rm = TRUE
)
    0%    10%    20%    30%    40%    50%    60%    70%    80%    90%   100% 
 12000  22630  25760  29000  32280  34650  38580  42710  46340  52910 107200 
length(unique(stats::na.omit(nuts2_map$gdp_pc_pps)))
[1] 188
par(mfrow = c(1, 2), mar = c(4, 4, 2, 1))

gdp_pc_values <- nuts2_map$gdp_pc_pps
gdp_pc_values <- gdp_pc_values[is.finite(gdp_pc_values)]

gdp_median <- median(gdp_pc_values)

hist(
  gdp_pc_values,
  breaks = "FD",
  col = "#B8D8E8",
  border = "white",
  main = "Κατανομή του κατά κεφαλή ΑΕΠ",
  xlab = "ΑΕΠ κατά κεφαλή (ΜΑΔ ανά κάτοικο)"
)

abline(v = gdp_median, col = "#B2182B", lwd = 2, lty = 2)

boxplot(
  gdp_pc_values,
  horizontal = TRUE,
  col = "#B8D8E8",
  main = "Ακραίες τιμές και διασπορά",
  xlab = "ΑΕΠ κατά κεφαλή (ΜΑΔ ανά κάτοικο)"
)

abline(v = gdp_median, col = "#B2182B", lwd = 2, lty = 2)

par(mfrow = c(1, 1))

Η κόκκινη διακεκομμένη γραμμή αντιστοιχεί στη διάμεσο. Το ιστόγραμμα (histogram) και το θηκόγραμμα (boxplot) δείχνουν αν η κατανομή είναι συμμετρική, αν παρουσιάζει μεγάλη δεξιά ουρά και πόσο ισχυρά επηρεάζεται από ακραίες περιφέρειες. Αυτές οι πληροφορίες είναι αναγκαίες πριν επιλέξουμε αριθμό κλάσεων και μέθοδο ταξινόμησης.

4.1 Πρακτικά κριτήρια για την επιλογή ορίων

Δεν υπάρχει ένα μοναδικό σύνολο «σωστών» ορίων για κάθε μεταβλητή. Η επιλογή πρέπει να συνδυάζει τη σημασία της μεταβλητής, τη στατιστική κατανομή και το χωρικό πρότυπο που προκύπτει στον χάρτη.

Κριτήριο Τι ελέγχουμε Πρακτική συνέπεια
ουσιαστικό νόημα θεσμικά κατώφλια, στόχος, μηδέν, μέσος ή δείκτης 100 τα τεκμηριωμένα όρια προηγούνται μιας καθαρά στατιστικής βελτιστοποίησης
μορφή της κατανομής (distributional shape) συμμετρία, ασυμμετρία, βαριά ουρά, πολλαπλές κορυφώσεις προσαρμόζουμε τη μέθοδο στη μορφή των δεδομένων
θέση των τομών (break placement) αν ένα όριο τέμνει πυκνή συστάδα τιμών (cluster) ή βρίσκεται σε αραιό τμήμα όταν αναζητούμε φυσικές ομάδες, προτιμούμε «κοιλάδες» και μεγάλα κενά, όχι κορυφώσεις
ισορροπία μεγέθους των κλάσεων (class balance) κενές ή πολύ ολιγομελείς κλάσεις επανεξετάζουμε τον αριθμό κλάσεων ή τα όρια, εκτός αν μια μικρή ακραία ομάδα έχει ουσιαστικό νόημα
εσωτερική ομοιογένεια (within-class homogeneity) πόσο κοντινές είναι οι τιμές μέσα σε κάθε κλάση μικρότερη ενδοομαδική διασπορά περιορίζει την πληροφορία που χάνεται με την ταξινόμηση
διαχωρισμός κλάσεων (between-class separation) πόσο διαφέρουν οι διαδοχικές κλάσεις αποφεύγουμε διαφορετικά χρώματα για σχεδόν ίδιες τιμές
αναγνωσιμότητα (legibility) στρογγυλοποιημένα όρια, εύλογος αριθμός δεκαδικών, διακριτά χρώματα το υπόμνημα πρέπει να διαβάζεται και να απομνημονεύεται εύκολα
σταθερότητα (stability) αν μικρή αλλαγή δεδομένων ή έτους μετακινεί έντονα τα όρια για συγκρίσεις προτιμούμε κοινά, προκαθορισμένα και παγιωμένα όρια
χωρική συνοχή (spatial coherence) αν προκύπτει ευδιάκριτο χωρικό πρότυπο ή ένας κατακερματισμένος χάρτης ελέγχουμε πάντοτε και τον χάρτη, επειδή μια στατιστικά αποτελεσματική ταξινόμηση δεν λαμβάνει κατ’ ανάγκην υπόψη τη χωρική διάταξη

Η πρακτική συμβουλή «κόβουμε σε κοιλάδα και όχι σε κορυφή» αφορά κυρίως την αναζήτηση φυσικών ομάδων (natural groupings). Μια τομή κοντά σε κορύφωση της κατανομής χωρίζει πολλές παρόμοιες τιμές σε διαφορετικά χρώματα. Αντίθετα, ένα όριο σε τοπικό ελάχιστο (local minimum ή valley) ή σε μεγάλο κενό μεταξύ διαδοχικών τιμών διαχωρίζει ομάδες που είναι ήδη σχετικά απομακρυσμένες. Ο κανόνας δεν υπερισχύει ενός τεκμηριωμένου κατωφλίου και επηρεάζεται από την εξομάλυνση (smoothing) της κατανομής.

Η εκτίμηση πυκνότητας πυρήνα (kernel density estimate — KDE) προσφέρει μια ομαλότερη εικόνα από το ιστόγραμμα. Οι κατακόρυφες γραμμές στο επόμενο διάγραμμα δείχνουν τα τοπικά ελάχιστα που βρίσκονται μεταξύ του 5ου και του 95ου ποσοστημορίου:

density_gdp <- density(gdp_pc_values)

turning_points <- diff(sign(diff(density_gdp$y)))
valley_index <- which(turning_points > 0) + 1

candidate_valleys <- density_gdp$x[valley_index]

candidate_valleys <- candidate_valleys[
  candidate_valleys >= quantile(gdp_pc_values, 0.05) &
    candidate_valleys <= quantile(gdp_pc_values, 0.95)
]

plot(
  density_gdp,
  lwd = 2,
  col = "#2166AC",
  main = "Πυκνότητα της κατανομής και υποψήφιες κοιλάδες",
  xlab = "ΑΕΠ κατά κεφαλή (ΜΑΔ ανά κάτοικο)",
  ylab = "Πυκνότητα"
)

rug(gdp_pc_values, col = grDevices::adjustcolor("grey30", 0.35))

if (length(candidate_valleys) > 0) {
  abline(
    v = candidate_valleys,
    col = "#B2182B",
    lwd = 1.5,
    lty = 3
  )
}

Οι υποψήφιες κοιλάδες λειτουργούν ως διαγνωστικό βοήθημα και όχι ως έτοιμα όρια ταξινόμησης. Η θέση και ο αριθμός τους μεταβάλλονται με το εύρος εξομάλυνσης (bandwidth) της KDE. Χρήσιμο συμπλήρωμα είναι η εμπειρική αθροιστική συνάρτηση κατανομής (empirical cumulative distribution function — ECDF): σχεδόν οριζόντια τμήματα υποδηλώνουν αραιές περιοχές τιμών, ενώ απότομα τμήματα αντιστοιχούν σε περιοχές όπου συγκεντρώνονται πολλές παρατηρήσεις.

4.2 Προσαρμογή της μεθόδου στη μορφή της κατανομής

  • Σε περίπου ομοιόμορφη κατανομή ή σε κατανομή χωρίς έντονη ασυμμετρία, τα ίσα διαστήματα (equal intervals) ή τα ευανάγνωστα στρογγυλοποιημένα όρια μπορεί να είναι επαρκή.
  • Σε έντονα ασύμμετρη κατανομή ή κατανομή με βαριά ουρά (heavy-tailed distribution) εξετάζουμε γεωμετρικά διαστήματα (geometric intervals), λογαριθμική απεικόνιση (logarithmic mapping) ή head/tail breaks, εφόσον διατηρείται η ερμηνεία της μεταβλητής.
  • Σε πολυτροπική κατανομή (multimodal distribution) οι φυσικές τομές ή τεκμηριωμένα χειροκίνητα όρια μπορούν να ακολουθήσουν τα κενά μεταξύ των συστάδων.
  • Όταν ενδιαφέρει κυρίως η σχετική κατάταξη, τα ποσοστημόρια είναι χρήσιμα, αλλά πρέπει να ελέγξουμε αν χωρίζουν σχεδόν ίδιες τιμές.
  • Όταν υπάρχουν στόχοι πολιτικής ή κοινή χρονική σύγκριση, προτιμούμε σταθερά όρια, ακόμη και αν δεν συμπίπτουν με τις κοιλάδες της κατανομής.
  • Αν λίγες ακραίες τιμές συμπιέζουν όλες τις υπόλοιπες, εξετάζουμε ξεχωριστή κλάση για αυτές, μετασχηματισμό (transformation) με σαφή αιτιολόγηση ή συνεχή κλίμακα. Δεν απομακρύνουμε, όμως, τιμές μόνο και μόνο για να γίνει ο χάρτης πιο ομοιόμορφος.

Η συνεκτίμηση της κατανομής, του χωρικού προτύπου και του σκοπού του χάρτη ακολουθεί την προσέγγιση των Andrienko, Andrienko και Savinov (2001), καθώς και τις πρακτικές οδηγίες των ArcGIS Pro και του EU Data Visualisation Guide. Κοινή αφετηρία είναι ότι η μέθοδος επιλέγεται με βάση τον αναλυτικό σκοπό, την κατανομή και την απαιτούμενη συγκρισιμότητα — όχι μόνο με βάση την εμφάνιση του τελικού χάρτη.

5. Συνεχής κλίμακα (continuous scale) ή ταξινόμηση σε κλάσεις;

5.1 Συνεχής κλίμακα (continuous scale)

Η συνεχής κλίμακα διατηρεί τη συνέχεια της αριθμητικής μεταβλητής. Κάθε τιμή αντιστοιχίζεται σε θέση πάνω σε ένα συνεχές χρωματικό φάσμα.

Είναι χρήσιμη όταν:

  • θέλουμε να τονίσουμε τη βαθμιαία μεταβολή,
  • το κοινό είναι εξοικειωμένο με συνεχή υπομνήματα,
  • δεν υπάρχουν θεσμικά ή αναλυτικά κατώφλια, και
  • ο χάρτης χρησιμοποιείται κυρίως για διερεύνηση.
tmap::tmap_mode("plot")

upper_gdp <- max(
  60000,
  ceiling(max(nuts2_map$gdp_pc_pps, na.rm = TRUE) / 10000) * 10000
)

ticks_gdp <- pretty(c(0, upper_gdp), n = 7)
ticks_gdp <- ticks_gdp[
  ticks_gdp >= 0 &
    ticks_gdp <= upper_gdp
]

map_continuous <-
  tm_shape(bg_non_eu, bbox = eu_bbox) +
  tm_polygons(
    fill = "grey95",
    fill_alpha = 1,
    col = "grey85",
    lwd = 0.3
  ) +
  tm_shape(nuts2_map, bbox = eu_bbox) +
  tm_polygons(
    fill = "gdp_pc_pps",
    fill.scale = tm_scale_continuous(
      limits = c(0, upper_gdp),
      ticks = ticks_gdp,
      values = "hcl.blues3",
      value.na = "grey80",
      label.na = "ΜΔ"
    ),
    fill.legend = tm_legend(
      title = "ΑΕΠ κατά κεφαλή\n(ΜΑΔ ανά κάτοικο)",
      position = tm_pos_out("right", "center")
    )
  ) +
  tm_borders(col = "grey80", lwd = 0.25) +
  tm_shape(nuts0_eu27, bbox = eu_bbox) +
  tm_borders(col = "grey25", lwd = 0.8) +
  tm_layout(
    frame = TRUE,
    legend.outside = TRUE
  ) +
  tm_title(
    paste0(
      "Συνεχής χρωματική κλίμακα\n",
      "ΑΕΠ κατά κεφαλή σε ΜΑΔ, NUTS-2, ",
      intime
    ),
    size = 0.9
  ) +
  tm_credits(
    paste0(
      "Πηγή: Eurostat (",
      indata,
      ", NUTS 2024) • ",
      inname
    ),
    position = c("left", "bottom"),
    scale = 0.5,
    fontface = "italic"
  )

map_continuous

5.2 Ταξινόμηση σε κλάσεις (data classification)

Η ταξινόμηση απλοποιεί την κατανομή σε έναν μικρό αριθμό διακριτών ομάδων. Διευκολύνει την ανάγνωση, αλλά εισάγει μια αναλυτική απόφαση: τα όρια των κλάσεων καθορίζουν ποιες περιφέρειες εμφανίζονται ως παρόμοιες και πού τοποθετούνται οι οπτικές ασυνέχειες.

Ο αριθμός των κλάσεων εξαρτάται από:

  • τον αριθμό των χωρικών ενοτήτων,
  • τη μορφή της κατανομής,
  • το μέγεθος και τη χρήση του χάρτη,
  • το επίπεδο εξοικείωσης του κοινού, και
  • την ανάγκη σύγκρισης με άλλους χάρτες.

Στην πράξη, πέντε έως επτά κλάσεις αποτελούν συχνά λειτουργικό σημείο εκκίνησης — όχι καθολικό κανόνα.

6. Μέθοδοι και έλεγχος της ταξινόμησης

6.1 Επιλεγμένες μέθοδοι ταξινόμησης (selected classification methods)

Παρουσιάζουμε τέσσερις συχνές μεθόδους ταξινόμησης και, στη συνέχεια, ορισμένες πρόσθετες επιλογές που είναι διαθέσιμες στο tmap.

6.1.1 Ίσα διαστήματα (equal intervals)

Η μέθοδος διαιρεί το συνολικό εύρος τιμών σε κλάσεις ίσου πλάτους.

Πλεονεκτήματα

  • είναι απλή και εύκολα εξηγήσιμη,
  • αποδίδει άμεσα το αριθμητικό μέγεθος,
  • επιτρέπει σύγκριση όταν διατηρούνται σταθερά όρια.

Περιορισμοί

  • σε ασύμμετρες κατανομές μπορεί να συγκεντρώνει πολλές παρατηρήσεις σε μία κλάση,
  • επηρεάζεται έντονα από ακραίες τιμές.

Στο tmap χρησιμοποιούμε style = "equal".

6.1.2 Ποσοστημόρια (quantiles)

Η μέθοδος των ποσοστημορίων τοποθετεί περίπου τον ίδιο αριθμό χωρικών ενοτήτων σε κάθε κλάση.

Πλεονεκτήματα

  • γεμίζει οπτικά όλες τις κλάσεις,
  • αναδεικνύει τη σχετική θέση κάθε περιφέρειας,
  • είναι χρήσιμη για κατάταξη.

Περιορισμοί

  • πολύ κοντινές τιμές μπορεί να βρεθούν σε διαφορετικές κλάσεις,
  • πολύ διαφορετικές τιμές μπορεί να τοποθετηθούν στην ίδια κλάση,
  • οι κλάσεις περιλαμβάνουν παρόμοιο αριθμό παρατηρήσεων, αλλά καλύπτουν διαφορετικά διαστήματα τιμών.

Στο tmap χρησιμοποιούμε style = "quantile".

6.1.3 Φυσικές τομές Fisher–Jenks (Fisher–Jenks natural breaks)

Η μέθοδος αναζητά κλάσεις με μικρή διασπορά στο εσωτερικό τους και σαφείς διαφορές μεταξύ τους. Είναι κατάλληλη όταν η κατανομή εμφανίζει ομάδες τιμών που χωρίζονται από σχετικά μεγάλα κενά.

Πλεονεκτήματα

  • προσαρμόζεται στη δομή των δεδομένων,
  • συχνά αναδεικνύει καθαρά υπαρκτές ομάδες τιμών.

Περιορισμοί

  • τα όρια εξαρτώνται από το συγκεκριμένο σύνολο δεδομένων,
  • δυσκολεύει τη σύγκριση διαφορετικών ετών ή περιοχών,
  • μια μικρή αλλαγή στα δεδομένα μπορεί να μεταβάλει τα όρια.

Το classInt παρέχει τις επιλογές "fisher" και "jenks". Στο παράδειγμα χρησιμοποιούμε style = "fisher", δηλαδή την ταχύτερη υλοποίηση του προβλήματος ομαδοποίησης Fisher–Jenks.

6.1.4 Προκαθορισμένα ή σταθερά όρια (fixed breaks)

Τα όρια καθορίζονται από τον αναλυτή με βάση:

  • θεωρητικά ή θεσμικά κατώφλια,
  • στόχους πολιτικής,
  • καθιερωμένες συμβάσεις,
  • μια ουσιαστική τιμή αναφοράς, ή
  • την ανάγκη συγκρισιμότητας μεταξύ χαρτών.

Στο παράδειγμα του κατά κεφαλή ΑΕΠ σε ΜΑΔ χρησιμοποιούμε στρογγυλοποιημένα, προκαθορισμένα όρια, ώστε το υπόμνημα να είναι εύκολα αναγνώσιμο και να μπορεί να επαναχρησιμοποιηθεί σε άλλον χάρτη ή έτος. Τα συγκεκριμένα όρια επιλέγονται μόνο για τις ανάγκες του παραδείγματος και δεν αποτελούν επίσημα κατώφλια οικονομικής πολιτικής.

Όταν υπάρχει σαφώς τεκμηριωμένο κανονιστικό ή αναλυτικό σημείο αναφοράς — όπως ο δείκτης EU27_2020 = 100 — τα σταθερά όρια αποκτούν ισχυρότερη ουσιαστική τεκμηρίωση. Η περίπτωση αυτή παρουσιάζεται χωριστά στην ενότητα για τα αποκλίνοντα χρωματικά σχήματα.

Στο tmap χρησιμοποιούμε style = "fixed" και δηλώνουμε τα όρια με το όρισμα breaks.

6.1.5 Άλλες διαθέσιμες μέθοδοι (other classification methods)

Η tm_scale_intervals() υποστηρίζει επίσης, μεταξύ άλλων:

  • "pretty" για στρογγυλοποιημένα και ευανάγνωστα όρια,
  • "sd" για κλάσεις γύρω από τον μέσο όρο και την τυπική απόκλιση,
  • "kmeans" και "hclust" για ταξινομήσεις βασισμένες σε συσταδοποίηση,
  • "headtails" για έντονα ασύμμετρες κατανομές με βαριά ουρά, και
  • "log10_pretty" για λογαριθμική κλίμακα με ευανάγνωστα όρια.

Η ύπαρξη μιας μεθόδου στο λογισμικό δεν σημαίνει ότι είναι αυτόματα κατάλληλη. Η επιλογή πρέπει να συνδέεται με το ερευνητικό ερώτημα και να δηλώνεται στην τεκμηρίωση του χάρτη.

6.2 Επιθεώρηση των ορίων (break inspection) με classInt

Πριν κατασκευάσουμε τους χάρτες, μπορούμε να δούμε τα όρια που παράγει κάθε μέθοδος:

gdp_values <- stats::na.omit(nuts2_map$gdp_pc_pps)

breaks_equal <- classInt::classIntervals(
  gdp_values,
  n = 5,
  style = "equal"
)$brks

breaks_quantile <- classInt::classIntervals(
  gdp_values,
  n = 5,
  style = "quantile"
)$brks

breaks_fisher <- classInt::classIntervals(
  gdp_values,
  n = 5,
  style = "fisher"
)$brks

round(breaks_equal, 1)
[1]  12000  31040  50080  69120  88160 107200
round(breaks_quantile, 1)
[1]  12000  25760  32280  38580  46340 107200
round(breaks_fisher, 1)
[1]  12000  30250  41550  56750  82400 107200

Δεν αρκεί να δούμε μόνο τα αριθμητικά όρια. Ελέγχουμε επίσης πόσες παρατηρήσεις περιλαμβάνει κάθε κλάση:

table(
  classInt::classify_intervals(
    gdp_values,
    n = 5,
    style = "equal"
  )
)

 [12000,31040)  [31040,50080)  [50080,69120)  [69120,88160) [88160,107200] 
            91            117             27              7              2 
table(
  classInt::classify_intervals(
    gdp_values,
    n = 5,
    style = "quantile"
  )
)

 [12000,25760)  [25760,32280)  [32280,38580)  [38580,46340) [46340,107200] 
            49             49             48             49             49 
table(
  classInt::classify_intervals(
    gdp_values,
    n = 5,
    style = "fisher"
  )
)

 [12000,30250)  [30250,41550)  [41550,56750)  [56750,82400) [82400,107200] 
            87             78             58             18              3 

6.3 Προαιρετικός ποσοτικός διαγνωστικός έλεγχος (optional quantitative diagnostics)

Η επιλογή ορίων μπορεί να υποστηριχθεί από περισσότερους του ενός δείκτες. Κανένας δείκτης δεν αποτελεί από μόνος του απόδειξη «ορθής» ταξινόμησης:

  1. Η κανονικοποιημένη εντροπία Shannon των συχνοτήτων των κλάσεων (normalized Shannon entropy of class frequencies) είναι:

    \[ H^* = \frac{-\sum_{j=1}^{k} p_j \log(p_j)} {\log(k)} \]

    όπου \(p_j\) είναι το ποσοστό των παρατηρήσεων στην κλάση \(j\). Η τιμή κυμαίνεται από 0 έως 1. Υψηλή τιμή σημαίνει πιο ισόρροπη κατανομή παρατηρήσεων μεταξύ των κλάσεων· γι’ αυτό τα ποσοστημόρια τείνουν προς το 1. Δεν σημαίνει, όμως, ότι οι τιμές μέσα σε κάθε κλάση είναι ομοιογενείς.

  2. Ο δείκτης προσαρμογής διακύμανσης (Goodness of Variance Fit — GVF) είναι:

    \[ GVF = 1 - \frac{SDCM}{SDAM} \]

    όπου SDAM είναι το συνολικό άθροισμα τετραγωνικών αποκλίσεων από τον γενικό μέσο και SDCM το άθροισμα των τετραγωνικών αποκλίσεων από τους μέσους των επιμέρους κλάσεων. Τιμή κοντά στο 1 σημαίνει μικρότερη ενδοομαδική διασπορά. Η Fisher–Jenks βελτιστοποιεί ακριβώς αυτή τη λογική ομοιογένειας, αλλά παραμένει εξαρτημένη από το συγκεκριμένο δείγμα.

  3. Η σχετική πυκνότητα στα όρια (relative density at class breaks) συγκρίνει την πυκνότητα της KDE στα εσωτερικά όρια με τη μέγιστη πυκνότητα της κατανομής. Μικρότερη τιμή υποδηλώνει ότι οι τομές βρίσκονται, κατά μέσο όρο, πιο κοντά σε κοιλάδες. Ο δείκτης είναι ευαίσθητος στο bandwidth και δεν υπερισχύει ουσιαστικών κατωφλίων.

Ορίζουμε πρώτα τα ενδεικτικά σταθερά όρια που θα χρησιμοποιηθούν και στους χάρτες:

upper_reference <- max(
  80000,
  (floor(max(gdp_values) / 10000) + 1) * 10000
)

reference_breaks <- sort(unique(
  c(
    0,
    20000,
    30000,
    40000,
    60000,
    upper_reference
  )
))

Στη συνέχεια υπολογίζουμε τους τρεις διαγνωστικούς δείκτες και το μικρότερο και μεγαλύτερο πλήθος παρατηρήσεων ανά κλάση:

classification_diagnostics <- function(x, breaks) {
  x <- as.numeric(x)
  x <- x[is.finite(x)]
  breaks <- sort(unique(as.numeric(breaks)))

  if (
    length(breaks) < 3 ||
      min(breaks) > min(x) ||
      max(breaks) < max(x)
  ) {
    stop("Τα breaks πρέπει να καλύπτουν όλο το εύρος των τιμών.")
  }

  n_classes <- length(breaks) - 1

  class_id <- cut(
    x,
    breaks = breaks,
    include.lowest = TRUE,
    right = FALSE,
    labels = FALSE
  )

  # Το ανώτερο άκρο εντάσσεται ρητά στην τελευταία κλάση.
  class_id[
    is.na(class_id) &
      x == max(breaks)
  ] <- n_classes

  if (anyNA(class_id)) {
    stop("Ορισμένες τιμές δεν εντάχθηκαν σε κλάση.")
  }

  class_counts <- tabulate(
    class_id,
    nbins = n_classes
  )

  class_probabilities <- class_counts / sum(class_counts)
  positive_probabilities <- class_probabilities[
    class_probabilities > 0
  ]

  normalized_entropy <-
    -sum(
      positive_probabilities *
        log(positive_probabilities)
    ) / log(n_classes)

  sdam <- sum((x - mean(x))^2)

  sdcm <- sum(vapply(
    split(x, class_id),
    function(class_values) {
      sum(
        (class_values - mean(class_values))^2
      )
    },
    numeric(1)
  ))

  gvf <- if (sdam > 0) {
    1 - sdcm / sdam
  } else {
    NA_real_
  }

  density_fit <- density(x)
  internal_breaks <- breaks[
    seq.int(2, length(breaks) - 1)
  ]

  relative_break_density <- mean(
    approx(
      density_fit$x,
      density_fit$y,
      xout = internal_breaks,
      rule = 2
    )$y
  ) / max(density_fit$y)

  data.frame(
    classes = n_classes,
    minimum_class_n = min(class_counts),
    maximum_class_n = max(class_counts),
    normalized_entropy = normalized_entropy,
    gvf = gvf,
    relative_break_density = relative_break_density
  )
}

diagnostics_table <- dplyr::bind_rows(
  "Ίσα διαστήματα" = classification_diagnostics(
    gdp_values,
    breaks_equal
  ),
  "Ποσοστημόρια" = classification_diagnostics(
    gdp_values,
    breaks_quantile
  ),
  "Fisher–Jenks" = classification_diagnostics(
    gdp_values,
    breaks_fisher
  ),
  "Σταθερά όρια" = classification_diagnostics(
    gdp_values,
    reference_breaks
  ),
  .id = "method"
) |>
  dplyr::mutate(
    normalized_entropy = round(normalized_entropy, 3),
    gvf = round(gvf, 3),
    relative_break_density = round(
      relative_break_density,
      3
    )
  )

diagnostics_table
          method classes minimum_class_n maximum_class_n normalized_entropy
1 Ίσα διαστήματα       5               2             117              0.687
2   Ποσοστημόρια       5              48              49              1.000
3   Fisher–Jenks       5               3              87              0.820
4   Σταθερά όρια       5              13              72              0.889
    gvf relative_break_density
1 0.881                  0.380
2 0.807                  0.815
3 0.920                  0.488
4 0.887                  0.601

Πώς ερμηνεύουμε τον πίνακα

  • υψηλότερο normalized_entropy: πιο ισόρροπος αριθμός περιφερειών ανά κλάση,
  • υψηλότερο gvf: μεγαλύτερη εσωτερική ομοιογένεια των κλάσεων,
  • χαμηλότερο relative_break_density: τομές πιο κοντά σε αραιά τμήματα της κατανομής,
  • πολύ μικρό minimum_class_n: πιθανή υπερβολικά ολιγομελής κλάση.

Δεν υπάρχει, επομένως, ένας γενικός κανόνας «μεγιστοποίησης» ή «ελαχιστοποίησης» της εντροπίας. Η υψηλή εντροπία συχνοτήτων ευνοεί την ισόρροπη κατανομή των παρατηρήσεων στις κλάσεις, ενώ η ελαχιστοποίηση του ενδοομαδικού σφάλματος ευνοεί την ομοιογένεια των τιμών μέσα σε κάθε κλάση. Οι δύο στόχοι μπορεί να οδηγούν σε διαφορετικά όρια. Για τον ίδιο λόγο, δεν συνθέτουμε μηχανικά τους δείκτες σε μία συνολική βαθμολογία· τους συνεκτιμούμε ανάλογα με τον σκοπό του χάρτη.

6.4 Οπτική σύγκριση των μεθόδων (visual comparison)

Δημιουργούμε μια βοηθητική συνάρτηση, ώστε όλοι οι χάρτες να έχουν ακριβώς το ίδιο υπόβαθρο και να διαφέρουν μόνο ως προς την ταξινόμηση.

make_gdp_map <- function(scale_definition, title_text) {
  tm_shape(bg_non_eu, bbox = eu_bbox) +
    tm_polygons(
      fill = "grey95",
      fill_alpha = 1,
      col = "grey85",
      lwd = 0.25
    ) +
    tm_shape(nuts2_map, bbox = eu_bbox) +
    tm_polygons(
      fill = "gdp_pc_pps",
      fill.scale = scale_definition,
      fill.legend = tm_legend(
        title = "ΑΕΠ κατά κεφαλή\n(ΜΑΔ ανά κάτοικο)",
        position = tm_pos_out("right", "center")
      )
    ) +
    tm_borders(col = "grey80", lwd = 0.2) +
    tm_shape(nuts0_eu27, bbox = eu_bbox) +
    tm_borders(col = "grey25", lwd = 0.7) +
    tm_layout(
      frame = TRUE,
      legend.outside = TRUE,
      legend.text.size = 0.55,
      legend.title.size = 0.7
    ) +
    tm_title(title_text, size = 0.8)
}

Ορίζουμε ενδεικτικά σταθερά και στρογγυλοποιημένα όρια. Το ανώτερο όριο προσαρμόζεται στη μέγιστη παρατηρούμενη τιμή, ενώ τα ενδιάμεσα όρια παραμένουν σταθερά.

m_equal <- make_gdp_map(
  tm_scale_intervals(
    n = 5,
    style = "equal",
    values = "hcl.blues3",
    value.na = "grey80",
    label.na = "ΜΔ"
  ),
  "Ίσα διαστήματα"
)

m_quantile <- make_gdp_map(
  tm_scale_intervals(
    n = 5,
    style = "quantile",
    values = "hcl.blues3",
    value.na = "grey80",
    label.na = "ΜΔ"
  ),
  "Ποσοστημόρια"
)

m_fisher <- make_gdp_map(
  tm_scale_intervals(
    n = 5,
    style = "fisher",
    values = "hcl.blues3",
    value.na = "grey80",
    label.na = "ΜΔ"
  ),
  "Φυσικές τομές Fisher–Jenks"
)

m_reference <- make_gdp_map(
  tm_scale_intervals(
    style = "fixed",
    breaks = reference_breaks,
    values = "hcl.blues3",
    value.na = "grey80",
    label.na = "ΜΔ"
  ),
  "Προκαθορισμένα στρογγυλοποιημένα όρια"
)
tmap::tmap_arrange(
  m_equal,
  m_quantile,
  m_fisher,
  m_reference,
  ncol = 2
)

Πώς διαβάζουμε τη σύγκριση

Οι τέσσερις χάρτες βασίζονται στις ίδιες τιμές, στις ίδιες περιφέρειες και στο ίδιο γεωγραφικό πλαίσιο. Παρ’ όλα αυτά, μπορεί να δημιουργούν διαφορετική εντύπωση για το πόσο έντονες είναι οι περιφερειακές ανισότητες και για το πού βρίσκονται οι βασικές χωρικές ασυνέχειες.

Δεν αναζητούμε τη μέθοδο που παράγει τον «ωραιότερο» χάρτη. Αναζητούμε εκείνη που απαντά πιο έντιμα και καθαρά στο συγκεκριμένο ερώτημα.

7. Επιλογή χρωματικού σχήματος (color-scheme selection)

Με τον όρο χρωματικό σχήμα (color scheme ή palette) δεν εννοούμε απλώς μια συλλογή ελκυστικών χρωμάτων. Εννοούμε μια οργανωμένη αντιστοίχιση ανάμεσα στη σημασία των τιμών και στη μεταβολή της απόχρωσης, της φωτεινότητας και της χρωματικής έντασης.

7.1 Ποιοτικά χρωματικά σχήματα (qualitative color schemes)

Τα ποιοτικά σχήματα χρησιμοποιούν διακριτές αποχρώσεις χωρίς ενσωματωμένη σειρά. Είναι κατάλληλα για ονομαστικές/κατηγορικές μεταβλητές. Τα χρώματα πρέπει να διακρίνονται καθαρά, αλλά να έχουν όσο γίνεται παρόμοια οπτική βαρύτητα, ώστε μία κατηγορία να μη δεσπόζει χωρίς αναλυτικό λόγο.

Στο ακόλουθο παράδειγμα οι περιφέρειες τεσσάρων επιλεγμένων κρατών μελών χρωματίζονται ανά χώρα. Οι κατηγορίες είναι ισότιμες: κανένα χρώμα δεν πρέπει να υποδηλώνει «περισσότερο» ή «λιγότερο».

selected_countries <- nuts2_map |>
  dplyr::filter(CNTR_CODE %in% c("EL", "ES", "IT", "PT")) |>
  dplyr::mutate(
    country = factor(
      CNTR_CODE,
      levels = c("EL", "ES", "IT", "PT"),
      labels = c("Ελλάδα", "Ισπανία", "Ιταλία", "Πορτογαλία")
    )
  )

map_categorical <-
  tm_shape(selected_countries) +
  tm_polygons(
    fill = "country",
    fill.scale = tm_scale_categorical(
      values = "tol.muted",
      levels = levels(selected_countries$country),
      value.na = "grey85",
      label.na = "ΜΔ"
    ),
    fill.legend = tm_legend(
      title = "Χώρα"
    )
  ) +
  tm_borders(col = "white", lwd = 0.5) +
  tm_layout(frame = TRUE) +
  tm_title(
    "Ονομαστική μεταβλητή: διοικητική υπαγωγή",
    size = 0.9
  )

map_categorical

7.2 Διαδοχικά χρωματικά σχήματα (sequential color schemes)

Τα διαδοχικά σχήματα οργανώνουν τα χρώματα σε αντιληπτή σειρά, συνήθως από ανοιχτές προς σκούρες αποχρώσεις. Μπορεί να βασίζονται σε μία απόχρωση ή να μεταβάλλουν σταδιακά και την απόχρωση, αρκεί η φωτεινότητα να διατηρεί σαφή μονοτονική πορεία. Είναι κατάλληλα για:

  • διατεταγμένες κατηγορίες,
  • ποσοστά,
  • πυκνότητες,
  • δείκτες χωρίς ουσιαστικό κεντρικό σημείο.

Μετατρέπουμε ενδεικτικά το κατά κεφαλή ΑΕΠ σε ΜΑΔ σε πέντε διατεταγμένες βαθμίδες:

nuts2_map <- nuts2_map |>
  dplyr::mutate(
    gdp_position = cut(
      gdp_pc_pps,
      breaks = c(-Inf, 20000, 30000, 40000, 60000, Inf),
      labels = c(
        "<20.000",
        "20.000–30.000",
        "30.000–40.000",
        "40.000–60.000",
        ">60.000"
      ),
      ordered_result = TRUE
    )
  )
map_ordinal <-
  tm_shape(bg_non_eu, bbox = eu_bbox) +
  tm_polygons(
    fill = "grey95",
    col = "grey85",
    lwd = 0.25
  ) +
  tm_shape(nuts2_map, bbox = eu_bbox) +
  tm_polygons(
    fill = "gdp_position",
    fill.scale = tm_scale_ordinal(
      values = "hcl.blues3",
      levels = levels(nuts2_map$gdp_position),
      value.na = "grey80",
      label.na = "ΜΔ"
    ),
    fill.legend = tm_legend(
      title = "ΑΕΠ κατά κεφαλή\n(ΜΑΔ ανά κάτοικο)",
      position = tm_pos_out("right", "center")
    )
  ) +
  tm_borders(col = "grey80", lwd = 0.2) +
  tm_shape(nuts0_eu27, bbox = eu_bbox) +
  tm_borders(col = "grey25", lwd = 0.7) +
  tm_layout(
    frame = TRUE,
    legend.outside = TRUE
  ) +
  tm_title(
    "Διατακτική μεταβλητή με διαδοχικό χρωματικό σχήμα",
    size = 0.9
  )

map_ordinal

7.3 Αποκλίνοντα χρωματικά σχήματα (diverging color schemes)

Τα αποκλίνοντα σχήματα αποτελούνται από δύο διαδοχικούς χρωματικούς κλάδους που απομακρύνονται από ένα κοινό ουδέτερο χρώμα. Είναι κατάλληλα μόνο όταν η μεταβλητή διαθέτει ουσιαστική κεντρική τιμή, όπως το 0, έναν μέσο όρο, έναν στόχο πολιτικής ή —στο παράδειγμά μας— τον δείκτη EU27_2020 = 100.

Εδώ χρησιμοποιούμε για πρώτη φορά τον δείκτη αντί της τιμής του κατά κεφαλή ΑΕΠ σε ΜΑΔ:

nuts2_map |>
  sf::st_drop_geometry() |>
  dplyr::select(NUTS_ID, gdp_pc_index, gdp_index_gap) |>
  head()
# A tibble: 6 × 3
  NUTS_ID gdp_pc_index gdp_index_gap
  <chr>          <dbl>         <dbl>
1 DE12             124            24
2 DE13             105             5
3 DE14             122            22
4 DE21             170            70
5 DE22             113            13
6 DE23             122            22

όπου:

\[ \text{gdp\_index\_gap}_i = \text{gdp\_pc\_index}_i - 100 \]

Άρα:

  • αρνητική τιμή σημαίνει ότι η περιφέρεια βρίσκεται κάτω από την αναφορά της ΕΕ27,
  • μηδενική τιμή αντιστοιχεί στην αναφορά,
  • θετική τιμή σημαίνει ότι βρίσκεται πάνω από την αναφορά.
gap_limit <- max(
  50,
  ceiling(max(abs(nuts2_map$gdp_index_gap), na.rm = TRUE) / 25) * 25
)

gap_ticks <- pretty(
  c(-gap_limit, gap_limit),
  n = 7
)

# Η pretty() μπορεί να επιστρέψει ακραία ticks λίγο έξω από τα limits.
gap_ticks <- gap_ticks[
  gap_ticks >= -gap_limit &
    gap_ticks <= gap_limit
]

map_diverging <-
  tm_shape(bg_non_eu, bbox = eu_bbox) +
  tm_polygons(
    fill = "grey95",
    col = "grey85",
    lwd = 0.25
  ) +
  tm_shape(nuts2_map, bbox = eu_bbox) +
  tm_polygons(
    fill = "gdp_index_gap",
    fill.scale = tm_scale_continuous(
      limits = c(-gap_limit, gap_limit),
      ticks = gap_ticks,
      midpoint = 0,
      values = c("#2166AC", "#F7F7F7", "#B2182B"),
      value.na = "grey80",
      label.na = "ΜΔ"
    ),
    fill.legend = tm_legend(
      title = "Απόκλιση του δείκτη\nαπό το EU27_2020 = 100",
      position = tm_pos_out("right", "center")
    )
  ) +
  tm_borders(col = "grey80", lwd = 0.2) +
  tm_shape(nuts0_eu27, bbox = eu_bbox) +
  tm_borders(col = "grey25", lwd = 0.7) +
  tm_layout(
    frame = TRUE,
    legend.outside = TRUE
  ) +
  tm_title(
    "Απόκλιση του δείκτη κατά κεφαλή ΑΕΠ από την αναφορά της ΕΕ27",
    size = 0.9
  )

map_diverging

Το ουδέτερο χρώμα πρέπει να αντιστοιχεί ακριβώς στην τιμή 0 της νέας μεταβλητής — δηλαδή στην αρχική τιμή 100 — και όχι απλώς στη μέση του παρατηρούμενου εύρους.

Το χρωματικό σχήμα δεν ορίζει από μόνο του την ερμηνεία

Το μπλε και το κόκκινο δεν σημαίνουν εγγενώς «καλό» και «κακό». Η σημασία τους προκύπτει από τον τίτλο, το υπόμνημα, τη φορά του χρωματικού σχήματος και το περιεχόμενο της μεταβλητής. Αν χρειάζεται, αντιστρέφουμε τη φορά ή επιλέγουμε αποχρώσεις που δεν μεταφέρουν ανεπιθύμητη αξιολογική φόρτιση.

7.4 Αναζήτηση και έλεγχος χρωματικών σχημάτων (palette search and evaluation) με cols4all

Το tmap 4 χρησιμοποιεί ονομαστικά χρωματικά σχήματα από το cols4all. Το πακέτο δεν λειτουργεί μόνο ως κατάλογος χρωμάτων· οργανώνει τα χρωματικά σχήματα ανά λειτουργικό τύπο και παρέχει πληροφορίες για:

  • τον μέγιστο προτεινόμενο αριθμό χρωμάτων,
  • τη χρωματική σειρά από την οποία προέρχονται,
  • τη συμπεριφορά τους σε προσομοιώσεις διαφορετικών μορφών δυσχρωματοψίας (color vision deficiency — CVD),
  • την οπτική ισορροπία και την αντιληπτή σειρά, και
  • το προτεινόμενο χρώμα για ελλιπείς τιμές (missing values).

Οι βασικοί τύποι που αναγνωρίζει το cols4all είναι:

Κωδικός Οικογένεια Βασική χρήση
cat κατηγορική/ποιοτική διακριτές κατηγορίες χωρίς σειρά
seq διαδοχική διατεταγμένες ή ποσοτικές τιμές από χαμηλές προς υψηλές
div αποκλίνουσα δύο κατευθύνσεις γύρω από ουσιαστικό κέντρο
cyc κυκλική (cyclic) μεταβλητές όπου το τέλος συνδέεται με την αρχή
bivs, bivc, bivd, bivg διμεταβλητή (bivariate) ταυτόχρονη απόδοση δύο μεταβλητών

Μπορούμε να δούμε τις διαθέσιμες οικογένειες και μερικά ονόματα χρωματικών σχημάτων:

cols4all::c4a_types()
  type                          description
1  cat                          categorical
2  seq                           sequential
3  div                            diverging
4  cyc                               cyclic
5 bivs  bivariate (sequential x sequential)
6 bivc bivariate (sequential x categorical)
7 bivd   bivariate (sequential x diverging)
8 bivg bivariate (sequential x desaturated)
head(cols4all::c4a_palettes(type = "cat"), 8)
[1] "misc.r3"        "misc.r4"        "misc.ggplot2"   "misc.okabe"    
[5] "brewer.accent"  "brewer.dark2"   "brewer.paired"  "brewer.pastel1"
head(cols4all::c4a_palettes(type = "seq"), 8)
[1] "hcl.grays"       "hcl.light_grays" "hcl.blues2"      "hcl.blues3"     
[5] "hcl.purples2"    "hcl.purples3"    "hcl.reds2"       "hcl.reds3"      
head(cols4all::c4a_palettes(type = "div"), 8)
[1] "hcl.blue_red"     "hcl.blue_red2"    "hcl.blue_red3"    "hcl.red_green"   
[5] "hcl.purple_green" "hcl.purple_brown" "hcl.green_brown"  "hcl.blue_yellow2"

Η c4a_info() επιστρέφει τον τύπο, τη σειρά προέλευσης, τον μέγιστο αριθμό χρωμάτων (nmax) και το χρώμα των ελλιπών τιμών:

palette_info <- cols4all::c4a_info(
  "tol.muted",
  verbose = FALSE
)

palette_info[c(
  "name",
  "series",
  "type",
  "nmax",
  "na"
)]
$name
[1] "muted"

$series
[1] "tol"

$type
[1] "cat"

$nmax
[1] 10

$na
[1] "#FFFFFF"

Στη συνέχεια προβάλλουμε ένα παράδειγμα από καθεμία από τις τρεις συχνότερες οικογένειες:

Χρωματικό σχήμα Οικογένεια Χρωματική οργάνωση και ενδεδειγμένη ανάγνωση
tol.muted ποιοτική διακριτές, σχετικά ήπιες αποχρώσεις για κατηγορίες χωρίς σειρά
hcl.blues3 διαδοχική πορεία από ανοιχτό προς σκούρο μπλε για τιμές με σαφή διάταξη
tol.pu_gn αποκλίνουσα μωβ και πράσινος κλάδος γύρω από ανοιχτό κέντρο για αποκλίσεις από σημείο αναφοράς

Στα διαγράμματα που ακολουθούν, η θέση με την ένδειξη NA δείχνει το προτεινόμενο χρώμα για τις ελλιπείς τιμές.

cols4all::c4a_plot(
  "tol.muted",
  n = 6,
  include.na = TRUE,
  nrows = 1
)

cols4all::c4a_plot(
  "hcl.blues3",
  n = 7,
  include.na = TRUE,
  nrows = 1
)

cols4all::c4a_plot(
  "tol.pu_gn",
  n = 7,
  include.na = TRUE,
  nrows = 1
)

Για να προσομοιώσουμε την εμφάνιση ενός χρωματικού σχήματος σε συνήθεις μορφές δυσχρωματοψίας:

cols4all::c4a_plot_cvd(
  "tol.muted",
  n = 6,
  include.na = TRUE
)

Για διαδραστική διερεύνηση όλων των διαθέσιμων χρωματικών σχημάτων:

cols4all::c4a_gui()

Η επιλογή μπορεί να ακολουθήσει την εξής σειρά:

  1. επιλέγουμε πρώτα τον λειτουργικό τύπο (cat, seq, div, cyc),
  2. ελέγχουμε αν ο αριθμός των κλάσεων υπερβαίνει το nmax,
  3. εξετάζουμε τη μεταβολή της φωτεινότητας και τη διάκριση των χρωμάτων,
  4. ελέγχουμε τις προσομοιώσεις δυσχρωματοψίας,
  5. επιβεβαιώνουμε ότι το χρώμα NA δεν συγχέεται με κανονική κατηγορία.

Η φορά πολλών χρωματικών σχημάτων μπορεί να αντιστραφεί προσθέτοντας - πριν από το όνομά τους:

tm_scale_intervals(
  n = 5,
  style = "quantile",
  values = "-hcl.blues3"
)

8. Συνολικές ποσότητες: αναλογικά σύμβολα (proportional symbols) αντί για χωροπληθή χάρτη

Το συνολικό ΑΕΠ είναι χωρικά αθροίσιμη ποσότητα: το ΑΕΠ μιας ευρύτερης περιοχής προκύπτει ως άθροισμα των επιμέρους περιφερειών. Εφόσον μας ενδιαφέρει το ίδιο το συνολικό οικονομικό μέγεθος —και όχι μια κατά κεφαλή τιμή— το αποδίδουμε με κύκλους πάνω στις περιφέρειες.

8.1 Θεωρητική αφετηρία: αναλογικότητα επιφάνειας (area proportionality)

Στον κύκλο η επιφάνεια (area) συνδέεται με την ακτίνα (radius) μέσω της σχέσης:

\[ A = \pi r^2 \]

Επομένως, η φράση «το μέγεθος του συμβόλου είναι ανάλογο της τιμής» χρειάζεται αποσαφήνιση. Αν η ακτίνα ή η διάμετρος (diameter) αυξάνεται γραμμικά με την τιμή, η επιφάνεια αυξάνεται με το τετράγωνό της και οι μεγάλες τιμές υπερτονίζονται.

Για άμεση σύγκριση των μεθόδων μπορούμε να γράψουμε:

\[ d_i = d_{\max} \left(\frac{v_i}{v_{\max}}\right)^p \]

όπου \(d_i\) είναι το εμφανιζόμενο γραμμικό μέγεθος —η διάμετρος ή, ισοδύναμα ως προς τις αναλογίες, η ακτίνα— και \(p\) ο εκθέτης κλιμάκωσης.

Μέθοδος Εκθέτης \(p\) Σχέση με την τιμή Χαρτογραφική συνέπεια
ακτίνα/διάμετρος 1.00 \(d \propto v\) η επιφάνεια αυξάνεται ως \(v^2\) και οι μεγάλες τιμές υπερτονίζονται
επιφάνεια 0.50 \(d \propto \sqrt{v}\) η επιφάνεια του κύκλου είναι ανάλογη της τιμής
Flannery 0.57 \(d \propto v^{0.57}\) ενισχύει ελαφρά τη διαφορά μεγεθών για αντιληπτική διόρθωση

Ποια επιλογή θεωρούμε βασική;

Για έναν αυστηρά αναλογικό χάρτη κυκλικών συμβόλων, η γεωμετρικά συνεπής επιλογή είναι το p = 0.5: διπλάσια τιμή σημαίνει διπλάσια επιφάνεια, όχι διπλάσια διάμετρο. Η μέθοδος Flannery (p = 0.57) δεν είναι «περισσότερο αναλογική»· αποτελεί δηλωμένη αντιληπτική διόρθωση. Η γραμμική μέθοδος p = 1 είναι χρήσιμη για σύγκριση, αλλά συνήθως δεν είναι κατάλληλη όταν ο αναγνώστης αντιλαμβάνεται το εμβαδόν του κύκλου ως ποσότητα.

Η αντιληπτική κλιμάκωση Flannery (Flannery perceptual scaling) επιχειρεί να αντισταθμίσει την τάση των αναγνωστών να υποεκτιμούν τις διαφορές μεταξύ επιφανειών κύκλων. Στην τεκμηρίωση του QGIS χρησιμοποιείται δύναμη 0.57. Πρόκειται ακριβέστερα για κλιμάκωση με δύναμη (power scaling) και όχι για εκθετική συνάρτηση (exponential function) της μορφής \(e^x\).

Ο εκθέτης μπορεί να προσαρμοστεί, αλλά πρέπει να δηλώνεται:

  • για \(p < 0.5\) οι μικρότερες τιμές εμφανίζονται σχετικά μεγαλύτερες,
  • για \(p = 0.5\) διατηρείται η αυστηρή αναλογικότητα της επιφάνειας,
  • για \(p > 0.5\) αυξάνεται η οπτική αντίθεση υπέρ των μεγαλύτερων τιμών.

Ορολογική διευκρίνιση

Το QGIS ονομάζει την πρώτη μέθοδο Radius, παρότι η τεκμηρίωσή του αναφέρεται στη γραμμική μεταβολή του εμφανιζόμενου μεγέθους/της διαμέτρου. Για κυκλικά σύμβολα η ακτίνα και η διάμετρος διαφέρουν μόνο κατά σταθερό συντελεστή· το κρίσιμο ζήτημα είναι αν το γραμμικό μέγεθος ή η επιφάνεια συνδέεται με την τιμή.

8.2 Αυτόματη κλιμάκωση (automatic scaling) με tm_bubbles()

Το tm_bubbles() είναι ειδική εκδοχή του tm_symbols() για αναλογικούς κύκλους. Όταν στο size δώσουμε μια αριθμητική μεταβλητή, το tmap ακολουθεί την εξής διαδικασία:

Στάδιο Τι συμβαίνει
size = "gdp_total" διαβάζονται οι αρχικές τιμές του συνολικού ΑΕΠ
αυτόματη επιλογή κλίμακας για αριθμητικό size επιλέγεται συνεχής κλίμακα
προεπιλογή για size.bubbles το εμφανιζόμενο γραμμικό μέγεθος μεταβάλλεται με τετραγωνική ρίζα
values.scale πολλαπλασιάζει όλα τα σύμβολα χωρίς να αλλάζει τον εκθέτη
υπόμνημα διατηρεί τις αρχικές τιμές της μεταβλητής

Σε απλουστευμένη μορφή, η προεπιλεγμένη αντιστοίχιση είναι:

\[ d_i = C \sqrt{\frac{v_i}{v_{\max}}} \]

όπου η σταθερά \(C\) καθορίζει το συνολικό μέγεθος των συμβόλων στη σελίδα ή στην οθόνη. Επομένως, η επιφάνεια είναι ανάλογη της τιμής. Το values.scale μεταβάλλει το \(C\), όχι τη σχέση τετραγωνικής ρίζας.

Στον κώδικα δηλώνουμε ρητά την ακολουθία οπτικών μεγεθών με values = tm_seq(power = "sqrt"), ώστε η επιλογή της τετραγωνικής ρίζας να είναι σαφής και να μη βασίζεται σε μια έμμεση προεπιλογή.

Κλίμακα δεδομένων και κλίμακα συμβόλων

Η tm_scale_continuous_sqrt() μετασχηματίζει την ίδια την αριθμητική κλίμακα των δεδομένων. Για την αναλογικότητα της επιφάνειας των φυσαλίδων θέλουμε να διατηρήσουμε γραμμική την κλίμακα των αρχικών τιμών και να εφαρμόσουμε την τετραγωνική ρίζα στην ακολουθία των οπτικών μεγεθών. Γι’ αυτό χρησιμοποιούμε tm_scale_continuous(values = tm_seq(power = "sqrt")).

nuts2_points <- nuts2_map |>
  dplyr::filter(
    is.finite(gdp_total),
    gdp_total >= 0
  ) |>
  sf::st_point_on_surface()

gdp_total_max <- max(
  nuts2_points$gdp_total,
  na.rm = TRUE
)
map_symbols <-
  tm_shape(bg_non_eu, bbox = eu_bbox) +
  tm_polygons(
    fill = "grey95",
    col = "grey85",
    lwd = 0.25
  ) +
  tm_shape(nuts2_map, bbox = eu_bbox) +
  tm_polygons(
    fill = "grey92",
    col = "grey80",
    lwd = 0.2
  ) +
  tm_shape(nuts0_eu27, bbox = eu_bbox) +
  tm_borders(col = "grey25", lwd = 0.7) +
  tm_shape(nuts2_points, bbox = eu_bbox) +
  tm_bubbles(
    size = "gdp_total",
    fill = "#E69F00",
    fill_alpha = 0.55,
    col = "#7A4A00",
    lwd = 0.4,
    size.scale = tm_scale_continuous(
      limits = c(0, gdp_total_max),
      values = tm_seq(power = "sqrt"),
      values.scale = 1.15
    ),
    size.legend = tm_legend(
      title = "Συνολικό ΑΕΠ\n(εκατ. ΜΑΔ)",
      position = tm_pos_out("right", "center")
    )
  ) +
  tm_layout(
    frame = TRUE,
    legend.outside = TRUE
  ) +
  tm_title(
    paste0(
      "Συνολικό ΑΕΠ με αναλογικά σύμβολα\n",
      "NUTS-2, ",
      intime
    ),
    size = 0.9
  )

map_symbols

Τα μεγέθη του tm_bubbles() εκφράζονται σε τυπογραφικές μονάδες (typographic units) και παραμένουν σταθερά ως προς τη σελίδα ή την οθόνη. Δεν πρέπει να συγχέονται με το tm_circles(), όπου το μέγεθος δηλώνει γεωγραφική ακτίνα σε μέτρα και μεταβάλλεται οπτικά με τη μεγέθυνση του χάρτη.

8.3 Προαιρετική χειροκίνητη κλιμάκωση (optional manual scaling)

Η αυτόματη διαδικασία είναι η απλούστερη επιλογή για έναν τυπικό χάρτη αναλογικών συμβόλων. Η χειροκίνητη διαδικασία είναι χρήσιμη όταν θέλουμε:

  • να εφαρμόσουμε διαφορετικό ή προσαρμοσμένο εκθέτη,
  • να χρησιμοποιήσουμε κοινό μέγιστο μέγεθος σε πολλούς χάρτες,
  • να αναπαράγουμε ακριβώς μια μέθοδο άλλου λογισμικού, ή
  • να ελέγξουμε πλήρως την αντιστοίχιση τιμής–μεγέθους.

Η αυτόματη και η χειροκίνητη διαδικασία στηρίζονται στην ίδια βασική αρχή. Η διαφορά βρίσκεται κυρίως στον βαθμό ελέγχου που έχουμε πάνω στην κλιμάκωση:

Διαδικασία Πότε τη χρησιμοποιούμε Τρόπος κλιμάκωσης
αυτόματη tm_bubbles() όταν αρκούν η αυτόματη κλιμάκωση και το αυτόματο υπόμνημα tm_seq(power = "sqrt")
χειροκίνητη όταν χρειαζόμαστε κοινό μέγιστο μέγεθος ή πλήρη έλεγχο προϋπολογισμός με p = 0.5
Flannery, αυτόματη ή χειροκίνητη όταν επιλέγεται ρητά αντιληπτική διόρθωση p = 0.57 ή sqrt_perceptual

Αν θέλουμε μόνο τη διόρθωση Flannery, το tmap μπορεί να την εφαρμόσει και αυτόματα, διατηρώντας το αυτόματο υπόμνημα:

tm_bubbles(
  size = "gdp_total",
  size.scale = tm_scale_continuous(
    values = tm_seq(power = "sqrt_perceptual"),
    values.scale = 1.15
  )
)

Η επιλογή sqrt_perceptual του tmap αντιστοιχεί ακριβέστερα σε εκθέτη 0.5716. Στη χειροκίνητη εκδοχή χρησιμοποιούμε 0.57, ώστε να αναπαράγουμε την προσέγγιση που εμφανίζεται στο QGIS.

Στη χειροκίνητη εκδοχή ο τύπος υπολογισμού δηλώνεται ρητά και ο εκθέτης μπορεί να προσαρμοστεί, αρκεί η επιλογή του να τεκμηριώνεται.

Έλεγχος μέγιστου μεγέθους (maximum symbol size) και επικαλύψεων (overlaps)

Πριν υπολογίσουμε τα τελικά μεγέθη, ορίζουμε ρητά το μέγιστο σύμβολο με την παράμετρο symbol_size_max. Η τιμή αυτή εκφράζεται στις τυπογραφικές μονάδες του tmap και αποτελεί τη βασική παράμετρο ελέγχου του μεγέθους: όσο μειώνεται, περιορίζονται οι επικαλύψεις, αλλά γίνονται λιγότερο ευδιάκριτες οι διαφορές μεταξύ μικρών τιμών.

Οι επικαλύψεις δεν εξαρτώνται μόνο από τις τιμές και τη θέση των συμβόλων. Επηρεάζονται επίσης από το τελικό πλάτος του χάρτη, την αναλογία πλευρών και τη θέση του υπομνήματος. Γι’ αυτό ο ακόλουθος έλεγχος είναι προσεγγιστικός: μετατρέπει τα μεγέθη των συμβόλων σε μονάδες του χάρτη για ένα δηλωμένο πλάτος πλαισίου και μετρά τα ζεύγη κύκλων που ενδέχεται να επικαλύπτονται. Χρησιμεύει για τη σύγκριση εναλλακτικών τιμών του size_max· ο τελικός οπτικός έλεγχος πρέπει να γίνεται πάντοτε στο πραγματικό μέγεθος εξαγωγής.

scale_symbol_size <- function(
  x,
  exponent,
  size_max = 3.2,
  reference_max = max(x, na.rm = TRUE)
) {
  if (!is.finite(reference_max) || reference_max <= 0) {
    stop("Το reference_max πρέπει να είναι θετικός πεπερασμένος αριθμός.")
  }

  if (!is.finite(size_max) || size_max <= 0) {
    stop("Το size_max πρέπει να είναι θετικός πεπερασμένος αριθμός.")
  }

  size_max * (pmax(x, 0) / reference_max)^exponent
}

estimate_symbol_overlaps <- function(
  points,
  size_values,
  map_bbox,
  panel_width_in = 8
) {
  if (length(size_values) != nrow(points)) {
    stop("Τα size_values πρέπει να αντιστοιχούν μία προς μία στα σημεία.")
  }

  if (!is.finite(panel_width_in) || panel_width_in <= 0) {
    stop("Το panel_width_in πρέπει να είναι θετικός πεπερασμένος αριθμός.")
  }

  bbox_values <- sf::st_bbox(map_bbox)
  map_width <- as.numeric(
    bbox_values["xmax"] - bbox_values["xmin"]
  )

  distance_matrix <- matrix(
    as.numeric(sf::st_distance(points)),
    nrow = nrow(points),
    ncol = nrow(points)
  )

  # Μία τυπογραφική γραμμή αντιστοιχεί περίπου σε 1/6 της ίντσας.
  symbol_diameter_map <-
    size_values / 6 / panel_width_in * map_width

  radius_sum <- outer(
    symbol_diameter_map / 2,
    symbol_diameter_map / 2,
    "+"
  )

  overlap_matrix <- distance_matrix < radius_sum
  diag(overlap_matrix) <- FALSE

  sum(overlap_matrix[upper.tri(overlap_matrix)])
}

symbol_size_candidates <- c(2.4, 2.8, 3.2)

overlap_check <- data.frame(
  size_max = symbol_size_candidates,
  estimated_overlap_pairs = vapply(
    symbol_size_candidates,
    function(candidate_size) {
      candidate_values <- scale_symbol_size(
        nuts2_points$gdp_total,
        exponent = 0.57,
        size_max = candidate_size,
        reference_max = gdp_total_max
      )

      estimate_symbol_overlaps(
        points = nuts2_points,
        size_values = candidate_values,
        map_bbox = eu_bbox,
        panel_width_in = 8
      )
    },
    numeric(1)
  )
)

overlap_check
  size_max estimated_overlap_pairs
1      2.4                      33
2      2.8                      51
3      3.2                      82

Στη σύγκριση που ακολουθεί το symbol_size_max δηλώνεται στην πρώτη γραμμή του αντίστοιχου τμήματος κώδικα. Η τιμή 2.8 είναι σημείο εκκίνησης και όχι γενικός κανόνας. Αν ο προηγούμενος πίνακας και η προεπισκόπηση δείξουν πολλές επικαλύψεις, δοκιμάζουμε μικρότερη τιμή. Σε χάρτη μεγαλύτερου πλάτους μπορούμε αντίστοιχα να την αυξήσουμε, διατηρώντας σταθερά τον εκθέτη και το reference_max.

Στη συνέχεια χρησιμοποιούμε tm_scale_asis(values.scale = 1). Έτσι το tmap λαμβάνει τα προϋπολογισμένα μεγέθη όπως είναι, χωρίς δεύτερη μετατροπή ή πρόσθετο πολλαπλασιαστή:

make_symbol_scaling_map <- function(
  points_data,
  size_variable,
  title_text
) {
  tm_shape(bg_non_eu, bbox = eu_bbox) +
    tm_polygons(
      fill = "grey95",
      col = "grey85",
      lwd = 0.2
    ) +
    tm_shape(nuts2_map, bbox = eu_bbox) +
    tm_polygons(
      fill = "grey92",
      col = "grey82",
      lwd = 0.15
    ) +
    tm_shape(nuts0_eu27, bbox = eu_bbox) +
    tm_borders(col = "grey30", lwd = 0.6) +
    tm_shape(points_data, bbox = eu_bbox) +
    tm_bubbles(
      size = size_variable,
      size.scale = tm_scale_asis(values.scale = 1),
      size.legend = tm_legend_hide(),
      fill = "#E69F00",
      fill_alpha = 0.55,
      col = "#7A4A00",
      lwd = 0.3
    ) +
    tm_layout(frame = TRUE) +
    tm_title(title_text, size = 0.75)
}
# Κύρια παράμετρος ελέγχου του μέγιστου συμβόλου:
# δοκιμάστε, για παράδειγμα, 2.4, 2.8 ή 3.2.
symbol_size_max <- 2.8

nuts2_points_comparison <- nuts2_points |>
  dplyr::mutate(
    size_radius = scale_symbol_size(
      gdp_total,
      exponent = 1,
      size_max = symbol_size_max,
      reference_max = gdp_total_max
    ),
    size_area = scale_symbol_size(
      gdp_total,
      exponent = 0.5,
      size_max = symbol_size_max,
      reference_max = gdp_total_max
    ),
    size_flannery = scale_symbol_size(
      gdp_total,
      exponent = 0.57,
      size_max = symbol_size_max,
      reference_max = gdp_total_max
    )
  )

map_radius <- make_symbol_scaling_map(
  nuts2_points_comparison,
  "size_radius",
  "Γραμμική ακτίνα/διάμετρος (p = 1)"
)

map_area <- make_symbol_scaling_map(
  nuts2_points_comparison,
  "size_area",
  "Αναλογική επιφάνεια (p = 0,50)"
)

map_flannery <- make_symbol_scaling_map(
  nuts2_points_comparison,
  "size_flannery",
  "Διόρθωση Flannery (p = 0,57)"
)

tmap::tmap_arrange(
  map_radius,
  map_area,
  map_flannery,
  ncol = 3
)

Η χειροκίνητη μέθοδος με p = 0.5 ακολουθεί την ίδια γεωμετρική αρχή με την αυτόματη tm_scale_continuous(values = tm_seq(power = "sqrt")). Η διαφορά βρίσκεται στον έλεγχο που έχει ο αναλυτής πάνω στο μέγιστο μέγεθος και στον εκθέτη. Η μέθοδος p = 1 υπερτονίζει τις μεγάλες τιμές, ενώ η Flannery αποτελεί αντιληπτική προσαρμογή.

Επειδή το tm_scale_asis() γνωρίζει μόνο τα προϋπολογισμένα μεγέθη και όχι τις αρχικές τιμές, προσθέτουμε χειροκίνητο υπόμνημα όταν θέλουμε να παρουσιάσουμε τελικό χάρτη. Το symbol_size_max δηλώνεται ξανά μέσα στο επόμενο chunk, ώστε η ίδια τιμή να χρησιμοποιείται τόσο στον χάρτη όσο και στο υπόμνημα:

# Η ίδια τιμή χρησιμοποιείται για τα σύμβολα του χάρτη και του υπομνήματος.
symbol_size_max <- 2.8

nuts2_points_manual <- nuts2_points |>
  dplyr::mutate(
    size_flannery = scale_symbol_size(
      gdp_total,
      exponent = 0.57,
      size_max = symbol_size_max,
      reference_max = gdp_total_max
    )
  )

legend_values <- pretty(
  c(0, gdp_total_max),
  n = 4
)

legend_values <- legend_values[
  legend_values > 0 &
    legend_values <= gdp_total_max
]

legend_sizes <- scale_symbol_size(
  legend_values,
  exponent = 0.57,
  size_max = symbol_size_max,
  reference_max = gdp_total_max
)

map_flannery_manual <-
  make_symbol_scaling_map(
    nuts2_points_manual,
    "size_flannery",
    "Διόρθωση Flannery (p = 0,57)"
  ) +
  tm_add_legend(
    labels = format(
      legend_values,
      big.mark = ".",
      decimal.mark = ",",
      scientific = FALSE,
      trim = TRUE
    ),
    size = legend_sizes,
    fill = rep("#E69F00", length(legend_values)),
    fill_alpha = rep(0.55, length(legend_values)),
    col = rep("#7A4A00", length(legend_values)),
    title = "Συνολικό ΑΕΠ\n(εκατ. ΜΑΔ)",
    position = tm_pos_out("right", "center")
  ) +
  tm_layout(legend.outside = TRUE)

map_flannery_manual

Ο τελικός χάρτης πρέπει να δηλώνει αν χρησιμοποιεί αυστηρή αναλογικότητα επιφάνειας (p = 0.5) ή αντιληπτική διόρθωση (p = 0.57). Ο εκθέτης δεν είναι μια αισθητική λεπτομέρεια· αποτελεί μέρος της χαρτογραφικής μεθόδου.

9. Πολυχρονικοί χάρτες (multitemporal maps): κοινές κλάσεις και κοινό χρωματικό σχήμα

Στους πολυχρονικούς χάρτες η συγκρισιμότητα απαιτεί:

Αν υπολογίσουμε ποσοστημόρια ή φυσικές τομές ξεχωριστά για κάθε έτος, το ίδιο χρώμα μπορεί να αντιστοιχεί σε διαφορετικό αριθμητικό εύρος. Έτσι, μια περιφέρεια μπορεί να αλλάξει χρώμα χωρίς ουσιαστική μεταβολή ή να διατηρήσει το ίδιο χρώμα παρότι η τιμή της μεταβλήθηκε σημαντικά.

Μια λύση είναι να εξετάσουμε από κοινού το εύρος όλων των ετών, να ορίσουμε στρογγυλοποιημένα και εύκολα ερμηνεύσιμα όρια και να τα διατηρήσουμε σταθερά σε κάθε χάρτη. Εδώ δεν χρησιμοποιούμε Fisher–Jenks. Η μέθοδος αυτή επιδιώκει μικρή διασπορά μέσα στις κλάσεις μιας συγκεκριμένης κατανομής, αλλά τα όρια εξαρτώνται από το συγκεκριμένο σύνολο τιμών και συχνά δεν είναι στρογγυλοποιημένα. Επομένως, δεν ενδείκνυται όταν προέχουν η διαχρονική συγκρισιμότητα και η εύκολη ανάγνωση του υπομνήματος.

years_facet <- 2019:2024

gdp_multi <- gdp_raw |>
  dplyr::filter(
    unit == "PPS_EU27_2020_HAB",
    observation_year %in% years_facet
  ) |>
  dplyr::transmute(
    geo,
    year = observation_year,
    gdp_pc_pps = values
  )

pooled_values <- gdp_multi$gdp_pc_pps |>
  as.numeric()

pooled_values <- pooled_values[
  is.finite(pooled_values)
]

if (
  length(pooled_values) < 2 ||
    length(unique(pooled_values)) < 2
) {
  stop(
    "Δεν υπάρχουν αρκετές έγκυρες και διαφορετικές τιμές για κοινά όρια."
  )
}

# Επαναχρησιμοποιούμε τα ίδια στρογγυλοποιημένα σταθερά όρια.
# Μόνο το ανώτερο όριο προσαρμόζεται στο σύνολο της περιόδου 2019–2024.
global_upper <- max(
  80000,
  (floor(max(pooled_values) / 10000) + 1) * 10000
)

global_breaks <- sort(unique(
  c(
    0,
    20000,
    30000,
    40000,
    60000,
    global_upper
  )
))

if (
  length(global_breaks) < 3 ||
    min(global_breaks) > min(pooled_values) ||
    max(global_breaks) < max(pooled_values)
) {
  stop("Τα κοινά όρια δεν καλύπτουν ολόκληρο το εύρος των τιμών.")
}

# Τα ίδια global_breaks χρησιμοποιούνται ως fixed breaks σε όλους τους χάρτες.
common_scale <- tm_scale_intervals(
  style = "fixed",
  breaks = global_breaks,
  values = "hcl.blues3",
  value.na = "grey80",
  label.na = "ΜΔ"
)

global_breaks
[1]      0  20000  30000  40000  60000 110000

Για να είναι το παράδειγμα ευανάγνωστο, εμφανίζουμε τρία έτη από την περίοδο 2019–2024. Τα όρια, όμως, έχουν οριστεί από το κοινό εύρος των παρατηρήσεων και των έξι ετών.

nuts2_base <- nuts2_map |>
  dplyr::select(NUTS_ID)

years_shown <- c(2019, 2022, 2024)

repeat_for_years <- function(x, years) {
  dplyr::bind_rows(lapply(
    years,
    function(current_year) {
      x |>
        dplyr::mutate(
          year = factor(
            current_year,
            levels = years
          )
        )
    }
  ))
}

time_map_data <- dplyr::bind_rows(lapply(
  years_shown,
  function(current_year) {
    year_values <- gdp_multi |>
      dplyr::filter(year == current_year) |>
      dplyr::select(geo, gdp_pc_pps)

    nuts2_base |>
      dplyr::left_join(
        year_values,
        by = c("NUTS_ID" = "geo")
      ) |>
      dplyr::mutate(
        year = factor(
          current_year,
          levels = years_shown
        )
      )
  }
))

bg_non_eu_time <- repeat_for_years(
  bg_non_eu,
  years_shown
)

nuts0_eu27_time <- repeat_for_years(
  nuts0_eu27,
  years_shown
)
map_time_comparison <-
  tm_shape(bg_non_eu_time, bbox = eu_bbox) +
  tm_polygons(
    fill = "grey95",
    col = "grey85",
    lwd = 0.2
  ) +
  tm_shape(time_map_data, bbox = eu_bbox) +
  tm_polygons(
    fill = "gdp_pc_pps",
    fill.scale = common_scale,
    fill.legend = tm_legend(
      title = "ΑΕΠ κατά κεφαλή (ΜΑΔ ανά κάτοικο)",
      orientation = "landscape",
      position = tm_pos_out("center", "bottom")
    )
  ) +
  tm_borders(col = "grey80", lwd = 0.2) +
  tm_shape(nuts0_eu27_time, bbox = eu_bbox) +
  tm_borders(col = "grey25", lwd = 0.6) +
  tm_facets_wrap(
    by = "year",
    nrow = 1,
    free.coords = FALSE
  ) +
  tm_layout(
    frame = TRUE,
    legend.outside = TRUE
  )

map_time_comparison

Επειδή τα τρία πλαίσια χρησιμοποιούν κοινά όρια και κοινά χρώματα, μια μεταβολή χρώματος αντιστοιχεί στην ίδια αριθμητική μετάβαση σε κάθε έτος. Αυτό δεν θα ίσχυε αν υπολογίζαμε νέα ποσοστημόρια ή νέες φυσικές τομές χωριστά για κάθε χάρτη. Η ενιαία διάταξη ως μικροί πολλαπλοί χάρτες (small multiples ή faceted map), σε συνδυασμό με το κοινό υπόμνημα (shared legend), διευκολύνει την άμεση σύγκριση των τριών ετών.

Αν υπάρχουν ουσιαστικά όρια αναφοράς, είναι συνήθως προτιμότερο να χρησιμοποιήσουμε αυτά ως σταθερά όρια για όλα τα έτη. Εδώ τα όρια 20.000, 30.000, 40.000 και 60.000 ΜΑΔ επιλέχθηκαν ενδεικτικά, επειδή είναι στρογγυλοποιημένα και διαβάζονται εύκολα. Δεν προέρχονται από θεσμική ταξινόμηση ούτε υποκαθιστούν τεκμηριωμένα θεωρητικά, εμπειρικά ή θεσμικά κατώφλια. Αφού οριστούν η περίοδος αναφοράς και τα όρια, τα global_breaks διατηρούνται σταθερά σε κάθε επικαιροποίηση της ίδιας σειράς χαρτών.

10. Οπτική ευκρίνεια, προσβασιμότητα (accessibility) και χαρτογραφική δεοντολογία (cartographic ethics)

Η οπτική ευκρίνεια αφορά το αν τα σύμβολα, τα χρώματα, οι επιγραφές και το υπόμνημα διακρίνονται καθαρά στο τελικό μέγεθος του χάρτη. Η προσβασιμότητα είναι ευρύτερη: εξετάζει αν η πληροφορία παραμένει κατανοητή από χρήστες με διαφορετική χρωματική αντίληψη και σε διαφορετικά μέσα παρουσίασης.

Έτσι, δεν αξιολογούμε ένα χρωματικό σχήμα μόνο αισθητικά. Ελέγχουμε αν:

Αποφεύγουμε τα χρωματικά σχήματα τύπου ουράνιου τόξου για διατεταγμένα ποσοτικά δεδομένα. Οι απότομες μεταβολές απόχρωσης μπορούν να δημιουργήσουν τεχνητά όρια, ενώ η φωτεινότητα δεν μεταβάλλεται πάντοτε με αντιληπτικά συνεπή τρόπο.

11. Πρακτικός οδηγός επιλογής

Αν η μεταβλητή… Προτιμούμε… Ελέγχουμε ιδιαίτερα…
είναι ονομαστική ποιοτικό χρωματικό σχήμα ισότιμη οπτική βαρύτητα
είναι διατακτική/διατεταγμένη διαδοχικό χρωματικό σχήμα σαφή, μονοτονική μεταβολή φωτεινότητας
είναι ποσοστό, λόγος ή πυκνότητα χωροπληθή χάρτη κατανομή και ακραίες τιμές
είναι συνολική/χωρικά αθροίσιμη ποσότητα αναλογικά σύμβολα σωστή κλιμάκωση επιφάνειας
έχει τιμή αναφοράς αποκλίνον χρωματικό σχήμα σωστό midpoint
συγκρίνεται μεταξύ ετών κοινά σταθερά όρια ίδια χρώματα και υπόμνημα
έχει έντονη ασυμμετρία Fisher–Jenks, head/tails ή μετασχηματισμό ερμηνευσιμότητα
χρησιμοποιείται για κατάταξη ποσοστημόρια ότι οι κλάσεις καλύπτουν διαστήματα διαφορετικού πλάτους
συνδέεται με θεσμικά κατώφλια σταθερά όρια τεκμηρίωση των ορίων

Μια σύντομη διαδικασία εργασίας είναι:

  1. ορίζουμε με ακρίβεια τη μεταβλητή και τη μονάδα μέτρησης,
  2. εξετάζουμε αν είναι αθροίσιμη ποσότητα ή κανονικοποιημένη τιμή,
  3. εξετάζουμε την κατανομή και τις ακραίες τιμές,
  4. εντοπίζουμε τυχόν ουσιαστικό σημείο αναφοράς,
  5. συγκρίνουμε τουλάχιστον δύο εύλογες ταξινομήσεις,
  6. επιλέγουμε χρωματικό σχήμα συμβατό με τη σημασία της μεταβλητής,
  7. ελέγχουμε την ευκρίνεια στο τελικό μέγεθος, την αναγνωσιμότητα του υπομνήματος και τη διακριτότητα των χρωμάτων, λαμβάνοντας υπόψη και τη δυσχρωματοψία,
  8. δηλώνουμε στον χάρτη τη μέθοδο, τις μονάδες και την πηγή.

Βασικό συμπέρασμα

Η ταξινόμηση και το χρώμα δεν είναι τελικές διακοσμητικές επιλογές. Αποτελούν μέρος της ανάλυσης: καθορίζουν ποιες διαφορές γίνονται ορατές, ποιες συγχωνεύονται και ποια χωρική αφήγηση προκύπτει από τα ίδια δεδομένα.

12. Ασκήσεις

  1. Αλλάξτε τον αριθμό κλάσεων από 5 σε 7. Σε ποια μέθοδο μεταβάλλεται περισσότερο η χωρική εικόνα;

  2. Συγκρίνετε τις μεθόδους "pretty", "equal", "quantile" και "fisher". Καταγράψτε τα όρια και τον αριθμό περιφερειών ανά κλάση.

  3. Αντιστρέψτε ένα διαδοχικό χρωματικό σχήμα. Ποια εκδοχή αποδίδει πιο διαισθητικά τις υψηλές τιμές και γιατί;

  4. Κατασκευάστε δύο πολυχρονικούς χάρτες:

    • έναν με νέα ποσοστημόρια για κάθε έτος,
    • έναν με κοινά σταθερά όρια.

    Συγκρίνετε πώς αλλάζει η εικόνα της περιφερειακής εξέλιξης.

  5. Επιλέξτε έναν δείκτη ανεργίας ή δημογραφικής γήρανσης από τη Eurostat. Τεκμηριώστε:

    • τον τύπο της μεταβλητής,
    • τη μέθοδο ταξινόμησης,
    • το χρωματικό σχήμα, και
    • τον χειρισμό των ελλιπών τιμών.

Το sessionInfo() καταγράφει την έκδοση της R, το λειτουργικό σύστημα και τις εκδόσεις των πακέτων που ήταν φορτωμένα κατά την παραγωγή του tutorial. Οι πληροφορίες ενημερώνονται αυτόματα σε κάθε νέο render.

sessionInfo()
R version 4.5.2 (2025-10-31 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=Greek_Greece.utf8  LC_CTYPE=Greek_Greece.utf8   
[3] LC_MONETARY=Greek_Greece.utf8 LC_NUMERIC=C                 
[5] LC_TIME=Greek_Greece.utf8    

time zone: Europe/Athens
tzcode source: internal

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

other attached packages:
[1] cols4all_0.10   classInt_0.4-11 tmap_4.4-1      dplyr_1.1.4    
[5] sf_1.1-1        giscoR_1.1.1    eurostat_4.0.0 

loaded via a namespace (and not attached):
 [1] tidyselect_1.2.1     fastmap_1.2.0        leaflegend_1.2.8    
 [4] leaflet_2.2.3        XML_3.99-0.23        digest_0.6.37       
 [7] timechange_0.4.0     lifecycle_1.0.5      terra_1.9-34        
[10] magrittr_2.0.3       compiler_4.5.2       rlang_1.1.6         
[13] tools_4.5.2          utf8_1.2.4           yaml_2.3.12         
[16] data.table_1.18.4    knitr_1.51           htmlwidgets_1.6.4   
[19] bit_4.6.0            sp_2.2-0             curl_7.1.0          
[22] here_1.0.2           plyr_1.8.9           xml2_1.6.0          
[25] abind_1.4-8          KernSmooth_2.23-26   withr_3.0.3         
[28] purrr_1.2.0          leafsync_0.1.0       grid_4.5.2          
[31] ISOweek_0.6-2        e1071_1.7-16         leafem_0.2.5        
[34] colorspace_2.1-2     spacesXYZ_1.6-0      cli_3.6.5           
[37] crayon_1.5.3         rmarkdown_2.31       generics_0.1.4      
[40] otel_0.2.0           rstudioapi_0.19.0    httr_1.4.8          
[43] tzdb_0.5.0           tmaptools_3.3        readxl_1.4.5        
[46] DBI_1.3.0            proxy_0.4-27         stringr_1.6.0       
[49] regions_0.1.8        stars_0.7-2          assertthat_0.2.1    
[52] parallel_4.5.2       s2_1.1.8             cellranger_1.1.0    
[55] base64enc_0.1-6      vctrs_0.6.5          jsonlite_2.0.0      
[58] hms_1.1.4            bit64_4.8.2          crosstalk_1.2.2     
[61] tidyr_1.3.1          maptiles_0.11.0      units_0.8-7         
[64] bibtex_0.5.2         glue_1.8.0           lwgeom_0.2-16       
[67] RefManageR_1.4.0     codetools_0.2-20     lubridate_1.9.5     
[70] stringi_1.8.7        countrycode_1.8.0    raster_3.6-32       
[73] tibble_3.2.1         logger_0.4.2         pillar_1.11.1       
[76] rappdirs_0.3.4       htmltools_0.5.8.1    R6_2.6.1            
[79] httr2_1.2.3          wk_0.9.4             microbenchmark_1.5.0
[82] rprojroot_2.1.1      vroom_1.7.1          evaluate_1.0.5      
[85] lattice_0.22-7       readr_2.1.5          png_0.1-9           
[88] backports_1.5.0      class_7.3-23         Rcpp_1.0.14         
[91] xfun_0.52            pkgconfig_2.0.3     

Ενδεικτική βιβλιογραφία και τεχνική τεκμηρίωση

Λοιπές αναφορές

Άδεια / Αναδημοσίευση

© 2026 Doukissas Leonidas, Pantazis Panagiotis, Psycharis Yannis, Politis Konstantinos.

Κείμενο: Διατίθεται με άδεια Creative Commons CC BY 4.0.
Μπορείτε να αναδιανείμετε και να προσαρμόσετε, με αναφορά στους δημιουργούς.
Άδεια: https://creativecommons.org/licenses/by/4.0/

Κώδικας R: MIT License (ελεύθερη χρήση, τροποποίηση, αναδιανομή με αναφορά).
Άδεια: https://opensource.org/licenses/MIT

Δεδομένα/Εικόνες τρίτων: Παραμένουν στις αρχικές τους άδειες.
Για χρήσεις πέραν των παραπάνω, επικοινωνήστε: ppantaz@uth.gr.

⬅ Επιστροφή στα Tutorials
Τελευταία ενημέρωση: 31/07/2026