Το dataset epi_r.csv περιλαμβάνει 20.052 συνταγές και
680 μεταβλητές. Κάθε γραμμή αντιστοιχεί σε μία συνταγή, ενώ οι πρώτες
βασικές στήλες περιλαμβάνουν πληροφορίες όπως τον τίτλο της συνταγής
(title), τη βαθμολογία της (rating) και
ορισμένα διατροφικά στοιχεία, όπως θερμίδες (calories),
πρωτεΐνη (protein), λιπαρά (fat) και νάτριο
(sodium). Το μεγαλύτερο μέρος του dataset όμως αποτελείται
από δυαδικές μεταβλητές/ετικέτες, οι οποίες παίρνουν τιμές 0 ή 1. Η τιμή
1 σημαίνει ότι η συγκεκριμένη ετικέτα ισχύει για τη συνταγή, ενώ η τιμή
0 σημαίνει ότι δεν ισχύει. Αυτές οι στήλες περιγράφουν χαρακτηριστικά
όπως το είδος γεύματος, π.χ. breakfast,
dinner, dessert, βασικά υλικά, π.χ.
chicken, beef, cheese, τρόπους
μαγειρέματος, π.χ. bake, grill/barbecue,
no-cook, καθώς και διατροφικές κατηγορίες, π.χ.
vegetarian, vegan, healthy. Στην
παρούσα εργασία η συσταδοποίηση βασίζεται σε αυτές τις δυαδικές
ετικέτες, με στόχο να εντοπιστούν ομάδες συνταγών που έχουν παρόμοια
χαρακτηριστικά.
# Φόρτωση του dataset
recipes <- read.csv("epi_r.csv")
# Εμφάνιση ενδεικτικών στηλών του dataset
recipes %>%
select(any_of(c(
"title", "rating",
"dessert", "breakfast", "dinner", "appetizer",
"vegetarian", "vegan", "healthy",
"chicken", "beef", "fish",
"bake", "roast"
))) %>%
head()## title rating dessert breakfast dinner
## 1 Lentil, Apple, and Turkey Wrap 2.500 0 0 0
## 2 Boudin Blanc Terrine with Red Onion Confit 4.375 0 0 0
## 3 Potato and Fennel Soup Hodge 3.750 0 0 0
## 4 Mahi-Mahi in Tomato Olive Sauce 5.000 0 0 1
## 5 Spinach Noodle Casserole 3.125 0 0 0
## 6 The Best Blts 4.375 0 0 0
## appetizer vegetarian vegan healthy chicken beef fish bake roast
## 1 0 0 0 0 0 0 0 0 0
## 2 0 0 0 0 0 0 0 1 0
## 3 0 0 0 0 0 0 0 0 0
## 4 0 0 0 1 0 0 1 0 0
## 5 0 1 0 0 0 0 0 1 0
## 6 0 0 0 0 0 0 0 0 0
## [1] 20052 680
## [1] "title" "rating" "calories"
## [4] "protein" "fat" "sodium"
## [7] "X.cakeweek" "X.wasteless" "X22.minute.meals"
## [10] "X3.ingredient.recipes" "X30.days.of.groceries" "advance.prep.required"
## [13] "alabama" "alaska" "alcoholic"
## [16] "almond" "amaretto" "anchovy"
## [19] "anise" "anniversary" "anthony.bourdain"
## [22] "aperitif" "appetizer" "apple"
## [25] "apple.juice" "apricot" "arizona"
## [28] "artichoke" "arugula" "asian.pear"
## [31] "asparagus" "aspen" "atlanta"
## [34] "australia" "avocado" "back.to.school"
## [37] "backyard.bbq" "bacon" "bake"
## [40] "banana"
Παραπάνω εμφανίζονται ενδεικτικά ορισμένες από τις στήλες του dataset που είναι σχετικές με τη συσταδοποίηση. Οι περισσότερες από αυτές είναι δυαδικές μεταβλητές/ετικέτες και δείχνουν αν μια συνταγή ανήκει σε κάποια κατηγορία, περιέχει κάποιο βασικό υλικό ή σχετίζεται με συγκεκριμένο τρόπο μαγειρέματος.
# Γενική δομή ενδεικτικών στηλών του dataset
recipes %>%
select(any_of(c(
"title", "rating",
"dessert", "breakfast", "dinner", "appetizer",
"vegetarian", "vegan", "healthy",
"chicken", "beef", "fish",
"cheese", "egg",
"bake", "roast"
))) %>%
str()## 'data.frame': 20052 obs. of 16 variables:
## $ title : chr "Lentil, Apple, and Turkey Wrap " "Boudin Blanc Terrine with Red Onion Confit " "Potato and Fennel Soup Hodge " "Mahi-Mahi in Tomato Olive Sauce " ...
## $ rating : num 2.5 4.38 3.75 5 3.12 ...
## $ dessert : num 0 0 0 0 0 0 0 0 0 0 ...
## $ breakfast : num 0 0 0 0 0 0 0 0 0 0 ...
## $ dinner : num 0 0 0 1 0 0 0 0 0 0 ...
## $ appetizer : num 0 0 0 0 0 0 0 0 0 0 ...
## $ vegetarian: num 0 0 0 0 1 0 0 1 0 0 ...
## $ vegan : num 0 0 0 0 0 0 0 0 0 0 ...
## $ healthy : num 0 0 0 1 0 0 1 0 0 0 ...
## $ chicken : num 0 0 0 0 0 0 0 0 0 0 ...
## $ beef : num 0 0 0 0 0 0 0 0 1 0 ...
## $ fish : num 0 0 0 1 0 0 0 0 0 0 ...
## $ cheese : num 0 0 0 0 1 0 0 0 0 0 ...
## $ egg : num 0 0 0 0 0 0 0 1 0 0 ...
## $ bake : num 0 1 0 0 1 0 0 0 0 0 ...
## $ roast : num 0 0 0 0 0 0 0 0 0 0 ...
Η εντολή str() χρησιμοποιήθηκε για να εξεταστεί η γενική
δομή επιλεγμένων στηλών του dataset. Παρατηρείται ότι το dataset
περιλαμβάνει μεταβλητές διαφορετικού τύπου, όπως ο τίτλος της συνταγής
που είναι κείμενο, η βαθμολογία που είναι αριθμητική μεταβλητή, και
αρκετές δυαδικές μεταβλητές/ετικέτες με τιμές 0 και 1. Οι δυαδικές αυτές
μεταβλητές είναι ιδιαίτερα χρήσιμες για τη συσταδοποίηση, καθώς
επιτρέπουν την ομαδοποίηση των συνταγών με βάση κοινά χαρακτηριστικά,
όπως κατηγορία γεύματος, βασικά υλικά και τρόπος μαγειρέματος.
Για τη συσταδοποίηση δεν χρησιμοποιούνται όλες οι μεταβλητές του dataset, καθώς πολλές ετικέτες είναι πολύ ειδικές ή εμφανίζονται σε λίγες συνταγές. Για τον λόγο αυτό επιλέγεται ένα μικρότερο σύνολο δυαδικών μεταβλητών που περιγράφουν βασικά χαρακτηριστικά των συνταγών, όπως είδος γεύματος, βασικά υλικά, τρόπος μαγειρέματος και διατροφική κατηγορία.
# Επιλογή βασικών tags για clustering
tag_names <- c(
"dessert", "breakfast", "lunch", "dinner", "appetizer", "snack",
"vegetarian", "vegan", "healthy", "low cal", "low fat", "low sodium",
"wheat/gluten-free", "dairy free", "quick & easy",
"chicken", "beef", "pork", "fish", "seafood",
"pasta", "rice", "potato", "tomato", "cheese", "egg",
"vegetable", "fruit",
"bake", "roast", "grill/barbecue", "no-cook", "soup/stew"
)
# Μετατροπή των ονομάτων ώστε να ταιριάζουν με τα ονόματα στηλών του R
tag_columns <- make.names(tag_names)
# Κρατάμε μόνο τα tags που υπάρχουν πραγματικά στο dataset
tag_columns <- tag_columns[tag_columns %in% names(recipes)]# Δημιουργία νέου dataset μόνο με τον τίτλο και τα επιλεγμένα tags
recipes_cluster <- recipes %>%
select(title, all_of(tag_columns)) %>%
unique()
# Διαστάσεις του νέου dataset
dim(recipes_cluster)## [1] 18091 34
Το νέο dataset περιλαμβάνει μόνο τον τίτλο κάθε συνταγής και τις επιλεγμένες δυαδικές μεταβλητές που θα χρησιμοποιηθούν στη συσταδοποίηση. Με αυτόν τον τρόπο η ανάλυση γίνεται πιο καθαρή και τα αποτελέσματα μπορούν να ερμηνευθούν πιο εύκολα.
# Αφαιρούμε συνταγές που δεν έχουν κανένα από τα επιλεγμένα tags
recipes_cluster <- recipes_cluster %>%
filter(rowSums(select(., -title)) > 0)
# Νέες διαστάσεις μετά τον καθαρισμό
dim(recipes_cluster)## [1] 16671 34
Αφαιρέθηκαν οι συνταγές που δεν είχαν καμία από τις επιλεγμένες ετικέτες, καθώς δεν μπορούν να ομαδοποιηθούν με βάση τα χαρακτηριστικά που επιλέχθηκαν για την ανάλυση.
Η ιεραρχική συσταδοποίηση εφαρμόζεται στις δυαδικές μεταβλητές/ετικέτες των συνταγών. Επειδή το dataset είναι μεγάλο, χρησιμοποιείται ένα τυχαίο δείγμα συνταγών, ώστε το δενδρόγραμμα να είναι πιο ευανάγνωστο και η ανάλυση πιο εύκολη στην παρουσίαση.
# Ορισμός seed για να είναι αναπαραγώγιμα τα αποτελέσματα
set.seed(123)
# Δημιουργία τυχαίου δείγματος 500 συνταγών
sample_size <- min(500, nrow(recipes_cluster))
sample_index <- sample(seq_len(nrow(recipes_cluster)), sample_size)
recipes_sample <- recipes_cluster[sample_index, ]
# Κρατάμε μόνο τις δυαδικές μεταβλητές για clustering
clustering_data <- recipes_sample %>%
select(-title)Χρησιμοποιήθηκε τυχαίο δείγμα 500 συνταγών, καθώς η ιεραρχική συσταδοποίηση υπολογίζει αποστάσεις μεταξύ όλων των παρατηρήσεων και μπορεί να γίνει δύσχρηστη όταν εφαρμόζεται σε πολύ μεγάλο αριθμό συνταγών.
# Υπολογισμός αποστάσεων μεταξύ των συνταγών
distances <- dist(clustering_data, method = "binary")
# Ιεραρχική συσταδοποίηση με τη μέθοδο Ward
cluster_recipes <- hclust(distances, method = "ward.D2")Επειδή οι μεταβλητές που χρησιμοποιούνται είναι δυαδικές, επιλέχθηκε
η μέθοδος απόστασης binary, η οποία είναι κατάλληλη για
δεδομένα με τιμές 0 και 1. Στη συνέχεια εφαρμόστηκε ιεραρχική
συσταδοποίηση με τη μέθοδο ward.D2, με στόχο τη δημιουργία
ομάδων συνταγών που έχουν παρόμοια χαρακτηριστικά.
# Δημιουργία δενδρογράμματος
plot(
cluster_recipes,
labels = FALSE,
main = "Δενδρόγραμμα Ιεραρχικής Συσταδοποίησης Συνταγών",
xlab = "",
sub = "",
ylab = "Απόσταση"
)Το δενδρόγραμμα παρουσιάζει τον τρόπο με τον οποίο οι συνταγές ομαδοποιούνται σταδιακά με βάση την ομοιότητα των ετικετών τους. Συνταγές που ενώνονται σε χαμηλότερα επίπεδα του δενδρογράμματος θεωρούνται πιο παρόμοιες μεταξύ τους.
# Επιλογή αριθμού συστάδων
cluster_groups <- cutree(cluster_recipes, k = 10)
# Αφαιρούμε τυχόν παλιό cluster αν υπάρχει
recipes_sample <- recipes_sample %>%
select(-any_of("cluster"))
# Προσθήκη του cluster στο δείγμα
recipes_sample$cluster <- as.factor(cluster_groups)
# Πλήθος συνταγών ανά cluster
table(recipes_sample$cluster)##
## 1 2 3 4 5 6 7 8 9 10
## 80 81 44 57 57 31 31 24 24 71
Το δενδρόγραμμα χωρίστηκε σε 10 συστάδες, ώστε να δημιουργηθούν πιο συγκεκριμένες ομάδες συνταγών. Η επιλογή των 10 clusters θεωρείται κατάλληλη για το συγκεκριμένο dataset, καθώς οι συνταγές περιγράφονται από αρκετές διαφορετικές ετικέτες, όπως είδος γεύματος, βασικά υλικά, τρόπος μαγειρέματος και διατροφική κατηγορία. Με αυτόν τον τρόπο μπορούν να προκύψουν πιο χρήσιμες ομάδες, όπως γλυκά, πρωινά, γρήγορες συνταγές, vegetarian/vegan συνταγές ή συνταγές με συγκεκριμένα βασικά υλικά.
plot(
cluster_recipes,
labels = FALSE,
main = "Δενδρόγραμμα με 10 Clusters",
xlab = "",
sub = "",
ylab = "Απόσταση"
)
rect.hclust(cluster_recipes, k = 10, border = 2:11)Το παραπάνω δενδρόγραμμα δείχνει τον διαχωρισμό των συνταγών σε 10 ομάδες. Τα ορθογώνια δείχνουν οπτικά τα clusters που δημιουργούνται μετά την κοπή του δενδρογράμματος.
# Υπολογισμός μέσης τιμής κάθε tag ανά cluster
cluster_summary <- recipes_sample %>%
group_by(cluster) %>%
summarise(across(-title, mean), .groups = "drop")
cluster_summary## # A tibble: 10 × 34
## cluster dessert breakfast lunch dinner appetizer snack vegetarian vegan
## <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 0.025 0.0125 0.05 0.0125 0 0 0.125 0
## 2 2 0 0 0.160 0.358 0.0370 0 0.0123 0
## 3 3 0.205 0.0682 0.0682 0.0682 0.0455 0.0227 1 0.955
## 4 4 0 0.0175 0.246 0.596 0.386 0 0.0526 0.0351
## 5 5 0.404 0.123 0.0526 0.0351 0.0175 0 0.825 0.0175
## 6 6 0 0 0 0.0323 0.194 0 0.548 0.0645
## 7 7 0.903 0.0323 0 0 0 0 0.290 0
## 8 8 0 0 0 0 0 0 0 0
## 9 9 0 0 0 0.125 0.25 0 0.0417 0
## 10 10 0.239 0.0563 0.0282 0.0282 0.113 0.0141 0.761 0.0423
## # ℹ 25 more variables: healthy <dbl>, low.cal <dbl>, low.fat <dbl>,
## # low.sodium <dbl>, wheat.gluten.free <dbl>, dairy.free <dbl>,
## # quick...easy <dbl>, chicken <dbl>, beef <dbl>, pork <dbl>, fish <dbl>,
## # seafood <dbl>, pasta <dbl>, rice <dbl>, potato <dbl>, tomato <dbl>,
## # cheese <dbl>, egg <dbl>, vegetable <dbl>, fruit <dbl>, bake <dbl>,
## # roast <dbl>, grill.barbecue <dbl>, no.cook <dbl>, soup.stew <dbl>
Ο παραπάνω πίνακας παρουσιάζει τη μέση τιμή κάθε δυαδικής μεταβλητής μέσα σε κάθε cluster. Επειδή οι μεταβλητές παίρνουν τιμές 0 και 1, η μέση τιμή μπορεί να ερμηνευθεί ως ποσοστό εμφάνισης του συγκεκριμένου χαρακτηριστικού μέσα στο cluster.
# Εύρεση των 5 πιο χαρακτηριστικών tags για κάθε cluster
top_tags_per_cluster <- cluster_summary %>%
pivot_longer(
cols = -cluster,
names_to = "Tag",
values_to = "Mean"
) %>%
group_by(cluster) %>%
slice_max(order_by = Mean, n = 5, with_ties = FALSE) %>%
mutate(Percentage = round(Mean * 100, 1)) %>%
select(cluster, Tag, Percentage) %>%
arrange(cluster, desc(Percentage))
print(top_tags_per_cluster, n = 50)## # A tibble: 50 × 3
## # Groups: cluster [10]
## cluster Tag Percentage
## <fct> <chr> <dbl>
## 1 1 vegetable 32.5
## 2 1 healthy 30
## 3 1 soup.stew 22.5
## 4 1 low.fat 20
## 5 1 beef 20
## 6 2 dinner 35.8
## 7 2 chicken 28.4
## 8 2 quick...easy 27.2
## 9 2 cheese 22.2
## 10 2 bake 21
## 11 3 vegetarian 100
## 12 3 vegan 95.5
## 13 3 wheat.gluten.free 86.4
## 14 3 dairy.free 86.4
## 15 3 quick...easy 40.9
## 16 4 wheat.gluten.free 82.5
## 17 4 dinner 59.6
## 18 4 dairy.free 52.6
## 19 4 quick...easy 40.4
## 20 4 appetizer 38.6
## 21 5 bake 91.2
## 22 5 vegetarian 82.5
## 23 5 dessert 40.4
## 24 5 egg 28.1
## 25 5 potato 24.6
## 26 6 tomato 96.8
## 27 6 vegetarian 54.8
## 28 6 quick...easy 35.5
## 29 6 pasta 32.3
## 30 6 bake 29
## 31 7 bake 96.8
## 32 7 dessert 90.3
## 33 7 fruit 67.7
## 34 7 vegetarian 29
## 35 7 egg 12.9
## 36 8 quick...easy 100
## 37 8 no.cook 16.7
## 38 8 grill.barbecue 12.5
## 39 8 healthy 8.3
## 40 8 fish 8.3
## 41 9 bake 100
## 42 9 appetizer 25
## 43 9 dinner 12.5
## 44 9 pork 12.5
## 45 9 egg 12.5
## 46 10 vegetarian 76.1
## 47 10 wheat.gluten.free 40.8
## 48 10 quick...easy 36.6
## 49 10 dessert 23.9
## 50 10 no.cook 22.5
Με βάση τα 5 πιο χαρακτηριστικά tags κάθε cluster, οι συστάδες μπορούν να ερμηνευθούν ως εξής:
| Cluster | Κυρίαρχα χαρακτηριστικά | Πιθανή ερμηνεία |
|---|---|---|
| Cluster 1 | vegetable, healthy, soup/stew, low fat, beef | Συνταγές με πιο υγιεινό χαρακτήρα, λαχανικά και ορισμένα πιάτα τύπου soup/stew. Το cluster είναι σχετικά μεικτό, καθώς τα ποσοστά δεν είναι πολύ υψηλά. |
| Cluster 2 | dinner, chicken, quick & easy, cheese, bake | Γρήγορες συνταγές για κυρίως γεύμα, συχνά με κοτόπουλο, τυρί και ψήσιμο. |
| Cluster 3 | vegetarian, vegan, wheat/gluten-free, dairy-free, quick & easy | Φυτικές και ειδικές διατροφικές συνταγές, κυρίως vegetarian/vegan, χωρίς γλουτένη και χωρίς γαλακτοκομικά. |
| Cluster 4 | wheat/gluten-free, dinner, dairy-free, quick & easy, appetizer | Συνταγές χωρίς γλουτένη και γαλακτοκομικά, κυρίως για dinner ή appetizer. |
| Cluster 5 | bake, vegetarian, dessert, egg, potato | Ψητές vegetarian συνταγές, με αρκετές γλυκές ή baked επιλογές. |
| Cluster 6 | tomato, vegetarian, quick & easy, pasta, bake | Συνταγές με βάση την ντομάτα, συχνά vegetarian και σχετικά γρήγορες, με παρουσία pasta ή ψησίματος. |
| Cluster 7 | bake, dessert, fruit, vegetarian, egg | Ψητά γλυκά και fruit-based desserts, όπως cakes, pies ή fruit desserts. |
| Cluster 8 | quick & easy, no-cook, grill/barbecue, healthy, fish | Πολύ γρήγορες και εύκολες συνταγές, ορισμένες χωρίς μαγείρεμα ή με grill/barbecue. |
| Cluster 9 | bake, appetizer, dinner, pork, egg | Ψητές αλμυρές συνταγές, πιθανώς appetizers ή μικρά γεύματα με υλικά όπως pork και egg. |
| Cluster 10 | vegetarian, wheat/gluten-free, quick & easy, dessert, no-cook | Vegetarian και gluten-free συνταγές, αρκετές από τις οποίες είναι γρήγορες, no-cook ή ελαφριές γλυκές επιλογές. |
Η ερμηνεία των clusters βασίζεται στα tags με τα μεγαλύτερα ποσοστά
εμφάνισης μέσα σε κάθε ομάδα. Για παράδειγμα, το Cluster 3
χαρακτηρίζεται έντονα από τα tags vegetarian,
vegan, wheat.gluten.free και
dairy.free, επομένως μπορεί να θεωρηθεί ως ομάδα φυτικών
και ειδικών διατροφικών συνταγών. Αντίστοιχα, το Cluster 7 έχει πολύ
υψηλά ποσοστά στα tags bake, dessert και
fruit, άρα ερμηνεύεται ως ομάδα ψητών γλυκών ή γλυκών με
φρούτα.
# Ενδεικτικές συνταγές από κάθε cluster
recipe_examples <- recipes_sample %>%
group_by(cluster) %>%
summarise(
Example_Recipes = paste(head(title, 5), collapse = "; "),
.groups = "drop"
)
recipe_examples## # A tibble: 10 × 2
## cluster Example_Recipes
## <fct> <chr>
## 1 1 "Roast Beef, Basil, and Spicy Tomato Chutney Lavash Sandwiches ; Soy…
## 2 2 "Goat Cheese Mashed Potatoes ; Mushroom Croque-Monsieur ; Bacon Swis…
## 3 3 "Broiled Eggplant with Cilantro Vinaigrette ; The World of Rice Sala…
## 4 4 "Broccolini with Italian Herb Oil ; Quinoa, Garbanzo, and Spinach Sa…
## 5 5 "Cheddar Cornmeal Crackers ; Broccoli-Parmesan Gratin ; Vanilla Ice …
## 6 6 "Linguine with Uncooked Tomato, Arugula, and Olive Sauce ; Fusilli A…
## 7 7 "Louise's Fruitcake ; Chocolate Plum and Walnut Torte ; Ginger-Orang…
## 8 8 "Harissa Sauce ; Hudson Bay ; Grilled Veal Chops with Warm Tomato-Ol…
## 9 9 "Blue Cheese and Pecan Crackers ; Cauliflower and Shrimp Caldin ; Bl…
## 10 10 "Lime and Lemon Posset ; Carrot and Bell Pepper Slaw ; Scallion Rice…
Ο παραπάνω πίνακας παρουσιάζει ενδεικτικές συνταγές από κάθε cluster. Τα παραδείγματα αυτά βοηθούν στην καλύτερη κατανόηση του περιεχομένου κάθε ομάδας και επιβεβαιώνουν αν τα clusters έχουν πρακτική ερμηνεία.
Η ιεραρχική συσταδοποίηση ομαδοποίησε τις συνταγές με βάση κοινά χαρακτηριστικά που προκύπτουν από τις δυαδικές ετικέτες του dataset. Η επιλογή των 10 clusters βοήθησε στη δημιουργία πιο αναλυτικών και πρακτικά χρήσιμων κατηγοριών συνταγών.
Τα αποτελέσματα δείχνουν ότι ορισμένα clusters έχουν πολύ ξεκάθαρη ερμηνεία. Για παράδειγμα, το Cluster 3 περιλαμβάνει κυρίως vegetarian, vegan, gluten-free και dairy-free συνταγές, ενώ το Cluster 7 περιλαμβάνει κυρίως ψητά γλυκά με φρούτα. Άλλα clusters, όπως το Cluster 1, είναι πιο μεικτά, επειδή τα βασικά tags εμφανίζονται με χαμηλότερα ποσοστά.
Συνολικά, η ανάλυση δείχνει ότι οι δυαδικές ετικέτες του dataset είναι κατάλληλες για clustering, καθώς επιτρέπουν τη δημιουργία ομάδων συνταγών με βάση στοιχεία όπως είδος γεύματος, βασικά υλικά, τρόπος μαγειρέματος και διατροφική κατηγορία. Τα αποτελέσματα θα μπορούσαν να χρησιμοποιηθούν για καλύτερη οργάνωση συνταγών ή για ένα απλό σύστημα προτάσεων παρόμοιων συνταγών.