%%html
<style>
table.dataframe {
font-size: 10px; /* Adjust font size as necessary */
overflow-x: auto; /* Adds horizontal scroll to the table */
width: auto; /* Adjust width settings */
max-width: 100%; /* Ensures the table does not exceed cell width */
display: block;
}
/* Container width settings for responsiveness */
@media only screen and (max-width: 768px) {
.container {
width: 100% !important;
}
}
@media only screen and (min-width: 769px) {
.container {
width: 80% !important;
}
}
.container {
max-width: 1920px !important;
margin: auto !important; /* This centers the container */
}
/* Ensuring images are responsive and do not exceed the container's width */
img {
max-width: 100%;
height: auto;
}
</style>
%load_ext pretty_jupyter
from plotly.offline import plot
import pandas as pd
from IPython.display import display, HTML
import plotly.express as px
import plotly.graph_objects as go
import plotly.io
import matplotlib.pyplot as plt
from pretty_jupyter.helpers import matplotlib_fig_to_html
import warnings
warnings.filterwarnings('ignore', category=FutureWarning)
Saying I'm a fan of George R.R. Martin's Fictional World of Ice & Fire is a bit of an understatement. From Fire & Blood and the A Song of Ice and Fire series, to his companion books The World of Ice & Fire and The Rise of the Dragon, I've read it all. So, you can imagine my excitement at the announcement of House of The Dragon, a Game of Thrones prequel to make up for that season 8 dumpster fire. I re-read all of Fire & Blood just to prepare for the series premiere, and while both the book and the show did not disappoint, I had even more questions.
We're given bits and pieces about the dragons, like general age, size, and some of their coloring but doesn't give much else. How are the dragons connected to each other? With the end of season 2, and more dragons having been introduced, I've decided that I have no other choice but to get my books out again and create a project that focuses on the dragons and their relationships.
For quick reference or just a general overview of the dragons themselves, below are all the dragons featured in the dataset. Clicking on a specific dragon will display a pop-up with a short description.
Background and Origins¶
The Valyrians discovered dragons in a ring of volcanoes known as the Fourteen Flames. They mastered the art of raising and riding dragons, which helped them build the powerful Valyrian Freehold Empire. The Fourteen flames erupted, wiping out most of the dragons and dragonlords in Valyria in what became known as the Doom of Valyria. However, House Targaryen survived, having left for Dragonstone a few years prior with their dragons—Balerion, Vhagar, and Meraxes.
The Dance of the Dragons¶
The Dance of the Dragons was a civil war within the Targaryen family that began after King Viserys I died. A power struggle broke out over who should sit on the Iron Throne. His daughter, Rhaenyra, had been named his heir, but her younger half-brother, Aegon II, usurped the throne. The resulting conflict led to the death of many Targaryens and their dragons. The war devastated the realm, weakened the Targaryen's claim to the Iron Throne, and led to the eventual decline of dragons in Westeros.
At the time of the civil war, there were twenty dragons. The largest were Vhagar, Cannibal, Vermithor, Silverwing, Dreamfyre, Meleys, Caraxes, Syrax, and Seasmoke. Others like Sunfyre, Vermax, Tessarion, and Moondancer weren't as large but still battle-ready. The wild dragons, Grey Ghost, Sheepstealer, and the Cannibal lived on Dragonstone but had no riders.
Reproduction and Gender¶
There’s an ongoing debate about how dragons reproduce. Some believe dragons can change sex, while others argue that their gender is fixed. For this project, we assume dragons require mates to lay eggs, based on direct quotes from George R. R. Martin himself. As he wrote in The Rise of The Dragon:
'Over time, Princess Rhaenyra became close friends with her good-sister, Lady Laena, and spent more and more time with both Laena and Prince Daemon, flying to and fro between Driftmark and Dragonstone. During this period, Rhaenyra's dragon Syrax laid several clutches of eggs, doubtless the result of matings with Caraxes.'
-The Rise of the Dragon, p. 193
Fire & Blood is a partial history of the Targaryens—a family of dragonriders that ruled for three centuries over all of Westeros. House of the Dragon is an HBO series that focuses on a very short but very significant time period during their reign, between 129-131 AC, known as The Dance of The Dragons. The Dance was a civil war between the Targaryens and was the beginning of the end for the dragons in Westeros and 300 years of Targaryen rule. My goal with this project is to give audiences of the show who might not have read Fire & Blood more insight into the dragons themselves.
Project Overview¶
If there were no dragons in Westeros before Aegon the Conqueror, then all the dragons alive at the time of The Dance must be related.
We know some dragons are She-Dragons based on their ability to lay eggs. Can we use their known traits to determine paternity or predict the characteristics of younger dragons without canon descriptions?
This project examines the lineages of the dragons involved in The Dance of The Dragons in George R. R. Martin's Fire & Blood, as featured on HBO's House of the Dragon. The goal is to map out the familial connections, create a dragon family tree, and predict the characteristics of dragons without canon descriptions.
Methodology¶
- Data Loading and Preparation:
- Load the dataset and clean it to handle missing or inconsistent values.
- Exploratory Dragon Analysis (EDA):
- Explore the data.
- Validate or correct all maternal relationships.
- The Genetics of Dragons:
- Determine recessive and dominant hereditary traits.
- The Paternity of Dragons:
- Use trait dominance information and machine learning, in combination with canon material, to assign dragon paternity.
- The Prediction of Dragons:
- Use lineages and dominance information to predict the physical appearances of all dragons without descriptions in the books.
- Generate accurate and original images of the dragons using the predicted physical appearances.
- The Tree of the Dragon:
- Create a final dragon family tree.
Cleaning and Preparation¶
Taking an initial look at the dataset to see any cleaning and reorganizing that might need to be done.
Setup¶
# Data Exploration and Machine Learning Libraries:
import pandas as pd
import numpy as np
import ast
import re
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import LabelEncoder
from collections import Counter
import colorsys
from keras.models import Sequential
from keras.layers import Dense, Input
# Visualization Libraries
import matplotlib.pyplot as plt
import seaborn as sns
import networkx as nx
from networkx.drawing.nx_agraph import graphviz_layout
import matplotlib.lines as mlines
from matplotlib import patches
from matplotlib.patches import Rectangle
Load the Dataset¶
# Load the dataset
dragon_data = pd.read_csv('dragon_data.csv')
# Data Exploration and Machine Learning Libraries:
import pandas as pd
import numpy as np
import ast
import re
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import LabelEncoder
from collections import Counter
import colorsys
from keras.models import Sequential
from keras.layers import Dense, Input
# Visualization Libraries
import matplotlib.pyplot as plt
import seaborn as sns
import networkx as nx
from networkx.drawing.nx_agraph import graphviz_layout
import matplotlib.lines as mlines
from matplotlib.patches import Rectangle
# Defining the color scheme
# Reds color palette
red_palette = ['#001514', '#1B1110', '#290F0E', '#360D0C', '#440B0A', '#4B0A09', '#510908', '#5E0706', '#650605', '#6B0504']
sns.set_palette(sns.color_palette(red_palette))
# Green color palette
green_palette = ['#031911', '#042117', '#05291C', '#063122', '#073928', '#09422D', '#0A4A33', '#0B5239', '#0C5A3E', '#0D6244']
sns.set_palette(sns.color_palette(green_palette))
# General color palette
general_palette = ['#092217', '#1B1110', '#063122', '#360D0C', '#001514', '#09422D', '#2D483D', '#510908', '#650605']
sns.set_palette(sns.color_palette(general_palette))
# Smaller color palette
small_palette = ['#28251D', '#1B1110', '#001514', '#2D483D', '#510908']
sns.set_palette(sns.color_palette(small_palette))
# Blacks vs. Green palette
allegiance_palette = ['#28251D', '#092818']
# Set the maximum width of each column
pd.set_option('display.max_colwidth', 30)
# Load the dataset
dragon_data = pd.read_csv('dragon_data.csv')
Dataset Information¶
First Few Rows¶
dragon_data.head(5)
Name | Gender | BirthDate | DeathDate | HatchedFrom | FirstRider | LastRider | Riders | NotableEvents | Size | ... | WingMembraneColor | EyeColor | PersonalityTraits | FoughtInTheDance | DanceAllegiance | SurvivedTheDance | BattlesDuringTheDance | Outcome | SurvivedTheBattle | KilledBy | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Arrax | Male | 115 AC | 129 AC | Syrax | Prince Lucerys Velaryon | Prince Lucerys Velaryon | ['Prince Lucerys Velaryon'] | ['Fight Above Shipbreaker ... | Small | ... | Gold | Gold | ['Young', 'Strong', 'Agile... | Yes | Black | No | Fight Above Shipbreaker Bay | Defeat | No | Vhagar |
1 | Balerion | Male | 114 BC | 94 AC | Unknown | King Aegon I Targaryen | King Viserys I Targaryen | ['King Aegon I Targaryen',... | ['Conquest of Westeros', '... | Huge | ... | Black | Red | ['Powerful', 'Willful', 'F... | No | NaN | NaN | NaN | NaN | NaN | NaN |
2 | Caraxes | Male | 50 AC | 130 AC | Dreamfyre | Prince Aemon Targaryen | Prince Daemon Targaryen | ['Prince Aemon Targaryen',... | ['Battle of the Stepstones... | Huge | ... | Red | Yellow | ['Formidable', 'Fearsome',... | Yes | Black | No | Assault on Harrenhal, Batt... | Victory, Victory, Victory,... | Yes, Yes, Yes, No | N/A, N/A, N/A, Vhagar, Sun... |
3 | Dreamfyre | Female | 32 AC | 130 AC | Meraxes | Queen Rhaena Targaryen | Queen Helaena Targaryen | ['Queen Rhaena Targaryen',... | ['Storming of the Dragonpit'] | Large | ... | Silver | Pale Blue | ['Loyal', 'Formidable', 'P... | Yes | Green | No | Storming of the Dragonpit | Defeat | No | Humans |
4 | Grey Ghost | Male | 90 AC | 130 AC | Silverwing | NaN | NaN | [] | ['Fight Above Dragonmount'] | Medium | ... | Pale Grey-White | Bright Green | ['Wild', ' Aggressive', 'S... | Yes | '' | No | Fall of Dragonstone | Defeat | No | Sunfyre |
5 rows × 21 columns
Column Descriptions¶
Column | Description |
---|---|
Name | Name of the dragon |
Gender | Gender of the dragon |
Birthdate | Birthdate of the dragon (BC = Before Conquest, AC = After Conquest) |
Deathdate | Deathdate of the dragon |
HatchedFrom | Source dragon egg |
FirstRider | First bonded Targaryen dragonrider |
LastRider | Last bonded Targaryen dragonrider |
Riders | List of all known, bonded, Targaryen dragonriders |
NotableEvents | Key events involving the dragon |
Size | Size of the dragon (Small, Medium, Large, Huge) |
ScaleColor | Color of the dragon's scales |
WingMembraneColor | Color of the dragon's wing membranes, accents, or crests |
EyeColor | Color of the dragon's eyes |
PersonalityTraits | Traits and characteristics of the dragon |
FoughtInTheDance | Whether the dragon fought in the Dance of the Dragons |
DanceAllegiance | Allegiance of the dragonrider during the Dance of the Dragons |
SurvivedTheDance | Whether the dragon survived the Dance of the Dragons |
BattlesDuringTheDance | Specific battles the dragon participated in during the Dance |
Outcome | Outcome of the dragon's participation in battles |
SurvivedTheBattle | Whether the dragon survived each battle |
KilledBy | Cause of dragon's death |
Structure¶
dragon_data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 24 entries, 0 to 23 Data columns (total 21 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Name 24 non-null object 1 Gender 24 non-null object 2 BirthDate 24 non-null object 3 DeathDate 24 non-null object 4 HatchedFrom 24 non-null object 5 FirstRider 22 non-null object 6 LastRider 22 non-null object 7 Riders 23 non-null object 8 NotableEvents 23 non-null object 9 Size 24 non-null object 10 ScaleColor 24 non-null object 11 WingMembraneColor 24 non-null object 12 EyeColor 24 non-null object 13 PersonalityTraits 24 non-null object 14 FoughtInTheDance 24 non-null object 15 DanceAllegiance 20 non-null object 16 SurvivedTheDance 20 non-null object 17 BattlesDuringTheDance 20 non-null object 18 Outcome 20 non-null object 19 SurvivedTheBattle 20 non-null object 20 KilledBy 19 non-null object dtypes: object(21) memory usage: 4.1+ KB
Missing Values¶
missing_values = dragon_data.isnull().sum()
missing_values
Name 0 Gender 0 BirthDate 0 DeathDate 0 HatchedFrom 0 FirstRider 2 LastRider 2 Riders 1 NotableEvents 1 Size 0 ScaleColor 0 WingMembraneColor 0 EyeColor 0 PersonalityTraits 0 FoughtInTheDance 0 DanceAllegiance 4 SurvivedTheDance 4 BattlesDuringTheDance 4 Outcome 4 SurvivedTheBattle 4 KilledBy 5 dtype: int64
# Handling Missing Values
dragon_data['FirstRider'].fillna('Unknown', inplace=True)
dragon_data['Riders'].fillna('Unknown', inplace=True)
dragon_data['NotableEvents'].fillna('Unknown', inplace=True)
dragon_data['LastRider'].fillna('Unknown', inplace=True)
dragon_data['DanceAllegiance'].fillna('Unknown', inplace=True)
dragon_data['SurvivedTheDance'].fillna('Unknown', inplace=True)
dragon_data['BattlesDuringTheDance'].fillna('Unknown', inplace=True)
dragon_data['Outcome'].fillna('Unknown', inplace=True)
dragon_data['SurvivedTheBattle'].fillna('Unknown', inplace=True)
dragon_data['KilledBy'].fillna('Unknown', inplace=True)
dragon_data['DeathDate'].fillna('Unknown', inplace=True)
# Focus on relevant columns
relevant_columns = ['Name', 'HatchedFrom', 'Gender', 'BirthDate', 'DeathDate', 'Size', 'ScaleColor', 'WingMembraneColor', 'EyeColor', 'PersonalityTraits', 'FoughtInTheDance', 'DanceAllegiance', 'SurvivedTheDance', 'BattlesDuringTheDance', 'Outcome', 'SurvivedTheBattle', 'KilledBy']
cleaned_dragon_data = dragon_data[relevant_columns]
cleaned_dragon_data.to_csv('cleaned_dragon_data.csv', index=False)
# Saving cleaned data
dragon_data = pd.read_csv('cleaned_dragon_data.csv')
# Extract unique values for 'ScaleColor', 'WingMembraneColor', and 'EyeColor'
scale_colors = dragon_data['ScaleColor'].unique()
wing_colors = dragon_data['WingMembraneColor'].unique()
eye_colors = dragon_data['EyeColor'].unique()
# Data conversion to lists
dragon_data['PersonalityTraits'] = dragon_data['PersonalityTraits'].apply(lambda x: x.strip("[]").replace("'", "").split(', ') if isinstance(x, str) else [])
# Flatten the list of lists to extract unique values properly
flattened_traits = [trait for sublist in dragon_data['PersonalityTraits'] for trait in sublist]
# Sorting unique values
traits_list = sorted(set(flattened_traits))
scale_colors_list = sorted(scale_colors)
wing_colors_list = sorted(wing_colors)
eye_colors_list = sorted(eye_colors)
# Define scale colors
defined_scale_colors = {
'Black': 'Black',
'Bronze': 'Bronze',
'Brown': 'Bronze',
'Charcoal': 'Black',
'Dark Cobalt Blue': 'Blue',
'Dark Grey': 'Black',
'Dark Grey-Blue': 'Blue',
'Gold': 'Gold',
'Jade Green': 'Green',
'Olive Green': 'Green',
'Pale Blue': 'Blue',
'Pale Green': 'Green',
'Pale Grey-White': 'White',
'Pale Pink': 'Pink',
'Pale Silver-White': 'White',
'Pale Silver-Grey': 'Silver',
'Pale White': 'White',
'Pearl-White': 'White',
'Red': 'Red',
'Scarlet Red': 'Red',
'Silver': 'Silver',
'Yellow': 'Yellow',
}
defined_wing_colors = {
'Black': 'Black',
'Brown': 'Bronze',
'Copper': 'Copper',
'Dark Grey': 'Grey',
'Gold': 'Gold',
'Green-Blue': 'Green',
'Pale Grey': 'Grey',
'Pale Grey-White': 'White',
'Pale Orange': 'Yellow',
'Pale Pink': 'Pink',
'Pearl-White': 'White',
'Red': 'Red',
'Silver': 'Silver',
'Tan': 'Bronze',
'White': 'White',
'Yellow': 'Yellow'
}
defined_eye_colors = {
'Bright Blue': 'Blue',
'Bright Green': 'Green',
'Copper': 'Bronze',
'Dark Green': 'Green',
'Gold': 'Gold',
'Green': 'Green',
'Pale Blue': 'Blue',
'Pale Green': 'Green',
'Red': 'Red',
'Yellow': 'Yellow'
}
defined_traits = {
' Aggressive': 'Aggressive',
'Ferocious': 'Aggressive',
'Fierce': 'Aggressive',
'Vicious': 'Aggressive',
'Gentle': 'Friendly',
'Powerful': 'Formidable',
'Strong': 'Formidable',
'beautiful': 'Beautiful',
'Brave': 'Willful',
'Agile': 'Swift',
'Protective': 'Loyal',
'formidable': 'Formidable',
}
# Function to map colors and traits
def standardize_scale_color(color):
return defined_scale_colors.get(color, color)
def standardize_wing_color(color):
return defined_wing_colors.get(color, color)
def standardize_eye_color(color):
return defined_eye_colors.get(color, color)
def standardize_traits(traits):
return [defined_traits.get(trait.strip(), trait.strip()) for trait in traits]
# Function to remove duplicates
def remove_duplicates(traits):
return list(set(traits))
# Create new columns for standardized colors
dragon_data['StandardizedScaleColor'] = dragon_data['ScaleColor'].apply(standardize_scale_color)
dragon_data['StandardizedWingColor'] = dragon_data['WingMembraneColor'].apply(standardize_wing_color)
dragon_data['StandardizedEyeColor'] = dragon_data['EyeColor'].apply(standardize_eye_color)
# Apply the function to each list in the 'PersonalityTraits' column
dragon_data['StandardizedPersonalityTraits'] = dragon_data['PersonalityTraits'].apply(standardize_traits)
# Remove duplicates in the 'StandardizedPersonalityTraits' column and clean up any characters
dragon_data['StandardizedPersonalityTraits'] = dragon_data['StandardizedPersonalityTraits'].apply(remove_duplicates)
dragon_data['StandardizedPersonalityTraits'] = dragon_data['StandardizedPersonalityTraits'].apply(lambda traits: [trait.strip("[]").replace("'", "") for trait in traits] if isinstance(traits, list) else [])
# Extract unique values for 'ScaleColor', 'WingMembraneColor', and 'EyeColor'
standardizedscale_colors = dragon_data['StandardizedScaleColor'].unique()
standardizedwing_colors = dragon_data['StandardizedWingColor'].unique()
standardizedeye_colors = dragon_data['StandardizedEyeColor'].unique()
# Flatten the list of lists to extract unique values properly
standardizedflattened_traits = [trait for sublist in dragon_data['StandardizedPersonalityTraits'] for trait in sublist]
# Sorting unique values
standardizedtraits_list = sorted(set(standardizedflattened_traits))
standardizedscale_colors_list = sorted(standardizedscale_colors)
standardizedwing_colors_list = sorted(standardizedwing_colors)
standardizedeye_colors_list = sorted(eye_colors)
dragon_data.to_csv('final_cleaned_dragon_data.csv', index = False)
def list_to_string(list_items):
"""Convert list to a comma-separated string."""
return ', '.join(map(str, list_items))
# Converting the original lists of unique values to comma-separated strings
original_traits_str = list_to_string(traits_list)
original_scale_colors_str = list_to_string(scale_colors_list)
original_wing_colors_str = list_to_string(wing_colors_list)
original_eye_colors_str = list_to_string(eye_colors_list)
# Converting the standardized lists to comma-separated strings
standardized_traits_str = list_to_string(standardizedtraits_list)
standardized_scale_colors_str = list_to_string(standardizedscale_colors_list)
standardized_wing_colors_str = list_to_string(standardizedwing_colors_list)
standardized_eye_colors_str = list_to_string(standardizedeye_colors_list)
Cleaning & Data Preparation¶
Cleaning:
- Fill missing values with 'Unknown' for categorical columns and 'None' for list-like structures
- Filter our data to include only the columns relevant to analyzing dragon lineages, physical characteristics, and behavioral traits.
- Our relevant columns in this case will be:
Name
,HatchedFrom
,Gender
,BirthDate
,Deathdate
,Size
,ScaleColor
,WingMembraneColor
,EyeColor
,PersonalityTraits
,FoughtInTheDance
,DanceAllegiance
,SurvivedTheDance
,BattlesDuringTheDance
,Outcome
,SurvivedTheBattle
,KilledBy
# Focus on relevant columns relevant_columns = ['Name', 'HatchedFrom', 'Gender', 'BirthDate', 'Deathdate', 'Size', 'ScaleColor', 'WingMembraneColor', 'EyeColor', 'PersonalityTraits', 'FoughtInTheDance', 'DanceAllegiance', 'SurvivedTheDance', 'BattlesDuringTheDance', 'Outcome', 'SurvivedTheBattle', 'KilledBy'] cleaned_dragon_data = dragon_data[relevant_columns]
- Our relevant columns in this case will be:
Preparation:
In this step, we'll make sure the physical traits and behavioral traits are standardized to make it easier to analyze and compare the dragons.
We'll list unique values from ScaleColor
, WingMembraneColor
, EyeColor
, and PersonalityTraits
, then create a function that groups each unique trait into more generalized group.
- Standardize colors for easier grouping
- Convert rows with multiple entries like ['Young', 'Strong', 'Agile', 'Brave'] into lists.
# Extract unique values for 'ScaleColor', 'WingMembraneColor', and 'EyeColor'
scale_colors = dragon_data['ScaleColor'].unique()
wing_colors = dragon_data['WingMembraneColor'].unique()
eye_colors = dragon_data['EyeColor'].unique()
# Data conversion to lists
dragon_data['PersonalityTraits'] = dragon_data['PersonalityTraits'].apply(lambda x x.strip("[]").replace("'", "").split(', ') if isinstance(x, str) else [])
# Function to map colors and traits
def standardize_scale_color(color):
return defined_scale_colors.get(color, color)
def standardize_wing_color(color):
return defined_wing_colors.get(color, color)
def standardize_eye_color(color):
return defined_eye_colors.get(color, color)
def standardize_traits(traits):
return [defined_traits.get(trait.strip(), trait.strip()) for trait in traits]
Original Traits¶
Personality Traits
Aggressive, Aggressive, Agile, Beautiful, Brave, Cunning, Fearsome, Ferocious, Fierce, Formidable, Friendly, Gentle, Heavy, Huge, Lazy, Loyal, Moody, Old, Powerful, Protective, Proud, Shy, Slender, Strong, Swift, Vicious, Wild, Willful, Young, beautiful, formidableScale Colors
Black, Bronze, Brown, Charcoal, Dark Cobalt Blue, Dark Grey, Dark Grey-Blue, Gold, Jade Green, Olive Green, Pale Blue, Pale Green, Pale Grey-White, Pale Pink, Pale Silver-Grey, Pale Silver-White, Pearl-White, Red, Scarlet Red, Silver, YellowWing & Accent Colors
Black, Brown, Copper, Dark Grey, Gold, Green-Blue, Pale Grey-White, Pale Orange, Pale Pink, Pearl-White, Red, Silver, Tan, YellowEye Colors
Bright Blue, Bright Green, Copper, Gold, Green, Pale Blue, Pale Green, Red, YellowStandardized Traits¶
Standardized Personality Traits
Aggressive, Beautiful, Cunning, Fearsome, Formidable, Friendly, Heavy, Huge, Lazy, Loyal, Moody, Old, Proud, Shy, Slender, Swift, Wild, Willful, YoungStandardized Scale Colors
Black, Blue, Bronze, Gold, Green, Pink, Red, Silver, White, YellowStandardized Wing & Accent Colors
Black, Bronze, Copper, Gold, Green, Grey, Pink, Red, Silver, White, YellowStandardized Eye Colors
Bright Blue, Bright Green, Copper, Gold, Green, Pale Blue, Pale Green, Red, Yellow# Define the function to convert dates to numeric values
def convert_to_numeric_date(date_str):
if pd.isna(date_str):
return None
if 'BC' in date_str:
return -int(re.findall(r'\d+', date_str)[0])
elif 'AC' in date_str:
return int(re.findall(r'\d+', date_str)[0])
return None
# Convert BirthDate and DeathDate to numeric values
dragon_data['BirthDate_numeric'] = dragon_data['BirthDate'].apply(convert_to_numeric_date)
dragon_data['DeathDate_numeric'] = dragon_data['DeathDate'].apply(convert_to_numeric_date)
# Filter only female dragons
female_dragons = dragon_data[dragon_data['Gender'] == 'Female']
# Define function
def find_potential_mothers_corrected(dragon_name, birth_date, female_dragons):
potential_mothers = []
for _, row in female_dragons.iterrows():
if row['Name'] != dragon_name and row['BirthDate_numeric'] < birth_date < row['DeathDate_numeric']:
potential_mothers.append(row['Name'])
return potential_mothers
#Apply the function to each dragon
dragon_data['PotentialMothers'] = dragon_data.apply(lambda row: find_potential_mothers_corrected(row['Name'], row['BirthDate_numeric'], female_dragons), axis=1)
potential_mothers = dragon_data[['Name', 'PotentialMothers']]
def format_potential_mothers_vertical(df):
markdown_output = ""
for _, row in df.iterrows():
# Check if the list is non-empty and does not contain 'None'
if row['PotentialMothers'] and 'None' not in row['PotentialMothers']:
mothers_list = ', '.join(row['PotentialMothers'])
markdown_output += f"**{row['Name']}**: {mothers_list}\n\n"
else:
continue # Skip adding to markdown_output if mothers are 'None'
return markdown_output
# Apply the function to the dataframe
potential_mothers_markdown = format_potential_mothers_vertical(potential_mothers)
Cleaning & Preparation Summary:¶
- Missing values in categorical columns were filled with 'Unknown'.
- The dataset was filtered to include only the columns relevant to analyzing dragon lineages, physical characteristics, and behavioral traits.
- Created dictionaries to map similar scale and wing colors to create new columns
StandardizedScaleColor
,StandardizedEyeColor
, andStandardizedWingColor
. - Converted the
PersonalityTraits
column to lists, flattened the list of lists to identify unique traits, mapped the unique traits to create a new columnStandardizedPersonalityTraits
. - Saved the cleaned and standardized dataset as
final_cleaned_dragon_data.csv
EDA will involve establishing any patterns and connections in order to validate or correct all current maternal relationships in the HatchedFrom
column for further analysis.
Establishing the Mothers of the Dragons¶
While there's a HatchedFrom
column already, those are just estimates based on various source materials. I think it's important we use the estimated birthdates and deathdates, as well as knowledge of the connections between their dragonriders, to make as accurate a family tree as possible. All mothers from the HatchedFrom
column are: Meraxes, Vhagar, Quicksilver, Dreamfyre, Silverwing, Meleys, Syrax, & Tessarion
To determine the most likely mother dragon for each dragon, we will:
- Filter Female Dragons
# Filter only female dragons female_dragons = dragon_data[dragon_data['Gender'] == 'Female']
- Check Life Span Overlap
# Define function def find_potential_mothers_corrected(dragon_name, birth_date, female_dragons): potential_mothers = [] for _, row in female_dragons.iterrows(): if row['Name'] != dragon_name and row['BirthDate_numeric'] < birth_date < row['DeathDate_numeric']: potential_mothers.append(row['Name']) return potential_mothers
- Compare each offspring's birth date with the lifespan of all female dragons
#Apply the function to each dragon dragon_data['PotentialMothers'] = dragon_data.apply(lambda row: find_potential_mothers_corrected(row['Name'], row['BirthDate_numeric'], female_dragons), axis=1)
Dragons & Their Possible Mothers¶
Arrax: Dreamfyre, Meleys, Syrax, Tessarion, Vhagar
Caraxes: Dreamfyre, Vhagar
Dreamfyre: Quicksilver, Vhagar
Grey Ghost: Dreamfyre, Meleys, Vhagar
Meleys: Dreamfyre, Vhagar
Moondancer: Dreamfyre, Meleys, Syrax, Tessarion, Vhagar
Morghul: Dreamfyre, Meleys, Moondancer, Syrax, Tessarion, Vhagar
Morning: Dreamfyre, Moondancer, Shrykos, Syrax, Tessarion, Vhagar
Quicksilver: Meraxes, Vhagar
Seasmoke: Dreamfyre, Meleys, Vhagar
Sheepstealer: Dreamfyre, Quicksilver, Vhagar
Shrykos: Dreamfyre, Meleys, Moondancer, Syrax, Tessarion, Vhagar
Silverwing: Dreamfyre, Quicksilver, Vhagar
Stormcloud: Dreamfyre, Meleys, Moondancer, Shrykos, Syrax, Tessarion, Vhagar
Sunfyre: Dreamfyre, Meleys, Syrax, Vhagar
Syrax: Dreamfyre, Meleys, Vhagar
Tessarion: Dreamfyre, Meleys, Syrax, Vhagar
Tyraxes: Dreamfyre, Meleys, Syrax, Tessarion, Vhagar
Vermax: Dreamfyre, Meleys, Syrax, Tessarion, Vhagar
Vermithor: Dreamfyre, Quicksilver, Vhagar
Vhagar: Meraxes
Cannibal: Quicksilver, Vhagar
dragon_data['BirthDate_numeric'] = dragon_data['BirthDate'].apply(convert_to_numeric_date)
dragon_data['DeathDate_numeric'] = dragon_data['DeathDate'].apply(convert_to_numeric_date)
# Plotting the timelines
plt.figure(figsize=(15, 6))
for index, row in dragon_data.iterrows():
plt.plot([row['BirthDate_numeric'], row['DeathDate_numeric']], [index, index], marker='o')
# Adding labels
plt.yticks(range(len(dragon_data)), dragon_data['Name'])
plt.xlabel('Year')
plt.ylabel('Dragon')
plt.title('Timeline of Dragon Lifespans')
plt.grid(True)
plt.show()
We'll use the potential mothers dataframe, this timeline, and canon material to understand the relationships:
Quicksilver: Quicksilver, a She-Dragon, shares pale-silvery tones with Meraxes, likely making Meraxes Quicksilver's mother. Since Meraxes died in 10 AC and dragon eggs rarely stay dormant for over 20 years, Quicksilver is most likely the mother of Dreamfyre and Silverwing.
Vermithor: Born around the same time as Dreamfyre and Silverwing but lacks their pale coloring or their more friendly temperaments. Vermithor is likely Vhagar's offspring due to his size and similar traits.
Cannibal and Sheepstealer: Dreamfyre, Quicksilver, and Vhagar are their potential mothers. They are wild dragons known for their aggression, like Vhagar. Sheepstealer has mud-brown coloring and copper eyes, similar to Vermithor, while Cannibal is pitch black, like Balerion. From color and temperament, Vhagar is most likely their mother.
Dreamfyre: Bonded to Queen Rhaena, Dreamfyre laid eggs by 43 AC, including a clutch in 50 AC from which Meleys and Caraxes may have hatched.
"A clutch of three eggs was laid by Dreamfyre on Fair Isle around 50 AC, which hatched after being brought to Dragonstone."
Tessarion: Tessarion has blue coloring and was a cradle egg (a dragon egg that is placed in the cradle of a newborn Targaryen baby) for Prince Daeron. After King Viserys I’s death, the Hightowers (Team Green) couldn’t access Dragonstone, making Dreamfyre the most likely mother due to her presence in King's Landing and blue coloring.
"Later in 114, Alicent gave birth to Daeron. Viserys couldn't admit that he only put eggs in the cradles of Rhaenyra’s sons because he doubted their paternity; he put an egg in his son Daeron’s cradle as well; this egg hatched into Tessarion."
Seasmoke: Meleys is likely Seasmoke's mother, as Rhaenys probably gave her son a dragon egg from her own dragon.
Silverwing: Silverwing is likely Grey Ghost's mother due to her time on Dragonstone, her temperament, and similar physical traits. She may also be the mother of Syrax and Sunfyre.
Shrykos and Morghul: These dragons were cradle eggs the twin children of King Aegon II Targaryen and Queen Helaena Targaryen, born in 123 AC. Vermithor resided in Dragonstone by 103 AC, while Dreamfyre was in the Dragonpit at King's Landing with Princess Helaena. Since Vermithor and Dreamfyre couldn't have mated due to being in separate locations, and the Greens had no access to Dragonstone, Tessarion is the likely mother. She was in King's Landing by 123 AC, and her title "The Blue Queen" indicates she laid eggs, making her the best candidate for the mother of Shrykos and Morghul.
I have most of these entries already placed correctly in the HatchedFrom
column but need to update the mother from Meraxes to Quicksilver for Dreamfyre and Silverwing.
# Update the 'HatchedFrom' column for Dreamfyre and Silverwing
dragon_data.loc[dragon_data['Name'].isin(['Dreamfyre', 'Silverwing']), 'HatchedFrom'] = 'Quicksilver'
She-Dragons & Their Offspring¶
# Update the 'HatchedFrom' column for Dreamfyre and Silverwing
dragon_data.loc[dragon_data['Name'].isin(['Dreamfyre', 'Silverwing']), 'HatchedFrom'] = 'Quicksilver'
dragon_data.to_csv('final_cleaned_dragon_data.csv', index = False)
# Load data
dragon_data = pd.read_csv('final_cleaned_dragon_data.csv')
# Filter out 'Unknown' nodes
dragon_data = dragon_data[dragon_data['HatchedFrom'] != 'Unknown']
# Calculate the number of offspring for each parent
offspring_counts = dragon_data['HatchedFrom'].value_counts()
# Make a directed graph
G = nx.DiGraph()
# Add edges based on parent-offspring relationships in the original dataset
for _, row in dragon_data.iterrows():
parent = row['HatchedFrom']
offspring = row['Name']
G.add_edge(parent, offspring)
# Define node sizes and colors based on dragon name
node_sizes = []
node_colors = []
# Define a color map for the nodes
color_data = {
'Name': ['Arrax', 'Caraxes', 'Cannibal', 'Dreamfyre', 'Meleys', 'Moondancer', 'Morghul', 'Morning', 'Seasmoke', 'Sheepstealer', 'Shrykos', 'Silverwing',
'Stormcloud', 'Sunfyre', 'Syrax', 'Tessarion', 'Tyraxes', 'Vermax', 'Vermithor', 'Vhagar', 'Balerion', 'Meraxes', 'Quicksilver', 'Grey Ghost'],
'HexCode_Scales': ['#F4EFE6', '#C50016', '#181818', '#7A95C8', '#B22A30', '#8A9C74', '#2F2F2F', '#CC9E9B', '#DADADA', '#4A3931', '#245550', '#bcc6cc', '#354669',
'#E5B347', '#F5D21E', '#2F37A8', '#181818', '#929151', '#A77F5B', '#9A835A', '#181818', '#D3D4DB', '#F3ECEA', '#F2F2F2']
}
color_map = dict(zip(color_data['Name'], color_data['HexCode_Scales']))
mother_node_size = 1500
offspring_node_size = 1000
for node in G.nodes():
if node in offspring_counts:
node_sizes.append(mother_node_size) # Distinct size for mothers
node_colors.append(color_map.get(node, '#611010')) # Use color from the map or a default color
else:
node_sizes.append(offspring_node_size) # Distinct size for offspring
node_colors.append(color_map.get(node, '#123524')) # Use color from the map or a default color
# Use an automatic layout to get initial positions
pos = nx.spring_layout(G, seed=42)
pos['Vermax'][1] += 0.9
pos['Vermax'][0] += 1.0
pos['Morning'][1] += .9
pos['Morning'][0] += .5
pos['Sheepstealer'][1] += 1.7
pos['Sheepstealer'][0] -= .9
pos['Tessarion'][1] -= 0.4
pos['Tessarion'][0] -= 0.6
pos['Shrykos'][1] -= 1.8
pos['Shrykos'][0] -= 0.5
pos['Morghul'][0] -= 0.7
pos['Vhagar'][1] += 0.2
pos['Vhagar'][0] -= 0.2
pos['Meraxes'][1] -= 0.3
pos['Grey Ghost'][0] -= 0.3
pos['Grey Ghost'][1] -= 0.4
pos['Sunfyre'][0] += 0.3
pos['Sunfyre'][1] -= 0.1
pos['Syrax'][0] += 0.2
pos['Syrax'][1] += 0.1
pos['Stormcloud'][0] -= 0.4
pos['Tyraxes'][0] -= 0.3
pos['Tyraxes'][1] -= 0.2
pos['Meraxes'][1] -= 0.1
pos['Arrax'][1] += 0.2
pos['Arrax'][0] -= 0.2
pos['Moondancer'][0] += 0.1
pos['Seasmoke'][1] -= 0.5
pos['Dreamfyre'][0] += 0.1
# Draw the graph with initial and manually adjusted positions
plt.figure(figsize=(12, 8))
# Draw edges with specified colors
edges = G.edges(data=True)
edge_colors = ['#8f8f8f' for _ in edges]
nx.draw_networkx_edges(G, pos, alpha=0.5, edge_color=edge_colors, min_source_margin=15, min_target_margin=15)
# Draw nodes with distinct sizes and colors
nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=node_colors, alpha=0.8)
# Draw labels with updated font size and weight
labels = {node: node for node in G.nodes()}
nx.draw_networkx_labels(G, pos, labels=labels, font_size=10)
plt.title("Dragon Lineage - Mother and Offspring", fontsize=14)
plt.axis('off')
plt.savefig('lineage_network_graph_updated.png')
plt.show()
EDA Summary¶
We've successfully established mother to offspring relatioships using source material, calculated potential mothers, and lifespan timelines. These are the following results:
Meraxes' pale scales show up repeatedly across generations, while Vhagar's bronze and green tones stay consistent. Balerion's black scales aren't as common, but his red eyes are noticeable, especially in younger dragons, suggesting red eyes might be recessive. The red from him appears in scales, accents, or eyes in nearly every generation. Vhagar seems to influence green and copper eye colors. Her green eyes mixed with Balerion's red eyes likely caused the copper eyes seen in Vermithor and Sheepstealer, which then passed to Vermithor's descendants, Meleys and Tessarion.
Both Silverwing and Dreamfyre paired with Vermithor, producing new color variations. Dreamfyre hatched Caraxes and Meleys, both bright red dragons, while Silverwing hatched Syrax and Sunfyre, the only yellow-toned dragons. Syrax is yellow, and Sunfyre is gold. Caraxes and Syrax share yellow traits, and Meleys and Sunfyre share metallic coloring, suggesting Vermithor's metallic bronze from Vhagar is a strong trait. Gold eyes from Meraxes appear sporadically. All three of Silverwing's offspring have varied scale and accent colors but inherit green eyes.
With the above information, we can move on to determining dominant and recessive traits, potential paternity, and predicting the physical appearances of dragons without canon descriptions.
Before we can begin with assigning paternity, we have to establish trait dominance. In this section of the project, we'll be focusing on determining recessive and dominant hereditary traits in terms of scale colors, wings membrance & accent colors, & eye colors.
Wing & Accent Colors are the colors of wing membranes and/or crests and horns.
Scale Colors, Accent Colors, & Eye Colors¶
# Scale Colors
scale_data = pd.DataFrame({
'Name': ['Arrax', 'Caraxes', 'Cannibal', 'Dreamfyre', 'Meleys', 'Moondancer', 'Morghul', 'Morning', 'Seasmoke', 'Sheepstealer', 'Shrykos', 'Silverwing',
'Stormcloud', 'Sunfyre', 'Syrax', 'Tessarion', 'Tyraxes', 'Vermax', 'Vermithor', 'Vhagar', 'Balerion', 'Meraxes', 'Quicksilver', 'Grey Ghost'],
'HexCode_Scales': ['#F4EFE6', '#632322', '#181818', '#7AA2D0', '#8E1000', '#75968F', '#616269', '#B98293', '#DADADA', '#46322A', '#245550', '#bcc6cc', '#354669',
'#F1BD59', '#E4BD57', '#2F37A8', '#181818', '#929151', '#814C32', '#9A835A', '#181818', '#D3D4DB', '#F3ECEA', '#F2F2F2'],
})
wingmembrane_data = pd.DataFrame({
'Name': ['Arrax', 'Caraxes', 'Cannibal', 'Dreamfyre', 'Meleys', 'Moondancer', 'Morghul', 'Morning', 'Seasmoke', 'Sheepstealer', 'Shrykos', 'Silverwing',
'Stormcloud', 'Sunfyre', 'Syrax', 'Tessarion', 'Tyraxes', 'Vermax', 'Vermithor', 'Vhagar', 'Balerion', 'Meraxes', 'Quicksilver', 'Grey Ghost'],
'HexCode_Accents': ['#E5B347', '#632322', '#181818', '#bcc6cc', '#B87333', '#F4EFE6', '#616269', '#181818', '#CC9E9B', '#46322A', '#B87333', '#bcc6cc', '#2F2F2F',
'#CC9E9B', '#E4BD57', '#B87333', '#C50016', '#C0712C', '#C56731', '#4F635A', '#181818', '#CC9E9B', '#E5B347', '#F2F2F2']
})
eye_data = pd.DataFrame({
'Name': ['Arrax', 'Caraxes', 'Cannibal', 'Dreamfyre', 'Meleys', 'Moondancer', 'Morghul', 'Morning', 'Seasmoke', 'Sheepstealer', 'Shrykos', 'Silverwing',
'Stormcloud', 'Sunfyre', 'Syrax', 'Tessarion', 'Tyraxes', 'Vermax', 'Vermithor', 'Vhagar', 'Balerion', 'Meraxes', 'Quicksilver', 'Grey Ghost'],
'HexCode_Eyes': ['#E5B347', '#F5D21E', '#59A630', '#A6C3DC', '#B87333', '#98AF05', '#B83A36', '#B83A36', '#A6C3DC', '#C48C55', '#B87333', '#A6C3DC', '#A6C3DC',
'#5E7B36', '#5E7B36', '#B87333', '#B83A36', '#F5D21E', '#C48C55', '#5E7B36', '#B83A36', '#E5B347', '#E5B347', '#98AF05'],
})
# Sort the DataFrame by the 'Name' column
scale_data = scale_data.sort_values(by='Name').reset_index(drop=True)
wingmembrane_data = wingmembrane_data.sort_values(by='Name').reset_index(drop=True)
eye_data = eye_data.sort_values(by='Name').reset_index(drop=True)
# Function to convert hex color code to RGB
def hex_to_rgb(hex_code):
return tuple(int(hex_code[i:i+2], 16)/255.0 for i in (1, 3, 5))
# Function to plot colors with names
def plot_colors_from_hex(names, hex_codes, title):
fig, ax = plt.subplots(figsize=(12, 2))
ax.set_title(title)
for i, (name, hex_code) in enumerate(zip(names, hex_codes)):
color = hex_to_rgb(hex_code)
ax.add_patch(plt.Rectangle((i, 0), 1, 1, color=color))
ax.text(i + 0.5, -0.1, name, ha='center', va='center', fontsize=10, color='black', rotation=90)
ax.set_xlim(0, len(hex_codes))
ax.set_ylim(-0.2, 1)
ax.axis('off')
plt.show()
# Plot the hex colors for scales with names
scales = plot_colors_from_hex(scale_data['Name'], scale_data['HexCode_Scales'], 'Scale Colors')
# Plot the hex colors for scales with names
accents = plot_colors_from_hex(wingmembrane_data['Name'], wingmembrane_data['HexCode_Accents'], 'Wing Membrane & Accent Colors')
# Plot the hex colors for scales with names
eyes = plot_colors_from_hex(eye_data['Name'], eye_data['HexCode_Eyes'], 'Eye Colors')
Determining Trait Dominance¶
To determine dominance, we will use the standardized color groups we made previously, then calculate which groups appear most frequently. Before we can do that, we need to create a new dataframe with the necessary lineage information like Father
, Mother
, and their associated color groups.
First we'll make the new dataframe by:
- Group colors in
EyeColor
by visual similarity. - Assign bonded pairs as
Father
andMother
. - Merge the DataFrames to link offspring with their parent color groups.
# Step 1: Group Colors by Visual Similarity
eye_assignments = {
'Gold': 'Gold',
'Yellow': 'Yellow',
'Green': 'Green',
'Pale Blue': 'Blue',
'Bright Blue': 'Blue',
'Bright Green': 'Green',
'Copper': 'Copper',
'Pale Green': 'Green',
'Red': 'Red',
'Dark Green': 'Green'
}
df['EyeColorGroup'] = df['EyeColor'].map(eye_assignments)
# Step 2: Assign 'Father' and 'Mother' from Known Bonded Pairs
df['Mother'] = df['HatchedFrom']
bonded_pairs = [
('Balerion', 'Meraxes'),
('Balerion', 'Vhagar'),
('Vermithor', 'Dreamfyre'),
('Vermithor', 'Silverwing'),
('Caraxes', 'Syrax'),
]
bonded_pairs_df = pd.DataFrame(bonded_pairs, columns=['Father', 'Mother'])
# Step 3: Merge DataFrames to Link Offspring with Parent Color Groups
bonded_pairs_df = bonded_pairs_df.merge(df[['Name', 'ScaleColorGroup', 'AccentColorGroup', 'EyeColorGroup']],
left_on='Father', right_on='Name', how='left').drop(columns=['Name'])
bonded_pairs_df = bonded_pairs_df.merge(df[['Name', 'ScaleColorGroup', 'AccentColorGroup', 'EyeColorGroup']],
left_on='Mother', right_on='Name', how='left', suffixes=('_Father', '_Mother')).drop(columns=['Name'])
color_df = df.merge(bonded_pairs_df, left_on='Mother', right_on='Mother', how='left')
# Initial dataframes
dragon_data = pd.read_csv('final_cleaned_dragon_data.csv')
color_data = dragon_data[['Name', 'HatchedFrom', 'ScaleColor', 'WingMembraneColor', 'EyeColor']]
scale_data = pd.DataFrame({
'Name': ['Arrax', 'Caraxes', 'Cannibal', 'Dreamfyre', 'Meleys', 'Moondancer', 'Morghul', 'Morning', 'Seasmoke', 'Sheepstealer', 'Shrykos', 'Silverwing',
'Stormcloud', 'Sunfyre', 'Syrax', 'Tessarion', 'Tyraxes', 'Vermax', 'Vermithor', 'Vhagar', 'Balerion', 'Meraxes', 'Quicksilver', 'Grey Ghost'],
'HexCode_Scales': ['#F4EFE6', '#632322', '#181818', '#7AA2D0', '#8E1000', '#75968F', '#616269', '#B98293', '#DADADA', '#46322A', '#245550', '#bcc6cc', '#354669',
'#F1BD59', '#E4BD57', '#2F37A8', '#181818', '#929151', '#814C32', '#9A835A', '#181818', '#D3D4DB', '#F3ECEA', '#F2F2F2'],
})
accent_data = pd.DataFrame({
'Name': ['Arrax', 'Caraxes', 'Cannibal', 'Dreamfyre', 'Meleys', 'Moondancer', 'Morghul', 'Morning', 'Seasmoke', 'Sheepstealer', 'Shrykos', 'Silverwing',
'Stormcloud', 'Sunfyre', 'Syrax', 'Tessarion', 'Tyraxes', 'Vermax', 'Vermithor', 'Vhagar', 'Balerion', 'Meraxes', 'Quicksilver', 'Grey Ghost'],
'HexCode_Accents': ['#E5B347', '#632322', '#181818', '#bcc6cc', '#B87333', '#F4EFE6', '#616269', '#181818', '#CC9E9B', '#46322A', '#B87333', '#bcc6cc', '#2F2F2F',
'#CC9E9B', '#E4BD57', '#B87333', '#C50016', '#C0712C', '#C56731', '#4F635A', '#181818', '#CC9E9B', '#E5B347', '#F2F2F2']
})
eye_data = pd.DataFrame({
'Name': ['Arrax', 'Caraxes', 'Cannibal', 'Dreamfyre', 'Meleys', 'Moondancer', 'Morghul', 'Morning', 'Seasmoke', 'Sheepstealer', 'Shrykos', 'Silverwing',
'Stormcloud', 'Sunfyre', 'Syrax', 'Tessarion', 'Tyraxes', 'Vermax', 'Vermithor', 'Vhagar', 'Balerion', 'Meraxes', 'Quicksilver', 'Grey Ghost'],
'HexCode_Eyes': ['#E5B347', '#F5D21E', '#59A630', '#A6C3DC', '#B87333', '#98AF05', '#B83A36', '#B83A36', '#A6C3DC', '#C48C55', '#B87333', '#A6C3DC', '#A6C3DC',
'#5E7B36', '#5E7B36', '#B87333', '#B83A36', '#F5D21E', '#C48C55', '#5E7B36', '#B83A36', '#E5B347', '#E5B347', '#98AF05'],
})
# Converting dictionaries to DataFrames
scale_df = pd.DataFrame(scale_data)
accent_df = pd.DataFrame(accent_data)
eye_df = pd.DataFrame(eye_data)
# Merging dataframes
merged_df = scale_df.merge(accent_df, on='Name', how='left').merge(eye_df, on='Name', how='left')
color_df = merged_df.merge(color_data, on='Name', how='left')
# Alphabetize by 'Name'
color_df = color_df.sort_values(by='Name').reset_index(drop=True)
df = pd.DataFrame(color_df)
# Define specific groups based on visual similarity
scale_assignments = {
'Silver': 'Silver',
'Charcoal': 'Black',
'Gold': 'Gold',
'Bronze': 'Bronze',
'Pearl-White': 'White',
'Pale Silver-Grey': 'Silver',
'Pale Silver-White': 'White',
'Pale Grey-White': 'White',
'Pale Grey': 'Grey',
'Red': 'Red',
'Scarlet Red': 'Red',
'Pale Pink': 'Pink',
'Black': 'Black',
'Dark Grey': 'Black',
'Brown': 'Bronze',
'Pale Green': 'Green',
'Olive Green': 'Green',
'Jade Green': 'Green',
'Green-Blue': 'Green',
'Yellow': 'Yellow',
'Dark Cobalt Blue': 'Blue',
'Pale Blue': 'Blue',
'Dark Grey-Blue': 'Blue'
}
accent_assignments = {
'Gold': 'Gold',
'Red': 'Red',
'Black': 'Black',
'Silver': 'Silver',
'Pale Pink': 'Pink',
'Pearl-White': 'White',
'Dark Grey': 'Black',
'Grey': 'Grey',
'Brown': 'Bronze',
'Green': 'Green',
'Yellow': 'Yellow',
'Pale Orange': 'Yellow',
'Tan': 'Bronze',
'Pale Grey': 'Grey',
'Green-Blue': 'Green',
'Pink': 'Pink',
'Bronze': 'Bronze',
'Charcoal': 'Black',
'Pale Grey-White': 'White',
'Copper': 'Copper',
'White': 'White'
}
eye_assignments = {
'Gold': 'Gold',
'Yellow': 'Yellow',
'Green': 'Green',
'Pale Blue': 'Blue',
'Bright Blue': 'Blue',
'Bright Green': 'Green',
'Copper': 'Copper',
'Pale Green': 'Green',
'Red': 'Red',
'Dark Green': 'Green'
}
# Apply the group assignments
df['ScaleColorGroup'] = df['ScaleColor'].map(scale_assignments)
df['AccentColorGroup'] = df['WingMembraneColor'].map(accent_assignments)
df['EyeColorGroup'] = df['EyeColor'].map(eye_assignments)
# Ensure 'Mother' is mapped using 'HatchedFrom'
df['Mother'] = df['HatchedFrom']
# List of known bonded pairs
bonded_pairs = [
('Balerion', 'Meraxes'),
('Balerion', 'Vhagar'),
('Vermithor', 'Dreamfyre'),
('Vermithor', 'Silverwing'),
('Caraxes', 'Syrax'),
]
# Create a DataFrame from bonded pairs
bonded_pairs_df = pd.DataFrame(bonded_pairs, columns=['Father', 'Mother'])
# Merge color group information for fathers and mothers
bonded_pairs_df = bonded_pairs_df.merge(df[['Name', 'ScaleColorGroup', 'AccentColorGroup', 'EyeColorGroup']], left_on='Father', right_on='Name', how='left').drop(columns=['Name'])
bonded_pairs_df = bonded_pairs_df.merge(df[['Name', 'ScaleColorGroup', 'AccentColorGroup', 'EyeColorGroup']], left_on='Mother', right_on='Name', how='left', suffixes=('_Father', '_Mother')).drop(columns=['Name'])
# Merge color data with bonded pairs to associate offspring with both parents
color_df = df.merge(bonded_pairs_df, left_on='Mother', right_on='Mother', how='left')
# Ensure no nulls by filling with 'Unknown'
color_df['ScaleColorGroup_Father'] = color_df['Father'].map(df.set_index('Name')['ScaleColorGroup']).fillna('Unknown')
color_df['ScaleColorGroup_Mother'] = color_df['Mother'].map(df.set_index('Name')['ScaleColorGroup']).fillna('Unknown')
color_df['AccentColorGroup_Father'] = color_df['Father'].map(df.set_index('Name')['AccentColorGroup']).fillna('Unknown')
color_df['AccentColorGroup_Mother'] = color_df['Mother'].map(df.set_index('Name')['AccentColorGroup']).fillna('Unknown')
color_df['EyeColorGroup_Father'] = color_df['Father'].map(df.set_index('Name')['EyeColorGroup']).fillna('Unknown')
color_df['EyeColorGroup_Mother'] = color_df['Mother'].map(df.set_index('Name')['EyeColorGroup']).fillna('Unknown')
# Display a sample of the merged data to ensure correctness
color_df[['Name', 'ScaleColor', 'ScaleColorGroup', 'HexCode_Scales', 'WingMembraneColor', 'AccentColorGroup', 'HexCode_Accents', 'EyeColor', 'EyeColorGroup', 'HexCode_Eyes', 'Father', 'Mother', 'ScaleColorGroup_Father',
'ScaleColorGroup_Mother', 'AccentColorGroup_Father', 'AccentColorGroup_Mother', 'EyeColorGroup_Father', 'EyeColorGroup_Mother']].head()
Name | ScaleColor | ScaleColorGroup | HexCode_Scales | WingMembraneColor | AccentColorGroup | HexCode_Accents | EyeColor | EyeColorGroup | HexCode_Eyes | Father | Mother | ScaleColorGroup_Father | ScaleColorGroup_Mother | AccentColorGroup_Father | AccentColorGroup_Mother | EyeColorGroup_Father | EyeColorGroup_Mother | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Arrax | Pearl-White | White | #F4EFE6 | Gold | Gold | #E5B347 | Gold | Gold | #E5B347 | Caraxes | Syrax | Red | Yellow | Red | Yellow | Yellow | Green |
1 | Balerion | Black | Black | #181818 | Black | Black | #181818 | Red | Red | #B83A36 | NaN | Unknown | Unknown | Unknown | Unknown | Unknown | Unknown | Unknown |
2 | Cannibal | Charcoal | Black | #181818 | Black | Black | #181818 | Bright Green | Green | #59A630 | Balerion | Vhagar | Black | Bronze | Black | Green | Red | Green |
3 | Caraxes | Red | Red | #632322 | Red | Red | #632322 | Yellow | Yellow | #F5D21E | Vermithor | Dreamfyre | Bronze | Blue | Bronze | Silver | Copper | Blue |
4 | Dreamfyre | Pale Blue | Blue | #7AA2D0 | Silver | Silver | #bcc6cc | Pale Blue | Blue | #A6C3DC | NaN | Quicksilver | Unknown | White | Unknown | Gold | Unknown | Gold |
df = pd.DataFrame(color_df)
# Calculate the overall frequency of each trait in the entire dataset
scale_frequencies = df['ScaleColorGroup'].value_counts(normalize=True).to_dict()
accent_frequencies = df['AccentColorGroup'].value_counts(normalize=True).to_dict()
eye_frequencies = df['EyeColorGroup'].value_counts(normalize=True).to_dict()
# Calculate the frequency of each trait in parents
parent_scale_frequencies = df[['ScaleColorGroup_Father', 'ScaleColorGroup_Mother']].melt().value_counts(normalize=True).to_dict()
parent_accent_frequencies = df[['AccentColorGroup_Father', 'AccentColorGroup_Mother']].melt().value_counts(normalize=True).to_dict()
parent_eye_frequencies = df[['EyeColorGroup_Father', 'EyeColorGroup_Mother']].melt().value_counts(normalize=True).to_dict()
# Calculate the frequency of each trait in offspring
offspring_scale_frequencies = df['ScaleColorGroup'].value_counts(normalize=True).to_dict()
offspring_accent_frequencies = df['AccentColorGroup'].value_counts(normalize=True).to_dict()
offspring_eye_frequencies = df['EyeColorGroup'].value_counts(normalize=True).to_dict()
# Function to calculate generation span for each trait
def calculate_generation_span(df, trait_column, father_column, mother_column):
generation_span = {}
for name, group in df.groupby(trait_column):
generations = set()
for idx, row in group.iterrows():
if row[father_column] == name or row[mother_column] == name:
generations.add('parent')
if row[trait_column] == name:
generations.add('offspring')
generation_span[name] = len(generations)
return generation_span
# Calculate generation span for each trait
scale_generation_span = calculate_generation_span(df, 'ScaleColorGroup', 'ScaleColorGroup_Father', 'ScaleColorGroup_Mother')
accent_generation_span = calculate_generation_span(df, 'AccentColorGroup', 'AccentColorGroup_Father', 'AccentColorGroup_Mother')
eye_generation_span = calculate_generation_span(df, 'EyeColorGroup', 'EyeColorGroup_Father', 'EyeColorGroup_Mother')
# Function to determine dominance based on frequency and generation span
def determine_dominance(trait_frequencies, parent_trait_frequencies, offspring_trait_frequencies, generation_span):
dominance_info = {}
for trait, freq in trait_frequencies.items():
parent_freq = parent_trait_frequencies.get(trait, 0)
offspring_freq = offspring_trait_frequencies.get(trait, 0)
gen_span = generation_span.get(trait, 0)
if freq > 0.1 and gen_span > 1: # Threshold for dominance
dominance_info[trait] = 'Dominant'
else:
dominance_info[trait] = 'Recessive'
return dominance_info
# Determine dominance for each trait type
scale_dominance_info = determine_dominance(scale_frequencies, parent_scale_frequencies, offspring_scale_frequencies, scale_generation_span)
accent_dominance_info = determine_dominance(accent_frequencies, parent_accent_frequencies, offspring_accent_frequencies, accent_generation_span)
eye_dominance_info = determine_dominance(eye_frequencies, parent_eye_frequencies, offspring_eye_frequencies, eye_generation_span)
def format_dominance_info(dominance_dict):
dominant_colors = [color for color, dom in dominance_dict.items() if dom == 'Dominant']
recessive_colors = [color for color, dom in dominance_dict.items() if dom == 'Recessive']
return dominant_colors, recessive_colors
scale_dominant, scale_recessive = format_dominance_info(scale_dominance_info)
accent_dominant, accent_recessive = format_dominance_info(accent_dominance_info)
eye_dominant, eye_recessive = format_dominance_info(eye_dominance_info)
Now that we have the necessary information, we can move on to the final stage and determine dominance by:
# Step 1: Calculating the frequency of each trait in parents, offspring, and the entire dataset.
scale_frequencies = df['ScaleColorGroup'].value_counts(normalize=True).to_dict()
accent_frequencies = df['AccentColorGroup'].value_counts(normalize=True).to_dict()
eye_frequencies = df['EyeColorGroup'].value_counts(normalize=True).to_dict()
# Step 2: Create a function to determining how traits span across generations.
def calculate_generation_span(df, trait_column, father_column, mother_column):
generation_span = {}
for name, group in df.groupby(trait_column):
generations = set()
for idx, row in group.iterrows():
if row[father_column] == name or row[mother_column] == name:
generations.add('parent')
if row[trait_column] == name:
generations.add('offspring')
generation_span[name] = len(generations)
return generation_span
# Step 3: Create a function to determine dominance based on frequency and generation span
def determine_dominance(trait_frequencies, parent_trait_frequencies, offspring_trait_frequencies, generation_span):
dominance_info = {}
for trait, freq in trait_frequencies.items():
parent_freq = parent_trait_frequencies.get(trait, 0)
offspring_freq = offspring_trait_frequencies.get(trait, 0)
gen_span = generation_span.get(trait, 0)
if freq > 0.1 and gen_span > 1: # Threshold for dominance
dominance_info[trait] = 'Dominant'
else:
dominance_info[trait] = 'Recessive'
return dominance_info
We need to determine paternity for Dreamfyre, Silverwing, Seasmoke, Shrykos, and Morghul. We'll use the previously determined dominance information in combination with a machine learning model to assign paternity. The model we'll use is the Random Forests Model. It aggregates the results of multiple decision trees, giving us a more stable and reliable prediction compared to a single decision tree. This reduces the risk of overfitting and gives a more generalized prediction.
The steps we'll take to determine paternity will be: 1) Define traits of the offspring and their potential fathers.
# Define the traits of Seasmoke, Meleys, and potential fathers
seasmoke_traits = {'ScaleColorGroup': 'Silver', 'AccentColorGroup': 'Pink', 'EyeColorGroup': 'Blue'}
meleys_traits = {'ScaleColorGroup': 'Red', 'AccentColorGroup': 'Copper', 'EyeColorGroup': 'Copper'}
potential_fathers = pd.DataFrame({
'Name': ['Sheepstealer', 'Cannibal'],
'ScaleColorGroup': ['Bronze', 'Black'],
'AccentColorGroup': ['Bronze', 'Black'],
'EyeColorGroup': ['Copper', 'Green']
})
2) Compare the dominance information of the lineages and parents against the offspring.
# Grandparent traits data
grandparent_traits = {
'Meleys': {'Father': {'ScaleColorGroup': 'Bronze', 'AccentColorGroup': 'Bronze', 'EyeColorGroup': 'Copper'},
'Mother': {'ScaleColorGroup': 'Blue', 'AccentColorGroup': 'Silver', 'EyeColorGroup': 'Blue'}},
'Sheepstealer': {'Father': {'ScaleColorGroup': 'Black', 'AccentColorGroup': 'Black', 'EyeColorGroup': 'Red'},
'Mother': {'ScaleColorGroup': 'Bronze', 'AccentColorGroup': 'Green', 'EyeColorGroup': 'Green'}},
'Cannibal': {'Father': {'ScaleColorGroup': 'Black', 'AccentColorGroup': 'Black', 'EyeColorGroup': 'Red'},
'Mother': {'ScaleColorGroup': 'Bronze', 'AccentColorGroup': 'Green', 'EyeColorGroup': 'Green'}}
}
# Dominance information
scale_dominance = scale_dominance_info
accent_dominance = accent_dominance_info
eye_dominance = eye_dominance_info
# Combine unique values from potential fathers and Seasmoke
combined_values = {
'ScaleColorGroup': list(set(potential_fathers['ScaleColorGroup'].unique()).union(['Silver'])),
'AccentColorGroup': list(set(potential_fathers['AccentColorGroup'].unique()).union(['Pink'])),
'EyeColorGroup': list(set(potential_fathers['EyeColorGroup'].unique()).union(['Blue']))
}
3) Convert traits to numerical values to train the Random Forest model for paternity prediction.
# Encode categorical traits into numerical values
label_encoders = {col: LabelEncoder().fit(combined_values[col]) for col in combined_values}
# Transform the traits of potential fathers
encoded_fathers = potential_fathers.copy()
for col, le in label_encoders.items():
encoded_fathers[col] = le.transform(potential_fathers[col])
# Prepare feature matrix (X) and target vector (y)
X = encoded_fathers[['ScaleColorGroup', 'AccentColorGroup', 'EyeColorGroup']]
y = encoded_fathers['Name']
4) Create functions for the model to run 100 iterations and determine the most predicted father.
# Function to fit the model and predict using Random Forest
def fit_and_predict_rf(traits):
clf = RandomForestClassifier(random_state=42)
clf.fit(X, y)
encoded_traits = {col: le.transform([traits[col]])[0] for col, le in label_encoders.items()}
traits_features = pd.DataFrame([encoded_traits])
return clf.predict(traits_features)[0]
# Fit the Random Forest model multiple times and collect predictions
def collect_predictions(traits, iterations=100):
predictions = [fit_and_predict_rf(traits) for _ in range(iterations)]
return pd.Series(predictions).mode()[0]
# Determine the most predicted father for Seasmoke
most_predicted_father = collect_predictions(seasmoke_traits)
The Paternity of Dreamfyre and Silverwing¶
For Dreamfyre and Silverwing, the only logical options would be Balerion and Cannibal. I think Cannibal is the father of Dreamfyre and Silverwing based on a couple of different theories:
- Size: Quicksilver only lived to be about 30-35 years old, dying in 10 AC. Balerion would have been 145 years old (or older), making him uncomfortably larger in size than Quicksilver. Without going into too much detail, I don't think coiling could've happened without Quicksilver being severely wounded or crushed by Balerion.
- Cannibal was the oldest of the three wild dragons. Sheepstealer was said to have hatched between 40-51 AC, so Cannibal had to have hatched long before then, ~around 10-25 AC. This would've made him a decent age around the time of Dreamfyre and Silverwing's eggs hatching in 32 and 34 AC.
- It's believed Cannibal hatched on Dragonstone, which is where Quicksilver resided with her rider and the Prince of Dragonstone, Prince Aenys Targaryen, until 37 AC. That timeline places Cannibal and Quicksilver both on Dragonstone.
# Define the traits of Dreamfyre, Silverwing, and potential fathers
dreamfyre_traits = {'ScaleColorGroup': 'Blue', 'AccentColorGroup': 'Silver', 'EyeColorGroup': 'Blue'}
silverwing_traits = {'ScaleColorGroup': 'Silver', 'AccentColorGroup': 'Silver', 'EyeColorGroup': 'Blue'}
quicksilver_traits = {'ScaleColorGroup': 'White', 'AccentColorGroup': 'Gold', 'EyeColorGroup': 'Gold'}
potential_fathers = pd.DataFrame({
'Name': ['Balerion', 'Cannibal'],
'ScaleColorGroup': ['Black', 'Black'],
'AccentColorGroup': ['Black', 'Black'],
'EyeColorGroup': ['Red', 'Green']
})
# Show the results for Dreamfyre and Silverwing
dreamfyre_silverwing_result = color_df[color_df['Name'].isin(['Dreamfyre', 'Silverwing'])][['Name', 'Mother', 'Father']]
df = pd.DataFrame(color_df)
# Define the traits of Dreamfyre, Silverwing, and potential fathers
dreamfyre_traits = {'ScaleColorGroup': 'Blue', 'AccentColorGroup': 'Silver', 'EyeColorGroup': 'Blue'}
silverwing_traits = {'ScaleColorGroup': 'Silver', 'AccentColorGroup': 'Silver', 'EyeColorGroup': 'Blue'}
quicksilver_traits = {'ScaleColorGroup': 'White', 'AccentColorGroup': 'Gold', 'EyeColorGroup': 'Gold'}
potential_fathers = pd.DataFrame({
'Name': ['Balerion', 'Cannibal'],
'ScaleColorGroup': ['Black', 'Black'],
'AccentColorGroup': ['Black', 'Black'],
'EyeColorGroup': ['Red', 'Green']
})
# Dominance information
scale_dominance = scale_dominance_info
accent_dominance = accent_dominance_info
eye_dominance = eye_dominance_info
# Combine unique values from potential fathers and offspring traits
combined_values = {
'ScaleColorGroup': list(set(potential_fathers['ScaleColorGroup'].unique()).union(['Blue', 'Silver'])),
'AccentColorGroup': list(set(potential_fathers['AccentColorGroup'].unique()).union(['Silver', 'Silver'])),
'EyeColorGroup': list(set(potential_fathers['EyeColorGroup'].unique()).union(['Blue', 'Blue']))
}
# Encode categorical traits into numerical values
label_encoders = {col: LabelEncoder().fit(combined_values[col]) for col in combined_values}
# Transform the traits of potential fathers
encoded_fathers = potential_fathers.copy()
for col, le in label_encoders.items():
encoded_fathers[col] = le.transform(potential_fathers[col])
# Prepare feature matrix (X) and target vector (y)
X = encoded_fathers[['ScaleColorGroup', 'AccentColorGroup', 'EyeColorGroup']]
y = encoded_fathers['Name']
# Function to fit the model and predict using Random Forest
def fit_and_predict_rf(traits):
clf = RandomForestClassifier(random_state=42)
clf.fit(X, y)
encoded_traits = {col: le.transform([traits[col]])[0] for col, le in label_encoders.items()}
traits_features = pd.DataFrame([encoded_traits])
return clf.predict(traits_features)[0]
# Fit the Random Forest model multiple times and collect predictions
def collect_predictions(traits, iterations=100):
predictions = [fit_and_predict_rf(traits) for _ in range(iterations)]
return pd.Series(predictions).mode()[0]
# Determine the most predicted father for each offspring
most_predicted_fathers = {
'Dreamfyre': collect_predictions(dreamfyre_traits),
'Silverwing': collect_predictions(silverwing_traits)
}
# Update color_df with the predicted fathers and their traits
for offspring, father in most_predicted_fathers.items():
color_df.loc[color_df['Name'] == offspring, 'Father'] = father
father_traits = potential_fathers[potential_fathers['Name'] == father].iloc[0]
color_df.loc[color_df['Name'] == offspring, 'ScaleColorGroup_Father'] = father_traits['ScaleColorGroup']
color_df.loc[color_df['Name'] == offspring, 'AccentColorGroup_Father'] = father_traits['AccentColorGroup']
color_df.loc[color_df['Name'] == offspring, 'EyeColorGroup_Father'] = father_traits['EyeColorGroup']
mother_traits = quicksilver_traits
color_df.loc[color_df['Name'] == offspring, 'ScaleColorGroup_Mother'] = mother_traits['ScaleColorGroup']
color_df.loc[color_df['Name'] == offspring, 'AccentColorGroup_Mother'] = mother_traits['AccentColorGroup']
color_df.loc[color_df['Name'] == offspring, 'EyeColorGroup_Mother'] = mother_traits['EyeColorGroup']
# Show the results for Dreamfyre and Silverwing
dreamfyre_silverwing_result = color_df[color_df['Name'].isin(['Dreamfyre', 'Silverwing'])][['Name', 'Mother', 'Father']]
Name | Mother | Father |
---|---|---|
Dreamfyre | Quicksilver | Cannibal |
Silverwing | Quicksilver | Cannibal |
The Paternity of Seasmoke¶
Based on timeline and ages, the only male dragons of the appropriate age and in the same general location as Meleys would've been Balerion, Caraxes, Cannibal, Vermithor, and Sheepstealer. Seasmoke was hatched around 97 AC. With that in mind, I think the only possible fathers could be either Cannibal or Sheepstealer. Here's why:
- Caraxes was in the Stepstones with his rider Daemon Targaryen from 96 AC - 99 AC. and Meleys would've been in Driftmark where her rider, Princess Rhaenys Targaryen, resided with her husband, Lord Corlys Velaryon. So Caraxes wouldn't have been around Meleys at all.
- Vermithor resided in the Dragonpit while Meleys was in Driftmark. Like Caraxes, lack of proximity excludes Vermithor from being a potential father to Seasmoke.
- Balerion died in 94 AC, before Seasmoke's egg was taken and given as a cradle egg to Princess Rhaenys' son, Ser Laenor Velaryon in 97 AC.
- Seasmoke is described as "nimble in the air", which is more in-line with Sheepstealer's description as a "slender mud brown dragon" This direct quote also places Sheepstealer in Driftmark with Meleys:
"In Fire & Blood, Sheepstealer was a slender mud brown dragon with a taste for mutton. He hatched sometime during the youth of Jaehaerys I Targaryen and resided within the Dragonmont, hunting sheep as far as Driftmark and the Wendwater."
We will use Sheepstealer and Cannibal as viable options for paternity for Seasmoke
# Define the traits of Seasmoke, Meleys, and potential fathers
seasmoke_traits = {'ScaleColorGroup': 'Silver', 'AccentColorGroup': 'Pink', 'EyeColorGroup': 'Blue'}
meleys_traits = {'ScaleColorGroup': 'Red', 'AccentColorGroup': 'Copper', 'EyeColorGroup': 'Copper'}
potential_fathers = pd.DataFrame({
'Name': ['Sheepstealer', 'Cannibal'],
'ScaleColorGroup': ['Bronze', 'Black'],
'AccentColorGroup': ['Bronze', 'Black'],
'EyeColorGroup': ['Copper', 'Green']
})
# Show the results for Seasmoke
seasmoke_result = color_df[color_df['Name'].isin(['Seasmoke'])][['Name', 'Mother', 'Father']]
# Define the traits of Seasmoke, Meleys, and potential fathers
seasmoke_traits = {'ScaleColorGroup': 'Silver', 'AccentColorGroup': 'Pink', 'EyeColorGroup': 'Blue'}
meleys_traits = {'ScaleColorGroup': 'Red', 'AccentColorGroup': 'Copper', 'EyeColorGroup': 'Copper'}
potential_fathers = pd.DataFrame({
'Name': ['Sheepstealer', 'Cannibal'],
'ScaleColorGroup': ['Bronze', 'Black'],
'AccentColorGroup': ['Bronze', 'Black'],
'EyeColorGroup': ['Copper', 'Green']
})
# Grandparent traits data
grandparent_traits = {
'Meleys': {'Father': {'ScaleColorGroup': 'Bronze', 'AccentColorGroup': 'Bronze', 'EyeColorGroup': 'Copper'},
'Mother': {'ScaleColorGroup': 'Blue', 'AccentColorGroup': 'Silver', 'EyeColorGroup': 'Blue'}},
'Sheepstealer': {'Father': {'ScaleColorGroup': 'Black', 'AccentColorGroup': 'Black', 'EyeColorGroup': 'Red'},
'Mother': {'ScaleColorGroup': 'Bronze', 'AccentColorGroup': 'Green', 'EyeColorGroup': 'Green'}},
'Cannibal': {'Father': {'ScaleColorGroup': 'Black', 'AccentColorGroup': 'Black', 'EyeColorGroup': 'Red'},
'Mother': {'ScaleColorGroup': 'Bronze', 'AccentColorGroup': 'Green', 'EyeColorGroup': 'Green'}}
}
# Dominance information
scale_dominance = scale_dominance_info
accent_dominance = accent_dominance_info
eye_dominance = eye_dominance_info
# Combine unique values from potential fathers and Seasmoke
combined_values = {
'ScaleColorGroup': list(set(potential_fathers['ScaleColorGroup'].unique()).union(['Silver'])),
'AccentColorGroup': list(set(potential_fathers['AccentColorGroup'].unique()).union(['Pink'])),
'EyeColorGroup': list(set(potential_fathers['EyeColorGroup'].unique()).union(['Blue']))
}
# Encode categorical traits into numerical values
label_encoders = {col: LabelEncoder().fit(combined_values[col]) for col in combined_values}
# Transform the traits of potential fathers
encoded_fathers = potential_fathers.copy()
for col, le in label_encoders.items():
encoded_fathers[col] = le.transform(potential_fathers[col])
# Prepare feature matrix (X) and target vector (y)
X = encoded_fathers[['ScaleColorGroup', 'AccentColorGroup', 'EyeColorGroup']]
y = encoded_fathers['Name']
# Function to fit the model and predict using Random Forest
def fit_and_predict_rf(traits):
clf = RandomForestClassifier(random_state=42)
clf.fit(X, y)
encoded_traits = {col: le.transform([traits[col]])[0] for col, le in label_encoders.items()}
traits_features = pd.DataFrame([encoded_traits])
return clf.predict(traits_features)[0]
# Fit the Random Forest model multiple times and collect predictions
def collect_predictions(traits, iterations=100):
predictions = [fit_and_predict_rf(traits) for _ in range(iterations)]
return pd.Series(predictions).mode()[0]
# Determine the most predicted father for Seasmoke
most_predicted_father = collect_predictions(seasmoke_traits)
# Update color_df with the predicted father and traits for Seasmoke
color_df.loc[color_df['Name'] == 'Seasmoke', 'Father'] = most_predicted_father
father_traits = potential_fathers[potential_fathers['Name'] == most_predicted_father].iloc[0]
color_df.loc[color_df['Name'] == 'Seasmoke', 'ScaleColorGroup_Father'] = father_traits['ScaleColorGroup']
color_df.loc[color_df['Name'] == 'Seasmoke', 'AccentColorGroup_Father'] = father_traits['AccentColorGroup']
color_df.loc[color_df['Name'] == 'Seasmoke', 'EyeColorGroup_Father'] = father_traits['EyeColorGroup']
mother_traits = meleys_traits
color_df.loc[color_df['Name'] == 'Seasmoke', 'ScaleColorGroup_Mother'] = mother_traits['ScaleColorGroup']
color_df.loc[color_df['Name'] == 'Seasmoke', 'AccentColorGroup_Mother'] = mother_traits['AccentColorGroup']
color_df.loc[color_df['Name'] == 'Seasmoke', 'EyeColorGroup_Mother'] = mother_traits['EyeColorGroup']
# Show the results for Seasmoke
seasmoke_result = color_df[color_df['Name'].isin(['Seasmoke'])][['Name', 'Mother', 'Father']]
Name | Mother | Father |
---|---|---|
Seasmoke | Meleys | Sheepstealer |
The Paternity of Shrykos and Morghul¶
Tessarion was between Oldtown, where her rider Daeron Targaryen was sent to be a squire for his uncle Lord Hobert Hightower, and King's Landing where she stayed in the Dragonpit. The best fit in terms of proximity to Tessarion, age, and timeline are either Vermax, Grey Ghost, Sheepstealer, Cannibal, or Sunfyre.
My bets are on Sunfyre being the father of Shrykos and Morghul. Here's why:
- Sunfyre was the only male dragon of a reasonably sexually mature age residing in the dragonpit on Kings Landing between 110 AC and 123 AC.
- With Vermax being split between Winterfell and Dragonstone, it's highly unlikely Vermax and Tessarion would have been anywhere near each other in terms of location and timelines. It's also possible that Vermax might be a She-Dragon. It's rumored that while in Winterfell, Vermax laid a clutch of eggs that never hatched because of the cold, Northern snow.
"...Mushroom claims in his Testimony that the dragon Vermax left a clutch of eggs somewhere in the depths of Winterfell’s crypts, where the waters of the hot springs run close to the walls, while his rider treated with Cregan Stark at the start of the Dance of the Dragons."
-The World of Ice & Fire, p. 332
- Grey Ghost, Sheepstealer, and Cannibal were wild dragons while Tessarion resided in the Dragonpit. It is unlikely any of them are the potential father. Sheepstealer and Cannibal were known to stay between Dragonstone & Driftmark, nowhere near Tessarion. This makes Grey Ghost the only remaining option, even though I still think it is unlikely.
So, we will use Grey Ghost and Sunfyre as potential fathers to Shrykos and Morgul.
# Define the traits of Stormcloud, Morghul, Tessarion, and potential fathers
shrykos_traits = {'ScaleColorGroup': 'Green', 'AccentColorGroup': 'Copper', 'EyeColorGroup': 'Copper'}
morghul_traits = {'ScaleColorGroup': 'Black', 'AccentColorGroup': 'Black', 'EyeColorGroup': 'Red'}
tessarion_traits = {'ScaleColorGroup': 'Blue', 'AccentColorGroup': 'Copper', 'EyeColorGroup': 'Copper'}
potential_fathers = pd.DataFrame({
'Name': ['Grey Ghost', 'Sunfyre'],
'ScaleColorGroup': ['White', 'Gold'],
'AccentColorGroup': ['White', 'Pink'],
'EyeColorGroup': ['Green', 'Green']
})
# Show the results for Stormcloud and Morghul
shrykos_morghul_result = color_df[color_df['Name'].isin(['Shrykos', 'Morghul'])][['Name', 'Mother', 'Father']]
# Define the traits of Shrykos, Morghul, Tessarion, and potential fathers
shrykos_traits = {'ScaleColorGroup': 'Green', 'AccentColorGroup': 'Copper', 'EyeColorGroup': 'Copper'}
morghul_traits = {'ScaleColorGroup': 'Black', 'AccentColorGroup': 'Black', 'EyeColorGroup': 'Red'}
tessarion_traits = {'ScaleColorGroup': 'Blue', 'AccentColorGroup': 'Copper', 'EyeColorGroup': 'Copper'}
potential_fathers = pd.DataFrame({
'Name': ['Grey Ghost', 'Sunfyre'],
'ScaleColorGroup': ['White', 'Gold'],
'AccentColorGroup': ['White', 'Pink'],
'EyeColorGroup': ['Green', 'Green']
})
# Grandparent traits data
grandparent_traits = {
'Tessarion': {'Father': {'ScaleColorGroup': 'Bronze', 'AccentColorGroup': 'Bronze', 'EyeColorGroup': 'Copper'},
'Mother': {'ScaleColorGroup': 'Blue', 'AccentColorGroup': 'Silver', 'EyeColorGroup': 'Blue'}},
'Grey Ghost': {'Father': {'ScaleColorGroup': 'Bronze', 'AccentColorGroup': 'Bronze', 'EyeColorGroup': 'Copper'},
'Mother': {'ScaleColorGroup': 'Silver', 'AccentColorGroup': 'Silver', 'EyeColorGroup': 'Blue'}},
'Sunfyre': {'Father': {'ScaleColorGroup': 'Bronze', 'AccentColorGroup': 'Bronze', 'EyeColorGroup': 'Copper'},
'Mother': {'ScaleColorGroup': 'Silver', 'AccentColorGroup': 'Silver', 'EyeColorGroup': 'Blue'}},
}
# Dominance information
scale_dominance = scale_dominance_info
accent_dominance = accent_dominance_info
eye_dominance = eye_dominance_info
# Combine values from potential fathers and offspring traits
combined_values = {
'ScaleColorGroup': list(set(potential_fathers['ScaleColorGroup'].unique()).union(['Green', 'Black'])),
'AccentColorGroup': list(set(potential_fathers['AccentColorGroup'].unique()).union(['Copper', 'Black'])),
'EyeColorGroup': list(set(potential_fathers['EyeColorGroup'].unique()).union(['Copper', 'Red']))
}
# Encode categorical traits into numerical values
label_encoders = {col: LabelEncoder().fit(combined_values[col]) for col in combined_values}
# Transform the traits of potential fathers
encoded_fathers = potential_fathers.copy()
for col, le in label_encoders.items():
encoded_fathers[col] = le.transform(potential_fathers[col])
# Prepare feature matrix (X) and target vector (y)
X = encoded_fathers[['ScaleColorGroup', 'AccentColorGroup', 'EyeColorGroup']]
y = encoded_fathers['Name']
# Function to fit the model and predict using Random Forest
def fit_and_predict_rf(traits):
clf = RandomForestClassifier(random_state=42)
clf.fit(X, y)
encoded_traits = {col: le.transform([traits[col]])[0] for col, le in label_encoders.items()}
traits_features = pd.DataFrame([encoded_traits])
return clf.predict(traits_features)[0]
# Fit the Random Forest model multiple times and collect predictions
def collect_predictions(traits, iterations=100):
predictions = [fit_and_predict_rf(traits) for _ in range(iterations)]
return pd.Series(predictions).mode()[0]
# Determine the most predicted father for each offspring
most_predicted_fathers = {
'Shrykos': collect_predictions(shrykos_traits),
'Morghul': collect_predictions(morghul_traits)
}
# Update color_df with the predicted fathers and their traits
for offspring, father in most_predicted_fathers.items():
color_df.loc[color_df['Name'] == offspring, 'Father'] = father
father_traits = potential_fathers[potential_fathers['Name'] == father].iloc[0]
color_df.loc[color_df['Name'] == offspring, 'ScaleColorGroup_Father'] = father_traits['ScaleColorGroup']
color_df.loc[color_df['Name'] == offspring, 'AccentColorGroup_Father'] = father_traits['AccentColorGroup']
color_df.loc[color_df['Name'] == offspring, 'EyeColorGroup_Father'] = father_traits['EyeColorGroup']
mother_traits = tessarion_traits
color_df.loc[color_df['Name'] == offspring, 'ScaleColorGroup_Mother'] = mother_traits['ScaleColorGroup']
color_df.loc[color_df['Name'] == offspring, 'AccentColorGroup_Mother'] = mother_traits['AccentColorGroup']
color_df.loc[color_df['Name'] == offspring, 'EyeColorGroup_Mother'] = mother_traits['EyeColorGroup']
# Show the results for Stormcloud and Morghul
shrykos_morghul_result = color_df[color_df['Name'].isin(['Shrykos', 'Morghul'])][['Name', 'Mother', 'Father']]
Name | Mother | Father |
---|---|---|
Morghul | Tessarion | Sunfyre |
Shrykos | Tessarion | Sunfyre |
Dragon Paternity Results¶
Name | Mother | Father |
---|---|---|
Arrax | Syrax | Caraxes |
Cannibal | Vhagar | Balerion |
Caraxes | Dreamfyre | Vermithor |
Dreamfyre | Quicksilver | Cannibal |
Grey Ghost | Silverwing | Vermithor |
Meleys | Dreamfyre | Vermithor |
Moondancer | Syrax | Caraxes |
Morghul | Tessarion | Sunfyre |
Morning | Syrax | Caraxes |
Quicksilver | Meraxes | Balerion |
Seasmoke | Meleys | Sheepstealer |
Sheepstealer | Vhagar | Balerion |
Shrykos | Tessarion | Sunfyre |
Silverwing | Quicksilver | Cannibal |
Stormcloud | Syrax | Caraxes |
Sunfyre | Silverwing | Vermithor |
Syrax | Silverwing | Vermithor |
Tessarion | Dreamfyre | Vermithor |
Tyraxes | Syrax | Caraxes |
Vermax | Syrax | Caraxes |
Vermithor | Vhagar | Balerion |
There are some dragons in the dataset whose appearances are only vaguely described, if mentioned at all, in Fire & Blood, The World of Ice & Fire, or The Rise of the Dragon. For example: Shrykos, Morghul, Stormcloud, Vermax, and Tyraxes. Now that we've established the likely parents of each dragon, I'm curious to see if we can use that information to also predict the most likely scale, accent, and eye colors for the dragons without specific descriptions in the books.
These are the exact steps we'll take:
1) Use hex_to_rgb
and rgb_to_hex
functions for color conversions.
# Step 1: Helper functions for color conversions
def hex_to_rgb(hex_code):
return np.array([int(hex_code[i:i+2], 16) for i in (1, 3, 5)]) # Skip '#' and convert
def rgb_to_hex(rgb):
clipped_rgb = np.clip(rgb, 0, 255)
return '#{:02x}{:02x}{:02x}'.format(int(clipped_rgb[0]), int(clipped_rgb[1]), int(clipped_rgb[2]))
2) Prepare the feature vector from ancestor hex codes.
# Step 2: Prepare the feature vector from ancestor hex codes
def prepare_features(parent_hex1, parent_hex2, grandparents_hex, great_grandparents_hex):
features = {}
for feature in parent_hex1:
parent_rgb1 = hex_to_rgb(parent_hex1[feature])
parent_rgb2 = hex_to_rgb(parent_hex2[feature])
grandparents_rgb = [hex_to_rgb(grandparents_hex[parent][feature]) for parent in ['Father', 'Mother1', 'Mother2']]
great_grandparents_rgb = [hex_to_rgb(great_grandparents_hex[grandparent][feature]) for grandparent in ['Great_Father', 'Great_Mother', 'Great_Father2', 'Great_Mother2']]
feature_vector = np.concatenate([parent_rgb1, parent_rgb2] + grandparents_rgb + great_grandparents_rgb)
features[feature] = feature_vector
return features
3) Blend RGB values from ancestors with some noise to simulate genetic variations.
- The weights
weights = np.array([0.2, 0.2, 0.1, 0.1, 0.1, 0.1, 0.05, 0.05, 0.05, 0.05])
correspond to each ancestor's influence on the color. They determine how much influence each dragon has on the blended RGB value when predicting the offspring's color characteristics.4) Create functions that blend the results to predict the colors for each trait.# Step 3: Blend RGB values from ancestors with some noise def blend_colors(*args): weights = np.array([0.2, 0.2, 0.1, 0.1, 0.1, 0.1, 0.05, 0.05, 0.05, 0.05]) # Adjusted weights for parents, grandparents, and great-grandparents blended_rgb = sum(weight * color for weight, color in zip(weights, args)) blended_rgb += np.random.normal(0, 29, blended_rgb.shape) # Add some noise return rgb_to_hex(np.clip(blended_rgb, 0, 255))
# Step 4: Predict colors for each feature group for each offspring separately. def predict_offspring_colors(parent_hex1, parent_hex2, grandparents_hex, great_grandparents_hex): predicted_colors = {} for feature in parent_hex1: parent_color1 = parent_hex1[feature] parent_color2 = parent_hex2[feature] grandparent_colors = [grandparents_hex[parent][feature] for parent in ['Father', 'Mother1', 'Mother2']] great_grandparent_colors = [great_grandparents_hex[grandparent][feature] for grandparent in ['Great_Father', 'Great_Mother', 'Great_Father2', 'Great_Mother2']] predicted_colors[feature] = blend_colors(hex_to_rgb(parent_color1), hex_to_rgb(parent_color2), *[hex_to_rgb(color) for color in grandparent_colors + great_grandparent_colors]) return predicted_colors
What Color are Shrykos and Morghul?¶
Shrykos and Morghul's appearance aren't stated in any canon source material. Aside from how they die, not much is known about these two dragons. What we do know is that:
- Shrykos was bonded to Jaehaerys and Morghul to Jaehaera, They were the twin son and daughter of King Aegon II and Queen Helaena Targaryen.
- "Morghul" means "death" in High Valyrian. So we can probably assume he has darker, more sinister tones.
- Shrykos is a young She-Dragon whose dragon-egg was a green color.
The colors I've used in the dataset are based on the illustrations of them in The Rise of the Dragon, shown below.
"The Rise of the Dragon", p. 257, Morghul: Knowing that Dreamfyre is that light blue colored dragon in the top lefthand corner of the image, we can assume that this is Morghul depicted as entirely greyish with bright red eyes.
"The Rise of the Dragon", p. 258, Shrykos: It's written the "Tyraxes, older than the others, was more cunning, retreating to his lair and forcing his would-be killers to approach through the corpse-filled entrance..." (Rise of the Dragon, p. 256). With Morghul and Dreamfyre being accounted for, that leaves Shrykos as the only possible dragon in this image. We can see Shrykos is shown with an emerald green coloring.
The scale, accents, and eye colors I used for Shrykos and Morghul are:
Name | ScaleColor | WingMembraneColor | EyeColor |
---|---|---|---|
Morghul | Dark Grey | Dark Grey | Red |
Shrykos | Jade Green | Copper | Copper |
Because of the illustration and the meaning or Morghul's name, I'm fairly confident in the colors I already have for him.
I'm confident in Shyrkos' coloring as well, but because the picture has a bit of an overlay, I'll be validating my color choices by calculating her coloring based off of the color traits within her lineage and seeing if they align well.
# Color data
data = [
['Arrax', '#F4EFE6', '#E5B347', '#E5B347', 'Syrax', 'Seasmoke'],
['Caraxes', '#C50016', '#C50016', '#F5D21E', 'Dreamfyre', 'Vermithor'],
['Cannibal', '#181818', '#181818', '#59A630', 'Vhagar', 'Balerion'],
['Dreamfyre', '#2F37A8', '#bcc6cc', '#A6C3DC', 'Quicksilver', 'Cannibal'],
['Meleys', '#B22A30', '#B87333', '#B87333', 'Dreamfyre', 'Vermithor'],
['Moondancer', '#8A9C74', '#F4EFE6', '#5E7B36', 'Syrax', 'Caraxes'],
['Morghul', '#2F2F2F', '#2F2F2F', '#B83A36', 'Tessarion', 'Sunfyre'],
['Morning', '#CC9E9B', '#181818', '#B83A36', 'Syrax', 'Caraxes'],
['Seasmoke', '#DADADA', '#CC9E9B', '#A6C3DC', 'Meleys', 'Sheepstealer'],
['Sheepstealer', '#4A3931', '#4A3931', '#C48C55', 'Vhagar', 'Balerion'],
['Shrykos', '#033537', '#B87333', '#B87333', 'Tessarion', 'Sunfyre'],
['Silverwing', '#bcc6cc', '#bcc6cc', '#A6C3DC', 'Quicksilver', 'Cannibal'],
['Stormcloud', '#354669', '#2F2F2F', '#A6C3DC', 'Syrax', 'Seasmoke'],
['Sunfyre', '#E5B347', '#CC9E9B', '#5E7B36', 'Silverwing', 'Vermithor'],
['Syrax', '#F5D21E', '#F5D21E', '#5E7B36', 'Silverwing', 'Vermithor'],
['Tessarion', '#2F37A8', '#B87333', '#B87333', 'Dreamfyre', 'Vermithor'],
['Tyraxes', '#181818', '#C50016', '#B83A36', 'Syrax', 'Caraxes'],
['Vermax', '#929151', '#F18763', '#F5D21E', 'Syrax', 'Caraxes'],
['Vermithor', '#A77F5B', '#C56731', '#C48C55', 'Vhagar', 'Balerion'],
['Vhagar', '#9A835A', '#4F635A', '#5E7B36', 'Unknown', 'NaN'],
['Balerion', '#181818', '#181818', '#B83A36', 'Unknown', 'NaN'],
['Meraxes', '#D3D4DB', '#CC9E9B', '#E5B347', 'Unknown', 'NaN'],
['Quicksilver', '#F3ECEA', '#E5B347', '#E5B347', 'Meraxes', 'Balerion'],
['Grey Ghost', '#F1F1F1', '#F1F1F1', '#59A630', 'Silverwing', 'Vermithor']
]
# Convert the data into a DataFrame
df = pd.DataFrame(data, columns=['Name', 'HexCode_Scales', 'HexCode_Accents', 'HexCode_Eyes', 'Mother', 'Father'])
# Sort the DataFrame by the 'Name' column
df_sorted = df.sort_values(by='Name').reset_index(drop=True)
# Function to convert hex to RGB
def hex_to_rgb(hex_code):
hex_code = hex_code.lstrip('#')
return [int(hex_code[i:i+2], 16) for i in (0, 2, 4)]
# Convert hex codes to RGB values after sorting
df_sorted['Scales_RGB'] = df_sorted['HexCode_Scales'].apply(hex_to_rgb)
df_sorted['Accents_RGB'] = df_sorted['HexCode_Accents'].apply(hex_to_rgb)
df_sorted['Eyes_RGB'] = df_sorted['HexCode_Eyes'].apply(hex_to_rgb)
# Helper functions for color conversions
def hex_to_rgb(hex_code):
return np.array([int(hex_code[i:i+2], 16) for i in (1, 3, 5)]) # Skip '#' and convert
def rgb_to_hex(rgb):
clipped_rgb = np.clip(rgb, 0, 255)
return '#{:02x}{:02x}{:02x}'.format(int(clipped_rgb[0]), int(clipped_rgb[1]), int(clipped_rgb[2]))
# Prepare the feature vector from ancestor hex codes
def prepare_features(parent_hex1, parent_hex2, grandparents_hex, great_grandparents_hex):
features = {}
for feature in parent_hex1:
parent_rgb1 = hex_to_rgb(parent_hex1[feature])
parent_rgb2 = hex_to_rgb(parent_hex2[feature])
grandparents_rgb = [hex_to_rgb(grandparents_hex[parent][feature]) for parent in ['Father', 'Mother1', 'Mother2']]
great_grandparents_rgb = [hex_to_rgb(great_grandparents_hex[grandparent][feature]) for grandparent in ['Great_Father', 'Great_Mother', 'Great_Father2', 'Great_Mother2']]
feature_vector = np.concatenate([parent_rgb1, parent_rgb2] + grandparents_rgb + great_grandparents_rgb)
features[feature] = feature_vector
return features
# Blend RGB values from ancestors with some noise
def blend_colors(*args):
weights = np.array([0.2, 0.2, 0.1, 0.1, 0.1, 0.1, 0.05, 0.05, 0.05, 0.05]) # Adjusted weights for parents, grandparents, and great-grandparents
blended_rgb = sum(weight * color for weight, color in zip(weights, args))
blended_rgb += np.random.normal(0, 29, blended_rgb.shape) # Add some noise
return rgb_to_hex(np.clip(blended_rgb, 0, 255))
# Predict colors for each feature group for each offspring separately
def predict_offspring_colors(parent_hex1, parent_hex2, grandparents_hex, great_grandparents_hex):
predicted_colors = {}
for feature in parent_hex1:
parent_color1 = parent_hex1[feature]
parent_color2 = parent_hex2[feature]
grandparent_colors = [grandparents_hex[parent][feature] for parent in ['Father', 'Mother1', 'Mother2']]
great_grandparent_colors = [great_grandparents_hex[grandparent][feature] for grandparent in ['Great_Father', 'Great_Mother', 'Great_Father2', 'Great_Mother2']]
predicted_colors[feature] = blend_colors(hex_to_rgb(parent_color1), hex_to_rgb(parent_color2), *[hex_to_rgb(color) for color in grandparent_colors + great_grandparent_colors])
return predicted_colors
# Lineage data
lineage_data = {
'Vermithor': {'HexCode_Scales': '#A77F5B', 'HexCode_Accents': '#C56731', 'HexCode_Eyes': '#C48C55'},
'Dreamfyre': {'HexCode_Scales': '#7A95C8', 'HexCode_Accents': '#bcc6cc', 'HexCode_Eyes': '#A6C3DC'},
'Silverwing': {'HexCode_Scales': '#bcc6cc', 'HexCode_Accents': '#bcc6cc', 'HexCode_Eyes': '#A6C3DC'},
'Cannibal': {'HexCode_Scales': '#181818', 'HexCode_Accents': '#181818', 'HexCode_Eyes': '#59A630'},
'Quicksilver': {'HexCode_Scales': '#DADADA', 'HexCode_Accents': '#E5B347', 'HexCode_Eyes': '#E5B347'},
'Vhagar': {'HexCode_Scales': '#9A835A', 'HexCode_Accents': '#4F635A', 'HexCode_Eyes': '#5E7B36'},
'Balerion': {'HexCode_Scales': '#181818', 'HexCode_Accents': '#181818', 'HexCode_Eyes': '#B83A36'}
}
# Predict colors for Shrykos
predicted_colors_shrykos = predict_offspring_colors(
{'HexCode_Scales': '#2F37A8', 'HexCode_Accents': '#B87333', 'HexCode_Eyes': '#B87333'}, # Tessarion
{'HexCode_Scales': '#E5B347', 'HexCode_Accents': '#CC9E9B', 'HexCode_Eyes': '#5E7B36'}, # Sunfyre
{'Father': lineage_data['Vermithor'], 'Mother1': lineage_data['Dreamfyre'], 'Mother2': lineage_data['Silverwing']},
{'Great_Father': lineage_data['Cannibal'], 'Great_Mother': lineage_data['Quicksilver'], 'Great_Father2': lineage_data['Balerion'], 'Great_Mother2': lineage_data['Vhagar']}
)
print("Predicted colors for Shrykos:", predicted_colors_shrykos)
Predicted colors for Shrykos: {'HexCode_Scales': '#4A7F80', 'HexCode_Accents': '#AB772D', 'HexCode_Eyes': '#88BB37'}
# Lineage data
lineage_data = {
'Vermithor': {'HexCode_Scales': '#A77F5B', 'HexCode_Accents': '#C56731', 'HexCode_Eyes': '#C48C55'}, # Grandfather
'Dreamfyre': {'HexCode_Scales': '#7A95C8', 'HexCode_Accents': '#bcc6cc', 'HexCode_Eyes': '#A6C3DC'}, # Grandmother
'Silverwing': {'HexCode_Scales': '#bcc6cc', 'HexCode_Accents': '#bcc6cc', 'HexCode_Eyes': '#A6C3DC'}, # Grandmother 2
'Cannibal': {'HexCode_Scales': '#181818', 'HexCode_Accents': '#181818', 'HexCode_Eyes': '#59A630'}, # Great Grandfather 1
'Quicksilver': {'HexCode_Scales': '#DADADA', 'HexCode_Accents': '#E5B347', 'HexCode_Eyes': '#E5B347'}, # Great Grandmother 1
'Vhagar': {'HexCode_Scales': '#9A835A', 'HexCode_Accents': '#4F635A', 'HexCode_Eyes': '#5E7B36'}, # Great Grandmother 2
'Balerion': {'HexCode_Scales': '#181818', 'HexCode_Accents': '#181818', 'HexCode_Eyes': '#B83A36'} # Great Grandfather 2
}
# Predict colors for Shrykos
predicted_colors_shrykos = predict_offspring_colors(
{'HexCode_Scales': '#2F37A8', 'HexCode_Accents': '#B87333', 'HexCode_Eyes': '#B87333'}, # Tessarion
{'HexCode_Scales': '#E5B347', 'HexCode_Accents': '#CC9E9B', 'HexCode_Eyes': '#5E7B36'}, # Sunfyre
{'Father': lineage_data['Vermithor'], 'Mother1': lineage_data['Dreamfyre'], 'Mother2': lineage_data['Silverwing']},
{'Great_Father': lineage_data['Cannibal'], 'Great_Mother': lineage_data['Quicksilver'], 'Great_Father2': lineage_data['Balerion'], 'Great_Mother2': lineage_data['Vhagar']}
)
We can see that the model's predicition and the current colors are very close color matches. The model's prediction of green eyes makes more sense, considering that green eyes are dominant and Shrykos' father, Sunfyre, has green eyes. I will be keeping the Jade Green and Copper accents, but updating Shrykos' EyeColor
from "Copper" to "Green".
tyraxes_vermax_stormcloud = color_df[color_df['Name'].isin(['Tyraxes', 'Vermax', 'Stormcloud'])][['Name', 'ScaleColor', 'WingMembraneColor', 'EyeColor']]
What Color are Tyraxes, Vermax, and Stormcloud?¶
Like Shrykos and Morghul, not much, if anything at all, is said about the colors of Tyraxes, Vermax, and Stormcloud.
Name | ScaleColor | WingMembraneColor | EyeColor |
---|---|---|---|
Stormcloud | Dark Grey-Blue | Dark Grey | Bright Blue |
Tyraxes | Black | Red | Red |
Vermax | Olive Green | Pale Orange | Yellow |
The colors in the dataset are based on the following:
- Stormcloud: Because of the name, I went with dark greys and blues and blue eyes.
- Tyraxes: I've noticed a trend in dragons with black and red color combinations being known for their aggressiveness (Balerion, Drogon, Morghul...). With the quote from the previous section mentioning Tyraxes' particularly cunning and aggressive behavior, I felt this coloring made the most sense. Especially with Caraxes, his father, being red himself.
- Vermax: The illustration of Vermax (seen below) in The Rise of the Dragon, p. 228 isn't a full-body drawing, but does show Vermax's underside being an orangish color and the scale a green color.
There is also evidence that the color of a dragon's flame correlate with the colors on their body. We know that Silverwing's flames were blue, and Vermithor's were gold. Leaving Vermax's flames as the orange or red flames seen during the Battle of the Gullet.
We can assume that Stormcloud takes on more of a the grey-ish blue tones, so we will reduce the weight of Vermithor's bronze and green traits in the blending process.
# Blend RGB values from ancestors with some noise
def blend_colors(*args):
weights = np.array([0.05, 0.05, 0.025, 0.2, 0.2, 0.15, 0.05, 0.15, 0.05]) # Adjusted weights for parents, grandparents, and great-grandparents
blended_rgb = sum(weight * color for weight, color in zip(weights, args))
blended_rgb += np.random.normal(0, 20, blended_rgb.shape) # Add some noise
return rgb_to_hex(np.clip(blended_rgb, 0, 255))
# Helper functions for color conversions
def hex_to_rgb(hex_code):
return np.array([int(hex_code[i:i+2], 16) for i in (1, 3, 5)]) # Skip '#' and convert
def rgb_to_hex(rgb):
clipped_rgb = np.clip(rgb, 0, 255)
return '#{:02x}{:02x}{:02x}'.format(int(clipped_rgb[0]), int(clipped_rgb[1]), int(clipped_rgb[2]))
# Prepare the feature vector from ancestor hex codes
def prepare_features(parent_hex1, parent_hex2, grandparents_hex, great_grandparents_hex):
features = {}
for feature in parent_hex1:
parent_rgb1 = hex_to_rgb(parent_hex1[feature])
parent_rgb2 = hex_to_rgb(parent_hex2[feature])
grandparents_rgb = [hex_to_rgb(grandparents_hex[parent][feature]) for parent in ['Father', 'Mother1', 'Mother2']]
great_grandparents_rgb = [hex_to_rgb(great_grandparents_hex[grandparent][feature]) for grandparent in ['Great_Father', 'Great_Mother', 'Great_Father2', 'Great_Mother2']]
feature_vector = np.concatenate([parent_rgb1, parent_rgb2] + grandparents_rgb + great_grandparents_rgb)
features[feature] = feature_vector
return features
# Blend RGB values from ancestors with some noise
def blend_colors(*args):
weights = np.array([0.05, 0.05, 0.025, 0.2, 0.2, 0.15, 0.05, 0.15, 0.05]) # Adjusted weights for parents, grandparents, and great-grandparents
blended_rgb = sum(weight * color for weight, color in zip(weights, args))
blended_rgb += np.random.normal(0, 20, blended_rgb.shape) # Add some noise
return rgb_to_hex(np.clip(blended_rgb, 0, 255))
# Predict colors for each feature group separately
def predict_offspring_colors(parent_hex1, parent_hex2, grandparents_hex, great_grandparents_hex):
predicted_colors = {}
for feature in parent_hex1:
parent_color1 = parent_hex1[feature]
parent_color2 = parent_hex2[feature]
grandparent_colors = [grandparents_hex[parent][feature] for parent in ['Father', 'Mother1', 'Mother2']]
great_grandparent_colors = [great_grandparents_hex[grandparent][feature] for grandparent in ['Great_Father', 'Great_Mother', 'Great_Father2', 'Great_Mother2']]
predicted_colors[feature] = blend_colors(hex_to_rgb(parent_color1), hex_to_rgb(parent_color2), *[hex_to_rgb(color) for color in grandparent_colors + great_grandparent_colors])
return predicted_colors
# Lineage data
lineage_data = {
'Vermithor': {'HexCode_Scales': '#A77F5B', 'HexCode_Accents': '#C56731', 'HexCode_Eyes': '#C48C55'},
'Dreamfyre': {'HexCode_Scales': '#7A95C8', 'HexCode_Accents': '#bcc6cc', 'HexCode_Eyes': '#A6C3DC'},
'Silverwing': {'HexCode_Scales': '#bcc6cc', 'HexCode_Accents': '#bcc6cc', 'HexCode_Eyes': '#A6C3DC'},
'Cannibal': {'HexCode_Scales': '#181818', 'HexCode_Accents': '#181818', 'HexCode_Eyes': '#59A630'},
'Quicksilver': {'HexCode_Scales': '#DADADA', 'HexCode_Accents': '#E5B347', 'HexCode_Eyes': '#E5B347'},
'Vhagar': {'HexCode_Scales': '#9A835A', 'HexCode_Accents': '#4F635A', 'HexCode_Eyes': '#5E7B36'},
'Balerion': {'HexCode_Scales': '#181818', 'HexCode_Accents': '#181818', 'HexCode_Eyes': '#B83A36'}
}
# Predict colors for Stormcloud
predicted_colors_stormcloud = predict_offspring_colors(
{'HexCode_Scales': '#F5D21E', 'HexCode_Accents': '#F5D21E', 'HexCode_Eyes': '#5E7B36'}, # Syrax
{'HexCode_Scales': '#C50016', 'HexCode_Accents': '#C50016', 'HexCode_Eyes': '#F5D21E'}, # Caraxes
{'Father': lineage_data['Vermithor'], 'Mother1': lineage_data['Dreamfyre'], 'Mother2': lineage_data['Silverwing']},
{'Great_Father': lineage_data['Cannibal'], 'Great_Mother': lineage_data['Quicksilver'], 'Great_Father2': lineage_data['Balerion'], 'Great_Mother2': lineage_data['Vhagar']}
)
print("Predicted colors for Stormcloud:", predicted_colors_stormcloud)
Predicted colors for Stormcloud: {'HexCode_Scales': '#516078', 'HexCode_Accents': '#65737F', 'HexCode_Eyes': '#79ADA8'}
While not exactly the same, the dark bluish-grey tones seem to be spot on. I think the model's predicted accents are more accurate than the accents in the dataset. I'll be changing the accent color groups from dark grey to grey
# Update 'Stormcloud' Colors
dragon_data.loc[dragon_data['Name'] == 'Stormcloud', ['StandardizedWingColor', 'AccentColorGroup']] = 'Grey'
dragon_data.loc[dragon_data['Name'] == 'Stormcloud', ['WingMembraneColor']] = 'Grey'
While not exactly the same, I think the model's predicted scale color is more accurate than the scale color in the dataset. I'll be changing the scale color groups from black to grey
# Update 'Tyraxes' Colors
dragon_data.loc[dragon_data['Name'] == 'Tyraxes', ['StandardizedScaleColor', 'ScaleColorGroup']] = 'Grey'
dragon_data.loc[dragon_data['Name'] == 'Tyraxes', ['ScaleColor']] = 'Dark Grey'
Final Dragon Colors¶
Shyrkos¶
Stormcloud¶
Tyraxes¶
Vermax¶
Below you'll find the final Targaryen Dragon Family Tree, determined through data and lore.
Final Dragon Depictions¶
Below, you'll you see an image carousel updated with the accurate color predictions of the dragons using dominant and recessive traits determined through machine learning.
Team Black Dragons:¶
Team Green Dragons:¶
Final Conclusions¶
This project successfully created a detailed and accurate understanding of the Targaryen dragons and their connections. Every dragon's lineage and traits have been carefully examined, resulting in a narrative that stays true to the source material while adding new insights.
Summary of Key Results:¶
Maternal Lineages:
- We reassigned certain dragons to more fitting maternal figures. Dreamfyre and Silverwing, for example, were assigned as the offpsring Quicksilver rather than Meraxes.
Dominant and Recessive Traits:
- We've established which traits are passed down consistently, revealing patterns such as the dominance of green and copper tones from Vhagar, and the rarity of Balerion's red eyes.
Determining Paternity:
- Using a the dominant & recessive traits in combination with machine learning, we identified likely fathers for several dragons. Dragons like Cannibal and Sheepstealer were found to have played important roles in the family tree.
Predicting Dragon Colors:
- For dragons with little or no description in the books, we predicted their appearance based on lineage data. These predictions align closely with what we know from the lore and existing illustrations.
Final Family Tree:
- The resulting family tree visually represents the connections and traits across generations, providing a clear view of the Targaryen dragons' familial connections.
Acknowledgments & References¶
Unless referenced otherwise, all headers, artwork, and graphics for this project were created by me. Please contact me directly or ensure appropriate credit is given if you use any of them.
The headers were created by myself using Canva.
I created all portraits of the dragons seen throughout the project through two generative AI models that I personally created and trained using Stable Diffusion 3.0 and hosted on OpenArt.AI.
I created the dataset myself, which can be downloaded through the repository here
The information for the dataset was obtained from the following sources and materials:
Books:
- Martin, George R. R. The World of Ice and Fire: The Untold History of Westeros and The Game of Thrones. Bantam Books, 2014, p. 78.
- Martin, George R. R. The Rise of the Dragon: An Illustrated History of the Targaryen Dynasty. Bantam Books, 2022.
- Martin, George R. R. The World of Ice and Fire: The Untold History of Westeros and The Game of Thrones. Bantam Books, 2014, p. 81.
- Martin, George R. R. Fire & Blood: 300 Years Before A Game of Thrones (The Targaryen Dynasty: The House of the Dragon). Bantam Books, 2018.
- Martin, George R. R. The Princess and The Queen. Tor Books, 2013.
Web Sources:
- Martin, George R. R. "Not A Blog." George R. R. Martin, https://georgerrmartin.com/notablog/.
- "Known Dragonriders." A Wiki of Ice and Fire, awoiaf.westeros.org, https://awoiaf.westeros.org/index.php/Dragonrider#Known_dragonriders.
- "Reproduction." A Wiki of Ice and Fire, awoiaf.westeros.org, https://awoiaf.westeros.org/index.php/Dragon#Reproduction.
- "Known Dragons." A Wiki of Ice and Fire, awoiaf.westeros.org, https://awoiaf.westeros.org/index.php/Dragon.
- "Dragon Breeds in Valyria of Old - Were They Bred?" Reddit, 11 Mar. 2023, reddit.com, https://www.reddit.com/r/HouseOfTheDragon/comments/11umayl/dragon_breeds_in_valyria_of_old_were_they_breed/.
- "Sheepstealer." Wiki of Westeros, https://gameofthrones.fandom.com/wiki/Sheepstealer#:~:text=In%20Fire%20%26%20Blood%2C%20Sheepstealer%20was,as%20Driftmark%20and%20the%20Wendwater.
Contact Information¶
- Email: phelpsbp@gmail.com
- LinkedIn: Brittany Phelps
- Github: phelpsbp
!jupyter nbconvert --to html --template pj --embed-images "The Data of Dragons.ipynb"
pip freeze > requirements.txt
Note: you may need to restart the kernel to use updated packages.