✨ Příprava dat¶
Data pocházejí z oficiálního zdroje otevřených dat města Austin.
📦 Import potřebných balíčků.¶
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib as mpl
import matplotlib.pyplot as plt
import missingno as msno
import holoviews as hv
import plotly
import pywaffle as waff
import httpimport
import alluvial
plt.style.use('ggplot')
red = (226/255, 74/255, 51/255)
blue = (52/255, 138/255, 189/255)
📂 Načtení dat z csv
souborů¶
intakes_df = pd.read_csv('intakes.csv')
outcomes_df = pd.read_csv('outcomes.csv')
📊 Seznámení s datasety¶
print('intakes.csv')
display(intakes_df.head(3))
print('outcomes.csv')
display(outcomes_df.head(3))
intakes.csv
Animal ID | Name | DateTime | MonthYear | Found Location | Intake Type | Intake Condition | Animal Type | Sex upon Intake | Age upon Intake | Breed | Color | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | A786884 | *Brock | 01/03/2019 04:19:00 PM | January 2019 | 2501 Magin Meadow Dr in Austin (TX) | Stray | Normal | Dog | Neutered Male | 2 years | Beagle Mix | Tricolor |
1 | A706918 | Belle | 07/05/2015 12:59:00 PM | July 2015 | 9409 Bluegrass Dr in Austin (TX) | Stray | Normal | Dog | Spayed Female | 8 years | English Springer Spaniel | White/Liver |
2 | A724273 | Runster | 04/14/2016 06:43:00 PM | April 2016 | 2818 Palomino Trail in Austin (TX) | Stray | Normal | Dog | Intact Male | 11 months | Basenji Mix | Sable/White |
outcomes.csv
Animal ID | Name | DateTime | MonthYear | Date of Birth | Outcome Type | Outcome Subtype | Animal Type | Sex upon Outcome | Age upon Outcome | Breed | Color | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | A794011 | Chunk | 05/08/2019 06:20:00 PM | May 2019 | 05/02/2017 | Rto-Adopt | NaN | Cat | Neutered Male | 2 years | Domestic Shorthair Mix | Brown Tabby/White |
1 | A776359 | Gizmo | 07/18/2018 04:02:00 PM | Jul 2018 | 07/12/2017 | Adoption | NaN | Dog | Neutered Male | 1 year | Chihuahua Shorthair Mix | White/Brown |
2 | A821648 | NaN | 08/16/2020 11:38:00 AM | Aug 2020 | 08/16/2019 | Euthanasia | NaN | Other | Unknown | 1 year | Raccoon | Gray |
print('intakes.csv:')
intakes_df.info()
print('\noutcomes.csv:')
outcomes_df.info()
intakes.csv: <class 'pandas.core.frame.DataFrame'> RangeIndex: 138585 entries, 0 to 138584 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Animal ID 138585 non-null object 1 Name 97316 non-null object 2 DateTime 138585 non-null object 3 MonthYear 138585 non-null object 4 Found Location 138585 non-null object 5 Intake Type 138585 non-null object 6 Intake Condition 138585 non-null object 7 Animal Type 138585 non-null object 8 Sex upon Intake 138584 non-null object 9 Age upon Intake 138585 non-null object 10 Breed 138585 non-null object 11 Color 138585 non-null object dtypes: object(12) memory usage: 12.7+ MB outcomes.csv: <class 'pandas.core.frame.DataFrame'> RangeIndex: 138769 entries, 0 to 138768 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Animal ID 138769 non-null object 1 Name 97514 non-null object 2 DateTime 138769 non-null object 3 MonthYear 138769 non-null object 4 Date of Birth 138769 non-null object 5 Outcome Type 138746 non-null object 6 Outcome Subtype 63435 non-null object 7 Animal Type 138769 non-null object 8 Sex upon Outcome 138768 non-null object 9 Age upon Outcome 138764 non-null object 10 Breed 138769 non-null object 11 Color 138769 non-null object dtypes: object(12) memory usage: 12.7+ MB
Velikost dat¶
V datasetu intakes je před úpravami 138585 a v outcomes 138769 záznamů.
🔎 Seznam příznaků¶
Oba datasety mají 12 příznaků. Pandas všechny zpracovává jako Object (typ string).
Datasety společně sdílí následující příznaky (v hranatých závorkách jsou datové typy charakteristické pro dané hodnoty):
- Animal ID_- id zvířete
- Name [str] - jméno zvířete
- DateTime [datetm.] - čas vzniku záznamu
- MonthYear [date] - měsíc a rok příchodu (in), odchodu (out)
- Animal Type [cat. nom.] - druh zvířete
- Intake-Outcome Type [cat. nom.] - způsob příchodu (in), odchodu (out)
- Sex upon Intake-Outcome [cat. nom.] - pohlaví zvířete (včetně inf. o kastraci)
- Age upon Intake-Outcome [int.] - věk zvířete v moment příchodu (in), odchodu (out)
- Breed [cat. nom.] - plemeno zvířete
- Color [cat. nom.] - barva zvířete
V datasetu intakes jsou navíc:
- Found Location [cat. nom.] - místo nalezení zvířete
- Intake Condition [cat. nom.] - stav zvířete při příchodu
V datasetu outcomes jsou navíc:
- Date of Birth [date] - datum narození zvířete
- Outcome Subtype [cat. nom.] - podtyp způsobu odchodu
❓ Chybějící hodnoty¶
Z výpisu o datasetech je vidět, že
- v intakes chybí data ve sloupcích [Name, Sex upon Intake]
- v outcomes chybí ve sloupích [Name, Outcome Type, Outcome Subtype, Sex upon Outcome, Sex upon Outcome]
Tohle však ještě nejsou všechna chybějící data (zbytek vyřešíme postupně u zpracování jednotlivých příznaků).
def subplot_missing_values(ax, data, title):
ax.barh(data.index, data)
ax.set_title(title)
ax.set_xlabel('Count')
ax.ticklabel_format(axis='x', style='sci', scilimits=(0,0), useMathText=True)
def subplot_missing_values_matrix(ax, data):
msno.matrix(data, fontsize=10, sparkline=False, ax=ax, color=red)
ax.set_xlabel('Data-density display')
ax.get_yaxis().set_visible(False)
def plot_missing(df1, name1, df2, name2, *, title='Missing value counts (incomplete)'):
fig, axes = plt.subplots(2, 2, figsize=(10, 8), layout='constrained')
fig.suptitle(title, fontsize=16)
subplot_missing_values(axes[0][0], df1.isna().sum(axis=0), name1)
subplot_missing_values(axes[0][1], df2.isna().sum(axis=0), name2)
subplot_missing_values_matrix(axes[1][0], df1)
subplot_missing_values_matrix(axes[1][1], df2)
plot_missing(intakes_df, 'Intakes', outcomes_df, 'Outcomes')
Unikátní hodnoty příznaků¶
print('intakes.csv:')
display(intakes_df.nunique())
print(intakes_df.apply(lambda col: col.unique()))
print('\noutcomes.csv:')
display(outcomes_df.nunique())
print(outcomes_df.apply(lambda col: col.unique()))
intakes.csv:
Animal ID 123890 Name 23544 DateTime 97442 MonthYear 103 Found Location 58367 Intake Type 6 Intake Condition 15 Animal Type 5 Sex upon Intake 5 Age upon Intake 54 Breed 2741 Color 616 dtype: int64
Animal ID [A786884, A706918, A724273, A665644, A682524, ... Name [*Brock, Belle, Runster, nan, Rio, Odin, Beowu... DateTime [01/03/2019 04:19:00 PM, 07/05/2015 12:59:00 P... MonthYear [January 2019, July 2015, April 2016, October ... Found Location [2501 Magin Meadow Dr in Austin (TX), 9409 Blu... Intake Type [Stray, Owner Surrender, Public Assist, Wildli... Intake Condition [Normal, Sick, Injured, Pregnant, Nursing, Age... Animal Type [Dog, Cat, Other, Bird, Livestock] Sex upon Intake [Neutered Male, Spayed Female, Intact Male, In... Age upon Intake [2 years, 8 years, 11 months, 4 weeks, 4 years... Breed [Beagle Mix, English Springer Spaniel, Basenji... Color [Tricolor, White/Liver, Sable/White, Calico, T... dtype: object outcomes.csv:
Animal ID 124068 Name 23425 DateTime 115364 MonthYear 103 Date of Birth 7576 Outcome Type 9 Outcome Subtype 26 Animal Type 5 Sex upon Outcome 5 Age upon Outcome 54 Breed 2749 Color 619 dtype: int64
Animal ID [A794011, A776359, A821648, A720371, A674754, ... Name [Chunk, Gizmo, nan, Moose, Princess, Quentin, ... DateTime [05/08/2019 06:20:00 PM, 07/18/2018 04:02:00 P... MonthYear [May 2019, Jul 2018, Aug 2020, Feb 2016, Mar 2... Date of Birth [05/02/2017, 07/12/2017, 08/16/2019, 10/08/201... Outcome Type [Rto-Adopt, Adoption, Euthanasia, Transfer, Re... Outcome Subtype [nan, Partner, Foster, SCRP, Out State, Suffer... Animal Type [Cat, Dog, Other, Bird, Livestock] Sex upon Outcome [Neutered Male, Unknown, Intact Male, Spayed F... Age upon Outcome [2 years, 1 year, 4 months, 6 days, 7 years, 2... Breed [Domestic Shorthair Mix, Chihuahua Shorthair M... Color [Brown Tabby/White, White/Brown, Gray, Buff, O... dtype: object
Můžeme si všimnout, že počet unikátních Animal ID neodpovídá počtu řádků v příslušných datasetech (některá zvířata se do útulku vrací). Dále z výčtu několika unikátních hodnot ze sloupců Age upon in/out zjišťujeme, že vstupy nějsou konzistentní (míchají se záznamy v letech, měsících and dnech).
🧹 Průzkum a čištění dat¶
print(pd.concat([intakes_df.columns.to_series(), outcomes_df.columns.to_series()]).drop_duplicates().tolist())
['Animal ID', 'Name', 'DateTime', 'MonthYear', 'Found Location', 'Intake Type', 'Intake Condition', 'Animal Type', 'Sex upon Intake', 'Age upon Intake', 'Breed', 'Color', 'Date of Birth', 'Outcome Type', 'Outcome Subtype', 'Sex upon Outcome', 'Age upon Outcome']
Postup
- Ručně projedeme následující příznaky, identifikujeme způsoby, kterými uvádějí chybějící hodnoty.
- Ty následně nahradíme hodnotou NaN.
- Současně transformujeme data tak, aby byly konzistentní společně s ostatními hodnotami (převod na společnou jednotku, stejný formát).
- Dále v průběhu zpracování každého příznaku převedeme sloupce na příslušný typ: category, int, datetime, atd.
- Redundantní příznaky odstraníme, rozsáhlé příznaky roztrhneme.
Napřed se zbavíme všech duplicitních záznamů.
intakes_df = intakes_df.drop_duplicates()
outcomes_df = outcomes_df.drop_duplicates()
print(intakes_df.shape[0], outcomes_df.shape[0])
138565 138752
Po odstranění vidíme, že skutečný počet záznamů v datasetech je 138565 (in) a 138752 (out).
Animal ID¶
print(sum(intakes_df['Animal ID'].str.startswith('A') == False),
sum(outcomes_df['Animal ID'].str.startswith('A') == False))
0 0
Všechny hodnoty ve sloupci Animal ID začínají na "A". ID jsou kozistentní a bez chybějících hodnot.
Name¶
print(intakes_df['Name'].iloc[:10].tolist())
['*Brock', 'Belle', 'Runster', nan, 'Rio', 'Odin', 'Beowulf', '*Ella', 'Mumble', nan]
Některá jména začínají hvězdičkou. Abychom zachovali konzistenci dat tento znak odstraníme.
Dále některé záznamy mají v položce jména duplicitně Animal ID. Tyto hodnoty nahradíme hodnotou NaN.
intakes_df['Name'] = intakes_df.loc[:, 'Name'].str.removeprefix('*')
outcomes_df['Name'] = intakes_df.loc[:, 'Name'].str.removeprefix('*')
mask = intakes_df['Name'] == intakes_df['Animal ID']
intakes_df.loc[mask, 'Name'] = np.nan
DateTime, Date of Birth¶
Příznaky [DateTime, MonthYear] jsou časové hodnoty, proto je převedeme na správný datový typ času (datetime64).
intakes_df['DateTime'] = pd.to_datetime(intakes_df.loc[:, 'DateTime'], format='%m/%d/%Y %H:%M:%S %p')
outcomes_df['DateTime'] = pd.to_datetime(outcomes_df.loc[:, 'DateTime'], format='%m/%d/%Y %H:%M:%S %p')
outcomes_df['Date of Birth'] = pd.to_datetime(outcomes_df.loc[:, 'Date of Birth'], format='%m/%d/%Y')
MonthYear¶
# porovnání hodnot v MonthYear a DateTime
print(sum(intakes_df['MonthYear'].str.split(expand=True)[0] != intakes_df['DateTime'].dt.month_name()),
sum(intakes_df['MonthYear'].str.split(expand=True)[1].astype('int32') != intakes_df['DateTime'].dt.year))
0 0
Příznak MonthYear obsahuje redundantní informaci, která je již obsažena v příznaku DateTime. Sloupec můžeme odstranit.
intakes_df = intakes_df.drop(columns=['MonthYear'], errors='ignore')
outcomes_df = outcomes_df.drop(columns=['MonthYear'], errors='ignore')
Intake-Outcome Type, Intake Condition, Animal Type, Breed, Color, Outcome Subtype, Found Location¶
print('Animal Type:\t\t', *intakes_df['Animal Type'].unique())
print('Intake Type:\t\t', *intakes_df['Intake Type'].unique())
print('Outcome Type:\t\t', *outcomes_df['Outcome Type'].unique())
print('Intake Condition:\t', *intakes_df['Intake Condition'].unique())
print('Outcome Subtype:\t', *outcomes_df['Outcome Subtype'].unique()[:10], '...')
print('Breed:\t\t\t', *intakes_df['Breed'].unique()[:5], '...')
print('Color:\t\t\t', *intakes_df['Color'].unique()[:10], '...')
print('Found Location:\t\t', *intakes_df['Found Location'].unique()[:4], '...')
Animal Type: Dog Cat Other Bird Livestock Intake Type: Stray Owner Surrender Public Assist Wildlife Euthanasia Request Abandoned Outcome Type: Rto-Adopt Adoption Euthanasia Transfer Return to Owner Died Disposal Missing Relocate nan Intake Condition: Normal Sick Injured Pregnant Nursing Aged Medical Other Neonatal Feral Behavior Med Urgent Space Med Attn Panleuk Outcome Subtype: nan Partner Foster SCRP Out State Suffering Underage Snr Rabies Risk In Kennel ... Breed: Beagle Mix English Springer Spaniel Basenji Mix Domestic Shorthair Mix Doberman Pinsch/Australian Cattle Dog ... Color: Tricolor White/Liver Sable/White Calico Tan/Gray Chocolate Black Brown Tabby Black/White Cream Tabby ... Found Location: 2501 Magin Meadow Dr in Austin (TX) 9409 Bluegrass Dr in Austin (TX) 2818 Palomino Trail in Austin (TX) Austin (TX) ...
Hodnoty v těchto příznacích jsou v pořádku. Příznaky jen zkonvertujeme na kategorické. Z výčtu jsou také vidět jiné způsoby značení chybějících hodnot ("", nan, Unknown). Chybějící hodnoty rovnou nahradíme hodnotou NaN.
for nanstr in ['', 'nan', 'Unknown']:
intakes_df = intakes_df.replace(nanstr, np.nan)
outcomes_df = outcomes_df.replace(nanstr, np.nan)
in_cols = ['Intake Type', 'Intake Condition', 'Animal Type', 'Breed', 'Color', 'Found Location']
out_cols = ['Outcome Type', 'Outcome Subtype', 'Animal Type', 'Breed', 'Color']
intakes_df[in_cols] = intakes_df[in_cols].astype('category')
outcomes_df[out_cols] = outcomes_df[out_cols].astype('category')
def subplot_cat_values(ax, series, *, rot=False, ylabel=True):
counts = series.value_counts()
ax.bar(counts.index, counts)
ax.set_xticks(counts.index)
ax.tick_params(labelsize=9)
if rot:
ax.tick_params(rotation=45)
ax.set_xlabel(series.name, fontsize=12)
if ylabel:
ax.set_ylabel('Count', fontsize=12)
ax.ticklabel_format(axis='y', style='sci', scilimits=(0,0), useMathText=True)
def plot_cat_values(df, title, c1, c2, c3, rot=False):
fig = plt.figure( figsize=(10, 8), layout='constrained')
gs = fig.add_gridspec(2, 3)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1:3])
ax3 = fig.add_subplot(gs[1, :])
fig.suptitle(title, fontsize=18)
subplot_cat_values(ax1, df[c1])
subplot_cat_values(ax2, df[c2], rot=rot)
subplot_cat_values(ax3, df[c3], rot=rot)
U příznaků s menším počtem kategorií si můžeme zobrazit četnost jednotlivých hodnot pro získání lepší představy o datech. V této fázi ještě z dat nic nevyvozuju.
plot_cat_values(intakes_df, 'Intakes', *['Animal Type', 'Intake Type', 'Intake Condition'], rot=False)
plot_cat_values(outcomes_df, 'Outcomes', *['Animal Type', 'Outcome Type', 'Outcome Subtype'], rot=True)
Sex upon Intake-Outcome¶
Příznak Sex upon Intake a Sex upon Outcome jdou roztrhnout na dva příznaky Sex (cat.) a Sterile (bool). Po rozdělení se s těmito příznaky bude lépe pracovat.
Provede následující transformace na zkonstruování nového příznaku Sterile:
- Neutered Male, Spayed Female -> Sterile: True
- Intact Male, Intact Female -> Sterile: False
Pohlaví pouze od zbytku oddělíme a přidělíme nový sloupec Sex. Původní příznak nebude obsahovat nic navíc (můžeme ho zahodit).
Následně nastavíme příznaky kategorického typu.
Nepoměr dat bude v poslední části analýzy podrobněji odiskutovaný.
print('Sex upon Intake:\t', intakes_df['Sex upon Intake'].unique())
print('Sex upon Outcome:\t', outcomes_df['Sex upon Outcome'].unique())
Sex upon Intake: ['Neutered Male' 'Spayed Female' 'Intact Male' 'Intact Female' nan] Sex upon Outcome: ['Neutered Male' nan 'Intact Male' 'Spayed Female' 'Intact Female']
# split
intakes_df[['Sterile', 'Sex']] = intakes_df['Sex upon Intake'].str.split(expand=True)
outcomes_df[['Sterile', 'Sex']] = outcomes_df['Sex upon Outcome'].str.split(expand=True)
sterilMap = {'Neutered': True, 'Spayed': True, 'Intact': False}
intakes_df['Sterile'] = intakes_df['Sterile'].map(sterilMap)
outcomes_df['Sterile'] = outcomes_df['Sterile'].map(sterilMap)
# drop
intakes_df = intakes_df.drop(columns=['Sex upon Intake'])
outcomes_df = outcomes_df.drop(columns=['Sex upon Outcome'])
cols = ['Sterile', 'Sex']
intakes_df[cols] = intakes_df[cols].astype('category')
outcomes_df[cols] = outcomes_df[cols].astype('category')
fig = plt.figure(figsize=(10, 3), layout='constrained')
(fig1, fig2) = fig.subfigures(1, 2, wspace=0.1)
axes = fig1.subplots(1, 2)
fig1.suptitle('Sex upon Intake', fontsize=13)
subplot_cat_values(axes[0], intakes_df['Sex'])
subplot_cat_values(axes[1], intakes_df['Sterile'], ylabel=False)
axes = fig2.subplots(1, 2)
fig2.suptitle('Sex upon Outcome', fontsize=13)
subplot_cat_values(axes[0], outcomes_df['Sex'])
subplot_cat_values(axes[1], outcomes_df['Sterile'], ylabel=False)
Age upon Intake-Outcome¶
intakes_df['Age upon Intake'].unique()
array(['2 years', '8 years', '11 months', '4 weeks', '4 years', '6 years', '6 months', '5 months', '14 years', '1 month', '2 months', '18 years', '9 years', '4 months', '1 year', '3 years', '4 days', '1 day', '5 years', '2 weeks', '15 years', '7 years', '3 weeks', '3 months', '12 years', '1 week', '9 months', '10 years', '10 months', '7 months', '8 months', '1 weeks', '5 days', '0 years', '2 days', '11 years', '17 years', '3 days', '13 years', '5 weeks', '19 years', '6 days', '16 years', '20 years', '-1 years', '22 years', '23 years', '-2 years', '21 years', '-3 years', '25 years', '24 years', '30 years', '28 years'], dtype=object)
Z výpisu unikátních hodnot je vidět, že se v datech vyskytují překlepy v podobě záporných hodnot. Tyto záznamy opravíme a se zbylými daty převedeme na dny.
- Číselnou hodnotu od slova oddělíme,
- převedeme na nezáporné číslo
- a následně transformujeme dle jednotky na celé dny tak, aby byly všechny hodnoty ve stejným měřítku.
Součástí zpracování příznaky rovnou nastavíme na numerický typ.
Pozn.: Zvířata označené jako 0 years
interpretujeme jako novorozence.
tmp_in = intakes_df['Age upon Intake'].str.split(expand=True)
tmp_out = outcomes_df['Age upon Outcome'].str.split(expand=True)
tmp_in[0] = tmp_in[0].astype('Int64').abs() # nullable integer
tmp_out[0] = tmp_out[0].astype('Int64').abs()
print(*tmp_in[1].unique())
print(*tmp_out[1].unique())
years months weeks month year days day week years year months days weeks month day week nan
def convert_ages(tmp_df):
yearMask = tmp_df[1].isin(['years', 'year'])
monthMask = tmp_df[1].isin(['months', 'month'])
weekMask = tmp_df[1].isin(['weeks', 'week'])
tmp_df.loc[yearMask, 0] = tmp_df.loc[yearMask, 0] * 365
tmp_df.loc[monthMask, 0] = tmp_df.loc[monthMask, 0] * 30
tmp_df.loc[weekMask, 0] = tmp_df.loc[weekMask, 0] * 7
return tmp_df[0]
intakes_df['Age upon Intake'] = convert_ages(tmp_in)
outcomes_df['Age upon Outcome'] = convert_ages(tmp_out)
def subplot_age(ax, data, title):
ax.set_title(title)
ax.hist(data, bins=15)
ax.set_ylabel('Count')
ax.set_xlabel('Age [years]')
ax.ticklabel_format(axis='y', style='sci', scilimits=(0,0), useMathText=True)
fig, axes = plt.subplots(1, 2, figsize=(10, 3), layout='constrained')
subplot_age(axes[0], intakes_df['Age upon Intake'].dropna()/365, 'Age upon Intake')
subplot_age(axes[1], outcomes_df['Age upon Outcome'].dropna()/365, 'Age upon Outcome')
💡 Shrnutí¶
Příznaky obou datasetů jsme převedli na příslušné datové typy.
- Příznaky [Animal ID, Name] jsme nechali ve formátu string (object),
- [DateTime, Date of Birth] převedli na datetime (datetime64),
- Age transformovali na numerickou hodnotu (int64) a
- ostatní převedli do kategorického typu (category).
- Redundantní příznak MonthYear jsme odstranili a
- rozsáhlejší příznak Sex upon Intake-Outcome roztrhli na dva smysluplné příznaky.
- Porušená data jsme opravili (záporný věk, překlepy) a chybějí doplnili.
Nyní máme mnohem lepší představu o chybějících datech. Předpřipravená data máme v proměnnách
- intakes_df
- outcomes_df
plot_missing(intakes_df, 'Intakes', outcomes_df, 'Outcomes', title='Updated missing values counts')
🖊️ Deskriptivní statistiky¶
def print_univar_num_stats(series):
print(series.name, 'statistics')
print()
print('min:\t', series.min())
print('max:\t', series.max())
print('mean:\t', series.mean())
print('median:\t', series.median())
print('range:\t', series.max() - series.min())
print()
print('lower quartile:\t', series.quantile(0.25))
print('upper quartile:\t', series.quantile(0.75))
print('IQR:\t\t', series.quantile(0.75) - series.quantile(0.25))
print()
print('variance:\t', series.var())
print('std. variation:\t', series.std())
print('skewness:\t', series.skew())
print('kurtosis:\t', series.kurtosis())
def print_univar_dt_stats(series):
print(series.name, 'statistics')
print()
print('min:\t', series.min())
print('max:\t', series.max())
print('mode:\t', list(series.mode()))
print('range:\t', series.max() - series.min())
def plot_univar_num(title1, title2, xlabel, ylabel, data, *, bins=15, xlim=None, scinot=False, showfliers=True):
fig, axes = plt.subplots(1, 2, figsize=(10, 4), layout='constrained', gridspec_kw={'wspace': 0.1})
ax = axes[0]
ax.set_title(title1)
ax.set_ylabel(ylabel)
ax.set_xlabel(xlabel)
if scinot:
ax.ticklabel_format(axis='y', style='sci', scilimits=(0, 0), useMathText=True)
ax.hist(data, bins=bins)
medianprops = dict(linewidth=2.5, color=(1, 0.38, 0.27))
flierprops = dict(marker='d', markerfacecolor=(1, 0.38, 0.27), markersize=8, markeredgecolor='none')
ax = axes[1]
ax.set_title(title2)
ax.set_xlabel(xlabel)
if xlim != None:
ax.set_xlim(xlim)
ax.boxplot(data, vert=False, widths=[0.4], showfliers=showfliers, flierprops=flierprops, medianprops=medianprops)
ax.get_yaxis().set_visible(False)
🎂 Příznak Age upon Intake¶
Popište příznaky Age upon Intake a DateTime (původně z datasetu intakes) pomocí univariačních deskriptivních statistik.
age = intakes_df['Age upon Intake'].dropna()
ageY = intakes_df['Age upon Intake'] / 365
age.info()
<class 'pandas.core.series.Series'> Index: 138565 entries, 0 to 138584 Series name: Age upon Intake Non-Null Count Dtype -------------- ----- 138565 non-null Int64 dtypes: Int64(1) memory usage: 2.2 MB
print_univar_num_stats(ageY)
Age upon Intake statistics min: 0.0 max: 30.0 mean: 2.0278033206313832 median: 1.0 range: 30.0 lower quartile: 0.1643835616438356 upper quartile: 2.0 IQR: 1.8356164383561644 variance: 8.170721564564788 std. variation: 2.8584474045475785 skewness: 2.3340016328003066 kurtosis: 5.968203952133479
Příznak Age upon Intake (věk zvířete v moment přijetí) je numerický příznak. Hodnoty v datasetu jsou z předzpracovaní vyjádřené v dnech. Záznamy s chybějící hodnotou Age upon Intake v tomto příznaku ignorujeme.
- Ze základních popisných statistik vidíme, že věk zvířat se v době příchodu pohybuje mezi 0 (novorozenci) a 30 let.
- Průměr je lehce nad 2 let a medián je 1 rok.
- I přestože celkový rozsah hodnot je 30 let, mezikvartilové rozpětí je pouhých 1.8 let.
- Kladný koeficient šikmosti indikuje pravostrannou šikmost.
- Vysoký koeficient šikmosti značí, že většina hodnot leží velmi blízko hodnoty 2 (střední hodnota).
V následujícím histogramu jdou názorně vidět zmíněné poznatky. Z oříznutého boxplotu je zřejmé, že i přestože je průměr 2, většina (75%) zvířat je ještě mladší. Průměr je v tomto případě zkreslený kvůli odlehlým hodnotám. O věku zvířat v útulku vypovídá lépe medián.
plot_univar_num('Age upon Intake', 'Age upon Intake < 6 (focused)', 'Age [years]', 'Count', ageY, xlim=(-0.5, 6.5), scinot=True)
intakes_df[ageY == 30]
Animal ID | Name | DateTime | Found Location | Intake Type | Intake Condition | Animal Type | Age upon Intake | Breed | Color | Sterile | Sex | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
132371 | A842878 | Sunshine | 2021-10-30 10:07:00 | 3008 West Avenue in Austin (TX) | Owner Surrender | Other | Bird | 10950 | Macaw | Blue/Gold | False | Female |
Nejstarší zvíře v útulku je 39 letý papoušek Ara (samička) jménem Sunshine 🙂🦜.
📅 Příznak Datetime (intakes)¶
Popište příznaky Age upon Intake a DateTime (původně z datasetu intakes) pomocí univariačních deskriptivních statistik.
def dt_to_counts(dts):
dtc = pd.to_datetime(dts).dt.date.value_counts()
# fill days with no intakes
idx = pd.date_range(dtc.index.min(), dtc.index.max())
dtc.index = pd.DatetimeIndex(dtc.index)
dtc = dtc.reindex(idx, fill_value=0)
return dtc
dt = intakes_df['DateTime']
dtcount = dt_to_counts(dt)
dt.info()
<class 'pandas.core.series.Series'> Index: 138565 entries, 0 to 138584 Series name: DateTime Non-Null Count Dtype -------------- ----- 138565 non-null datetime64[ns] dtypes: datetime64[ns](1) memory usage: 6.1 MB
print_univar_dt_stats(dt)
DateTime statistics min: 2013-10-01 01:12:00 max: 2022-04-27 07:54:00 mode: [Timestamp('2014-07-09 12:58:00'), Timestamp('2016-09-23 12:00:00')] range: 3130 days 06:42:00
print_univar_num_stats(dtcount)
count statistics min: 0 max: 140 mean: 44.25582880868732 median: 44.0 range: 140 lower quartile: 34.0 upper quartile: 55.0 IQR: 21.0 variance: 331.17574476812825 std. variation: 18.198234660761145 skewness: 0.26627105755009095 kurtosis: 0.7186242354271446
dtcount[dtcount == dtcount.max()]
2014-07-09 140 Freq: D, Name: count, dtype: int64
Příznak DateTime z datasetu intakes je časový příznak.
- Ze základních statistik vidíme, že první digitálně zaznamenaný příchod zvířete byl 1. října 2013 a poslední 27. dubna 2022.
- Nejvyšší počet přijatých zvířat bylo dne 9. července 2014, kdy bylo do systému přidáno 140 nových zvířat.
- Průměrně se každých den přijme 44 zvířat, v 50% případů 34-55 zvířat.
plot_univar_num('Intakes per day', 'Intakes per day', 'Number of Intakes', 'Count', dtcount, bins=20, showfliers=True)
🐾 Příznak Animal Type (intakes)¶
1. Vyberte si tři další příznaky a popište je pomocí univariačních deskriptivních statistik, které jsou pro ně vhodné.
def plot_univar_cat(title1, title2, xlabel, icons, data, *, prop=1000):
fig, axes = plt.subplots(1, 2, figsize=(10, 4), layout='constrained', gridspec_kw={'wspace': 0.2})
ax = axes[0]
ax.set_title(title1)
ax.set_xlabel(xlabel)
ax.ticklabel_format(axis='x', style='sci', scilimits=(0, 0), useMathText=True)
ax.barh(data.index, data)
ax.invert_yaxis()
val = data / prop
val_freq = val / val.sum()
waff.Waffle.make_waffle(
ax=axes[1],
rows=12,
values=val,
title={'label': title2, 'loc': 'center'},
labels=[f"{k} ({v*100:.2f}%)" for k, v in val_freq.items()],
legend={'bbox_to_anchor': (1.7, 1), 'ncol': 1, 'framealpha': 0},
icons=icons,
font_size=16,
# icon_style='solid',
icon_legend=True,
starting_location='NW',
vertical=True,
cmap_name="Set2"
)
in_type = intakes_df['Animal Type']
in_type_c = in_type.value_counts()
in_type_cnorm = in_type.value_counts(normalize=True)
print('Intakes Animal Type')
display(in_type.info())
Intakes Animal Type <class 'pandas.core.series.Series'> Index: 138565 entries, 0 to 138584 Series name: Animal Type Non-Null Count Dtype -------------- ----- 138565 non-null category dtypes: category(1) memory usage: 5.2 MB
None
in_type.describe()
count 138565 unique 5 top Dog freq 78135 Name: Animal Type, dtype: object
in_type_c
Animal Type Dog 78135 Cat 52373 Other 7372 Bird 661 Livestock 24 Name: count, dtype: int64
Příznak Animal Type je kategorický příznak s 5 kategoriemi: Bird, Cat, Dog, Livestock, Other.
- Ze základních statistik vidíme, že nejčetnějším zvířetem v útulku jsou psi (56%).
- Po psech následují kočky (37%). Méně zastoupená zvířata jsou ptáci, dobytek a "ostatní".
- Psi a kočky dohroma tvoří přes 94% záznamů.
plot_univar_cat('Intakes Animal Types', 'Proportion of Animal Types in Intakes', 'Count', ['dog', 'cat', 'paw', 'crow', 'cow'], in_type_c)
🌈 Příznak Color (intakes)¶
2. Vyberte si tři další příznaky a popište je pomocí univariačních deskriptivních statistik, které jsou pro ně vhodné.
intakes_df['Color'].str.split('/', n=-1).value_counts()
Color [Black, White] 14469 [Black] 11613 [Brown Tabby] 7948 [Brown] 5935 [White] 4866 ... [Black Tabby, Gray] 1 [Yellow, Red] 1 [Seal Point, Cream] 1 [White, Lilac Point] 1 [Brown Tabby, Tan] 1 Name: count, Length: 616, dtype: int64
len(intakes_df['Color'].unique())
616
cols = intakes_df['Color'].str.split('/', n=-1)
cols = pd.Series(np.concatenate(cols.to_numpy().flatten()))
cols = cols.value_counts()
cols.index, len(cols.index)
(Index(['White', 'Black', 'Brown', 'Tan', 'Brown Tabby', 'Blue', 'Orange Tabby', 'Tricolor', 'Brown Brindle', 'Red', 'Gray', 'Blue Tabby', 'Tortie', 'Calico', 'Chocolate', 'Torbie', 'Cream', 'Cream Tabby', 'Fawn', 'Sable', 'Yellow', 'Buff', 'Lynx Point', 'Blue Merle', 'Gray Tabby', 'Seal Point', 'Orange', 'Black Brindle', 'Flame Point', 'Black Tabby', 'Blue Tick', 'Brown Merle', 'Gold', 'Silver', 'Black Smoke', 'Red Tick', 'Lilac Point', 'Red Merle', 'Tortie Point', 'Silver Tabby', 'Blue Cream', 'Yellow Brindle', 'Apricot', 'Green', 'Blue Point', 'Chocolate Point', 'Liver', 'Calico Point', 'Pink', 'Blue Tiger', 'Brown Tiger', 'Agouti', 'Silver Lynx Point', 'Blue Smoke', 'Liver Tick', 'Black Tiger', 'Orange Tiger', 'Cream Tiger', 'Gray Tiger', 'Ruddy'], dtype='object'), 60)
cols.head()
White 61893 Black 43306 Brown 20784 Tan 15939 Brown Tabby 12772 Name: count, dtype: int64
Příznak Color je kategorický příznak.
- Ze základních statistik vidíme, že nejčastější barevné kombinace zvířat jsou černobílá ⬛⬜, černá ⬛ a hnědá 🟫.
- Počítáme-li všechny barvy na zvířeti, velká část má bílou ⬜, černou ⬛, hnědou 🟫 nebo bronzovou barvu.
- V datasetu mají zvířata celkem 616 unikátních barevných kombinací z 60 barev.
fig, axes = plt.subplots(1, 2, figsize=(10, 5), layout='constrained', gridspec_kw={'wspace': 0.1})
ax = axes[0]
toshow = cols.iloc[:12]
ax.set_title('12 Most Common Animal Colors')
ax.set_xlabel('Count')
ax.ticklabel_format(axis='x', style='sci', scilimits=(0, 0), useMathText=True)
ax.barh(toshow.index, toshow)
ax.invert_yaxis()
ax = axes[1]
ax.set_title('Animal Colors')
t = 6
sumcol = cols.iloc[:t]
sumcol['Other'] = cols.iloc[t:].sum()
patches, _, autotexts = ax.pie(sumcol, labels=sumcol.index, autopct='%1.1f%%', pctdistance=0.8, frame=False,
colors=['white', '#484848', '#884d3a', '#e1ad8e', '#e0a75d', '#add8e6', '#fbf3d0'],
wedgeprops={"edgecolor":'black','linewidth': 0.3, 'antialiased': True})
ax.axis('equal')
autotexts[1].set_color('white')
autotexts[2].set_color('white')
⚕️ Příznak Intake Condition¶
3. Vyberte si tři další příznaky a popište je pomocí univariačních deskriptivních statistik, které jsou pro ně vhodné.
cond = intakes_df['Intake Condition'].value_counts()
cond
Intake Condition Normal 119305 Injured 7841 Sick 5997 Nursing 3932 Aged 463 Neonatal 321 Other 245 Medical 174 Feral 125 Pregnant 103 Behavior 49 Space 4 Med Attn 3 Med Urgent 2 Panleuk 1 Name: count, dtype: int64
Příznak Intake Condition je kategorický příznak.
- Ze statistik vidíme, že ve většině případů jsou zvířata přijata ve stavu Normal (86%).
- V ostatních případech jsou nejčastěji přijata zraněná (40%), nemocná (31%), nebo se zotavují (20%).
normal = cond.loc['Normal']
nonnormal = cond.loc[cond.index != 'Normal']
fig, axes = plt.subplots(1, 2, figsize=(10, 5), layout='constrained', gridspec_kw={'wspace': 0.1})
# ax = axes[2]
# ax.set_title('Comparison of Non-Normal Intake Conditions')
# ax.set_xlabel('Count')
# ax.ticklabel_format(axis='x', style='sci', scilimits=(0, 0), useMathText=True)
# ax.barh(nonnormal.index, nonnormal)
# ax.invert_yaxis()
ax = axes[0]
ax.set_title('Intake Conditions')
patches, _, autotexts = ax.pie([normal, nonnormal.sum()], labels=['Normal', 'Non-Normal'], autopct='%1.1f%%', pctdistance=0.7, frame=False,
colors=['#98f59f', '#E0B0FF'], startangle=23, explode=(0, 0.12),
wedgeprops={"edgecolor":'black','linewidth': 0.3, 'antialiased': True})
ax.axis('equal')
ax = axes[1]
ax.set_title('Non-Normal Intake Conditions')
t = 5
sumcol = nonnormal.iloc[:t]
sumcol['Other'] = nonnormal.iloc[t:].sum()
patches, _, autotexts = ax.pie(sumcol, labels=sumcol.index, autopct='%1.1f%%', pctdistance=0.7, frame=False,
colors=['#e69b96', '#f9c367', '#7fbf7f', '#d8ecff', '#ffdbac', '#bbb'],
wedgeprops={"edgecolor":'black','linewidth': 0.3, 'antialiased': True})
ax.axis('equal');
🏠 Původ zvířat¶
1. Vyberte si dva příznaky, mezi kterými by mohl být nějaký vztah (např. korelace) a popište tento vztah pomocí bivariačních deskriptivních statistik.
sample = intakes_df[intakes_df['Animal Type'] == 'Other'].loc[:, 'Breed'].unique()
print(list(sample)[:20])
['Bat', 'Bat Mix', 'Hamster Mix', 'Raccoon', 'Raccoon Mix', 'Rabbit Sh Mix', 'Skunk Mix', 'Cinnamon', 'Rabbit Sh', 'Opossum', 'Skunk', 'Dutch/Angora-Satin', 'Fox', 'Rat', 'Guinea Pig Mix', 'Ferret', 'Cold Water', 'Rat Mix', 'Opossum Mix', 'Guinea Pig']
condtype = intakes_df.loc[:, ['Intake Type', 'Animal Type']].value_counts().unstack().fillna(0).astype(int).transpose()
cm = sns.light_palette(blue, as_cmap=True)
style = condtype.style.background_gradient(axis=1, cmap=cm).set_properties(**{'text-align':'center','font-size':'16px'})
style.map(lambda v: 'opacity: 30%;' if (v == 0) else None)
style.set_caption('Animal Types by Intake Type').set_table_styles([dict(
selector='caption', props=[('font-size', '150%'), ('text-align', 'left'), ('color', '#555')])])
Intake Type | Abandoned | Euthanasia Request | Owner Surrender | Public Assist | Stray | Wildlife |
---|---|---|---|---|---|---|
Animal Type | ||||||
Bird | 0 | 3 | 83 | 137 | 328 | 110 |
Cat | 366 | 59 | 10586 | 1157 | 40205 | 0 |
Dog | 347 | 183 | 17142 | 6735 | 53727 | 1 |
Livestock | 0 | 0 | 1 | 1 | 22 | 0 |
Other | 27 | 14 | 763 | 314 | 993 | 5261 |
Zvířata se do útulku většinou dostanou tím, že:
- (u ptáků, koček a psů) jsou nalezena zatoulaná nebo se jich původní majitelé vzdali
- u divokých a exotických zvířat (ostatní - netopýři, jeleni, hadi, žáby, ...) - jsou z volné přírody
- menší čast zvířat byla původními majiteli opuštěna nebo byla přijata s požadavkem o eutanazii
Vizualizace (tabulka) podkládá zmíněné poznatky. Hodnoty v tabulkách jsou četnosti výskytu daných případů. Barva na pozadí zvýrazňuje nejvíce zastoupené hodnoty na řádce.
🚪 Odchody zvířat¶
2. Vyberte si dva příznaky, mezi kterými by mohl být nějaký vztah (např. korelace) a popište tento vztah pomocí bivariačních deskriptivních statistik.
types = outcomes_df.loc[:, ['Outcome Subtype', 'Animal Type']].value_counts().unstack().fillna(0).astype(int).transpose().T
cm = sns.light_palette(blue, as_cmap=True)
style = types.style.background_gradient(axis=1, cmap=cm).set_properties(**{'text-align':'center','font-size':'16px'})
style.map(lambda v: 'opacity: 30%;' if (v == 0) else None)
style.set_caption('Animal Types by Outcome Type').set_table_styles([dict(
selector='caption', props=[('font-size', '150%'), ('text-align', 'left'), ('color', '#555')])])
Animal Type | Bird | Cat | Dog | Livestock | Other |
---|---|---|---|---|---|
Outcome Subtype | |||||
Aggressive | 0 | 4 | 565 | 0 | 1 |
At Vet | 2 | 169 | 102 | 1 | 25 |
Barn | 0 | 11 | 1 | 0 | 0 |
Behavior | 0 | 0 | 159 | 0 | 0 |
Court/Investigation | 0 | 0 | 38 | 0 | 0 |
Customer S | 0 | 6 | 12 | 0 | 0 |
Emer | 0 | 2 | 3 | 0 | 0 |
Emergency | 0 | 3 | 4 | 0 | 0 |
Enroute | 2 | 34 | 11 | 0 | 42 |
Field | 0 | 6 | 91 | 0 | 0 |
Foster | 38 | 7142 | 5249 | 10 | 101 |
In Foster | 2 | 262 | 53 | 0 | 12 |
In Kennel | 14 | 410 | 183 | 0 | 71 |
In State | 0 | 0 | 11 | 0 | 0 |
In Surgery | 0 | 14 | 12 | 0 | 1 |
Medical | 11 | 87 | 78 | 0 | 151 |
Offsite | 2 | 114 | 339 | 1 | 1 |
Out State | 0 | 0 | 397 | 0 | 0 |
Partner | 199 | 15723 | 16698 | 6 | 969 |
Possible Theft | 0 | 2 | 14 | 0 | 0 |
Prc | 0 | 4 | 8 | 0 | 0 |
Rabies Risk | 0 | 100 | 95 | 0 | 3861 |
SCRP | 0 | 3208 | 0 | 0 | 0 |
Snr | 0 | 2935 | 0 | 0 | 0 |
Suffering | 110 | 1821 | 891 | 1 | 686 |
Underage | 1 | 1 | 0 | 0 | 34 |
Zvířata odcházejí
- nejčastěji: s novým majitelem nebo jdou do pěstounské péče
- některým zvířatům způsobuje přežívání bolest a podstupují eutanazii
- u divokých a exotických zvířat (netopýři, jeleni, hadi, žáby, ...) - je častým důvodem odchodu riziko vztekliny
- potulné kočky jsou v útulcích často neutralizované metodamy SNR (shelter-neuter-return) nebo jim je nalezen domov v SCRP (Stray Cat Return Program)
❓ Insights¶
💙 Závislost typu odchodu/příchodu¶
Závisí typ odchodu zvířete z útulku (Outcome Type) na typu příchodu (Intake Type)? Pokud chcete, uvažujte pro zjednodušení pouze zvířata, která se v každém datasetu vyskytují právě jednou.
Sestrojíme si nový dataset merged, ve kterém jsou zvířata, která se v obou datasetech vyskytují právě jednou.
sonce = intakes_df['Animal ID'].value_counts() == 1
in_wo_dupl = intakes_df.loc[intakes_df['Animal ID'].isin(sonce[sonce].index.values)]
sonce = outcomes_df['Animal ID'].value_counts() == 1
out_wo_dupl = outcomes_df.loc[outcomes_df['Animal ID'].isin(sonce[sonce].index.values)]
merged = intakes_df.merge(outcomes_df, on='Animal ID', suffixes=('_in', '_out'))
durs = merged['DateTime_out'] - merged['DateTime_in']
merged = merged[durs == abs(durs)]
counts = merged.loc[:, ['Intake Type', 'Outcome Type']].value_counts().unstack().fillna(0).astype(int)
cm = sns.light_palette(blue, as_cmap=True)
style = counts.style.background_gradient(axis=1, cmap=cm).set_properties(**{'text-align':'center','font-size':'16px'})
style.map(lambda v: 'opacity: 30%;' if (v == 0) else None)
style.set_caption('Outcome Types by Intake Type').set_table_styles([dict(
selector='caption', props=[('font-size', '150%'), ('text-align', 'left'), ('color', '#555')])])
Outcome Type | Adoption | Died | Disposal | Euthanasia | Missing | Relocate | Return to Owner | Rto-Adopt | Transfer |
---|---|---|---|---|---|---|---|---|---|
Intake Type | |||||||||
Abandoned | 438 | 4 | 4 | 5 | 0 | 0 | 57 | 9 | 224 |
Euthanasia Request | 17 | 2 | 2 | 160 | 0 | 0 | 8 | 0 | 31 |
Owner Surrender | 20666 | 184 | 13 | 802 | 11 | 0 | 1722 | 338 | 7987 |
Public Assist | 1759 | 51 | 57 | 515 | 3 | 0 | 6712 | 188 | 1244 |
Stray | 49253 | 910 | 133 | 2892 | 63 | 8 | 18973 | 771 | 30324 |
Wildlife | 7 | 134 | 396 | 4406 | 2 | 14 | 3 | 0 | 51 |
Typ příchodu má zřejmě značný vliv na typ odchodu.
Vidíme, že
- zvířata, která skončila opuštěná, majitelé se jich vzdali nebo byla před přijetím do útulku toulavá, byla adoptovaná
- zvířata s typem příchodu Public Assist jsou často vrácena původním majitelům nebo v pětině případů adoptovaná
- zvířata z volné přírody + zvířata, která byla přijata s požadavkem o euthanázii, zákrok často podstoupí
🐥 Věk zvířete při adopci¶
Hraje věk zvířete roli při adopci?
Rozdělíme si odchody na typ Adoptovaná a Neadoptovaná.
adopted = outcomes_df.loc[outcomes_df['Outcome Type'] == 'Adoption', 'Age upon Outcome']/365
notadopted = outcomes_df.loc[outcomes_df['Outcome Type'] != 'Adoption', 'Age upon Outcome'].dropna()/365
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(9, 4), sharey=True, layout='constrained')
fig.suptitle('Adoption Rate by Age', fontsize=16)
ax1.xaxis.grid(True)
ax1.set_title('Full range', fontsize=12)
ax1.set_xlabel('Age [years]')
ax1.set_yticks([1, 2], labels=['Adopted', 'Not Adopted'])
parts = ax1.violinplot([adopted.astype(float), notadopted.astype(float)], [1, 2],
widths=0.6, points=100, vert=False, showmeans=True)
parts['bodies'][0].set_facecolor(red)
parts['bodies'][1].set_facecolor(blue)
for part in ['cbars', 'cmaxes', 'cmins', 'cmeans']:
parts[part].set_color([red, blue])
ax1.invert_yaxis()
ax2.xaxis.grid(True)
ax2.set_title('Age < 6(focused)', fontsize=12)
ax2.set_xlabel('Age [years]')
parts = ax2.violinplot([adopted.astype(float), notadopted.astype(float)], [1, 2],
widths=0.8, points=150, vert=False, showmeans=True)
parts['bodies'][0].set_facecolor(red)
parts['bodies'][1].set_facecolor(blue)
for part in ['cbars', 'cmaxes', 'cmins', 'cmeans']:
parts[part].set_color([red, blue])
ax2.set_xlim(left=-.4, right=6);
Z této vizualizace je možné si všimnout, že neadoptovaná zvířata mají relativně velké zastoupení i ve vyššíšch věkových kategorií, zatímco věk adoptovaných zvířat se drží u nižších hodnot.
Neadoptovaných zvířat je v datasetu víc, rozdíl je znatelný po normalizaci. Lze usoudit, že adopce je častější u mladších jedinců.
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(9, 4), layout='constrained')
ax.set_title('Normalized Adoption Rate by Age')
ax.hist(adopted, 15, alpha=0.5, color=(226/255, 74/255, 51/255), label='Adopted', density=True)
ax.hist(notadopted, 15, alpha=0.5, label='Not Adopted', color=(52/255, 138/255, 189/255), density=True)
ax.set_xlabel('Age')
ax.set_ylabel('Relative Freq.')
ax.legend();
📈 Příjem zvířat¶
Je příjem zvířat v rámci roku konstantní nebo existují období s větší/nižší zátěží?
time_ser = intakes_df['DateTime'].dt.strftime('%Y-%m').value_counts().sort_index()
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 8), layout='constrained', gridspec_kw={'hspace': 0.1})
ax1.plot(time_ser)
ticks = list(time_ser.index)[3::12]
ticklabels = list(time_ser.index)[3+5::12]
ax1.set_xticks(ticks)
ax1.set_xticklabels(f"{tick[:-3]}" for tick in ticks)
ax1.set_title('Intakes over Time')
ax1.set_ylim(ymin=0)
ax1.set_xlabel('Year')
ax1.set_ylabel('Intake Count')
ax1.grid(which='major', axis='x', color='grey', linestyle='dotted', linewidth=0.6)
dt = intakes_df[['DateTime']].copy()
dt['Year'] = dt['DateTime'].dt.year
dt['Month'] = dt['DateTime'].dt.month
dt = dt.drop('DateTime', axis=1)
dt = dt.value_counts().groupby(['Year', 'Month']).sum()
means = dt.groupby('Month').mean()
bar_labels=['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
(main, second, rest) = (red, '#ea8070', '#f3b6ad')
bar_colors = [rest, rest, rest, rest, main, main, second, second, second, second, rest, rest]
p = ax2.bar(means.index, means, width=0.6, color=bar_colors)
ax2.set_title('Average Monthly Intakes')
ax2.set_xticks(range(1, 13), labels=bar_labels)
ax2.set_xlabel('Month')
ax2.bar_label(p, fmt='%i', padding=-20, color='w')
ax2.set_yticks([])
ax2.set_ylim(ymin=0)
ax2.set_ylabel('Average Intake Count')
ax2.grid(visible=False, which='major', axis='both')
Na vývoji počtu přijatých zvířat je vidět, že jsou jistá obodbí v roce, kdy je příjem výrazně vyšší. Z druhého grafu vyčteme, že toto období trvá od května do října, kdy je průměrný přijem zvířat o zhruba 500 víc než v ostatních měsících. Nejmenší příjem je v zímně a na jaře.
Pozn.: 😷🦠 Z přvního grafu o vývoji lze vypozorovat náhlý pokles příjmů na konci roku 2019 z důvodu koronavirové krize. Od té doby frekvence příchodů pomalu roste.
💔 Kastrace zvířat¶
Jak se mění stav plodnosti zvířat? -- stav při příchodu v. stav při odchodu
counts = merged.loc[:, ['Sterile_in', 'Sterile_out']].value_counts()
scounts = counts.reset_index().reindex([2, 0, 1]).reset_index(drop=True)
cnt_nst_in = scounts.loc[scounts['Sterile_in'] == False, 'count'].sum()
cnt_st_in = scounts.loc[scounts['Sterile_in'] == True, 'count'].sum()
scounts.loc[:2, 'frac'] = scounts.loc[:2, 'count']/cnt_nst_in
scounts.loc[2:, 'frac'] = scounts.loc[2:, 'count']/cnt_st_in
scounts['frac'] = scounts['frac']
cm = sns.light_palette(blue, as_cmap=True)
style = scounts.style.set_properties(**{'text-align':'center','font-size':'16px'})
style.set_caption('Animal Types by Origin').set_table_styles([dict(
selector='caption', props=[('font-size', '150%'), ('text-align', 'right'), ('color', '#555')])])
style.format(precision=2)
Sterile_in | Sterile_out | count | frac | |
---|---|---|---|---|
0 | False | False | 32705 | 0.34 |
1 | False | True | 63245 | 0.66 |
2 | True | True | 45695 | 1.00 |
counts = merged.loc[:, ['Sterile_in', 'Sterile_out']].value_counts()
st_in, nst_in, st_out, nst_out = ('Sterile (in)', 'Non Sterile (in)', 'Sterile (out)', 'Non Sterile (out)')
cdict = {st_in: {st_out: counts.loc[True, True]},
nst_in: {st_out: counts.loc[False, True], nst_out: counts.loc[False, False]}}
ax = alluvial.plot(cdict, figsize=(6, 4), fontname='Monospace', color_side=0,
alpha=0.9, colors=[red, blue], src_label_override=[st_in, nst_in], dst_label_override=[st_out, nst_out],
disp_width=True, wdisp_sep=3*' ', width_in=False, v_gap_frac=0.1)
ax.set_title('Animal Sex Transitions')
left, width = .25, .5
bottom, height = .25, .5
right = left + width
top = bottom + height
ax.text(0.5, 0.55, 'neutering', fontstyle='oblique',
horizontalalignment='center', verticalalignment='center',
transform=ax.transAxes, color='w', fontsize=14);
Pro jednoduchost opět uvažujeme pouze zvířata, která navštívila a opustila útulek právě jednou.
Z vizualizace je patrné, že velká čast (66%) příchozích plodných zvířat podstoupí v útulku kastraci.
😃😃: Žádnému zvířeti se nepodařilo zvrátit operaci. Doktor Preobraženskij přestal experimentovat (Mikhail Bulgakov, Psí srdce 🐺).
⏳ Čas strávený v útulku¶
Kolik času stráví zvířata v útulku?
durs = (merged['DateTime_out'] - merged['DateTime_in']).dt.days
print_univar_num_stats(durs)
None statistics min: 0 max: 2948 mean: 63.78191485852635 median: 7.0 range: 2948 lower quartile: 3.0 upper quartile: 34.0 IQR: 31.0 variance: 40540.078983925494 std. variation: 201.345670387832 skewness: 6.139468243278622 kurtosis: 46.946690264259104
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(9, 4), sharey=False, layout='constrained', gridspec_kw={'wspace': 0.1})
ax1.hist(durs)
ax1.set_title('Time Spent in Animal Care')
ax1.set_xlabel('Days')
ax1.set_ylabel('Count')
ax1.ticklabel_format(axis='y', style='sci', scilimits=(0,0), useMathText=True)
ax2.hist(durs[durs < 150], bins=50)
ax2.set_title('Focused (< 150 days)')
ax2.set_xlabel('Days')
ax2.ticklabel_format(axis='y', style='sci', scilimits=(0,0), useMathText=True)
durs.sort_values(ascending=False).head(5) / 360
176625 8.188889 90634 8.163889 31455 8.077778 171654 8.072222 177941 8.052778 dtype: float64
Pro úplnost uvažujeme pouze zvířata, která už odešla. Obvykle nejsou zvířata v útulku déle než 3 měsíce, často odchází ještě dřív (do 1 měsíce). Najdeme i případy, kdy byla zvířata v pěči až 8 let. Průměrně však stráví v útulku 63 dní, přičemž medián je pouhých 7 dní.
Pozn.: V pravém histogramu má osa y jiné měřítko.
💉 Euthanazie¶
Je frekvence eutanazií konstantní, nebo je období, kdy jsou eutanazie častější?
counts = merged.loc[merged['Outcome Type'] == 'Euthanasia', 'DateTime_out']
fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1, figsize=(9, 8), sharey=False, layout='constrained', gridspec_kw={'hspace': 0.1})
ax1.set_title('Frequency of Euthanasia over Time')
time_ser = counts.dt.strftime('%Y-%m').value_counts().sort_index()
ax1.plot(time_ser, label='Real data', alpha=0.4)
ax1.plot(time_ser.rolling(window=12).mean(), label='Rolling average [annual]', linewidth=2.2)
ticks = list(time_ser.index)[3::12]
ticklabels = list(time_ser.index)[3+5::12]
ax1.set_xticks(ticks)
ax1.set_xticklabels(f"{tick[:-3]}" for tick in ticks)
ax1.set_ylim(ymin=0)
ax1.set_xlabel('Year')
ax1.set_ylabel('Euthanasia Procedure Count')
ax1.legend()
dt = merged.loc[merged['Outcome Type'] == 'Euthanasia', ['DateTime_out']].copy()
dt['Year'] = dt['DateTime_out'].dt.year
dt['Month'] = dt['DateTime_out'].dt.month
dt = dt.drop('DateTime_out', axis=1)
dt = dt.value_counts().groupby(['Year', 'Month']).sum()
means = dt.groupby('Month').mean()
bar_labels=['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
(main, second, rest) = (red, '#ea8070', '#f3b6ad')
bar_colors = [rest, rest, main, second, second, second, rest, rest, rest, rest, rest, rest]
p = ax2.bar(means.index, means, width=0.6, color=bar_colors)
ax2.set_title('Frequency of Euthanasia each Month')
ax2.set_xticks(range(1, 13), labels=bar_labels)
ax2.set_xlabel('Month')
ax2.bar_label(p, fmt='%i', padding=-20, color='w')
ax2.set_yticks([])
ax2.set_ylim(ymin=0)
ax2.set_ylabel('Average Intake Count')
ax2.grid(visible=False, which='major', axis='both')
Z prvního grafu s klouzavým průměrem je možné vyčíst, že počet zákroků od roku 2013 klesá. Průměr 150 zákroků měsíčně kleslo na 50 zákroků.
Celkové mesíční průměry jsou ale stále vlivem starších hodnot vysoké. Nejvýšší frekvence zákroků je v březnu (jarní úklid? 🌱🧹), kdy průměrně 140 zvířat podstupuje eutanazií. Průměr je zhruba okolo 100 další 3 měsíce. Ve zbytku roku se pohybuje počet zákroků pod 90 měsíčně.