Kaggle Tree EDA

Author

Daniil Solovjev

Знакомство с датасетом

Посмотрим на данные в датасете, рисунок: Figure 1.

Code
import os

import pandas as pd
from dotenv import load_dotenv

load_dotenv()
DATA_PATH = os.environ.get('KAGGLE_TREE_DATA', "data.csv")
df = pd.read_csv(DATA_PATH)
new_columns = ["_".join(c.split()) for c in df.columns]
df.columns = pd.Index(new_columns)
df.head()
tree_id block_id created_at tree_dbh stump_diam curb_loc status health spc_latin spc_common ... boro_ct state latitude longitude x_sp y_sp council_district census_tract bin bbl
0 180683 348711 2015-08-27T00:00:00.000 3 0 OnCurb Alive Fair Acer rubrum red maple ... 4073900 New York 40.723092 -73.844215 1027431.148 202756.7687 29.0 739.0 4052307.0 4.022210e+09
1 200540 315986 2015-09-03T00:00:00.000 21 0 OnCurb Alive Fair Quercus palustris pin oak ... 4097300 New York 40.794111 -73.818679 1034455.701 228644.8374 19.0 973.0 4101931.0 4.044750e+09
2 204026 218365 2015-09-05T00:00:00.000 3 0 OnCurb Alive Good Gleditsia triacanthos var. inermis honeylocust ... 3044900 New York 40.717581 -73.936608 1001822.831 200716.8913 34.0 449.0 3338310.0 3.028870e+09
3 204337 217969 2015-09-05T00:00:00.000 10 0 OnCurb Alive Good Gleditsia triacanthos var. inermis honeylocust ... 3044900 New York 40.713537 -73.934456 1002420.358 199244.2531 34.0 449.0 3338342.0 3.029250e+09
4 189565 223043 2015-08-30T00:00:00.000 21 0 OnCurb Alive Good Tilia americana American linden ... 3016500 New York 40.666778 -73.975979 990913.775 182202.4260 39.0 165.0 3025654.0 3.010850e+09

5 rows × 45 columns

Figure 1: Просмотр данных

Посмотрим более подробную информацию о датасете, рисунок: Figure 2.

Code
import io

buf = io.StringIO()
df.info(buf=buf)
lines = buf.getvalue().splitlines()[3:-2]
lines = lines[:1] + lines[2:]
line_list = [line.split()[1:3] + line.split()[4:5] for line in lines]
pd.DataFrame(data=line_list[1:], columns=line_list[0])
Column Non-Null Dtype
0 tree_id 683788 int64
1 block_id 683788 int64
2 created_at 683788 object
3 tree_dbh 683788 int64
4 stump_diam 683788 int64
5 curb_loc 683788 object
6 status 683788 object
7 health 652172 object
8 spc_latin 652169 object
9 spc_common 652169 object
10 steward 164350 object
11 guards 79866 object
12 sidewalk 652172 object
13 user_type 683788 object
14 problems 225844 object
15 root_stone 683788 object
16 root_grate 683788 object
17 root_other 683788 object
18 trunk_wire 683788 object
19 trnk_light 683788 object
20 trnk_other 683788 object
21 brch_light 683788 object
22 brch_shoe 683788 object
23 brch_other 683788 object
24 address 683788 object
25 postcode 683788 int64
26 zip_city 683788 object
27 community_board 683788 int64
28 borocode 683788 int64
29 borough 683788 object
30 cncldist 683788 int64
31 st_assem 683788 int64
32 st_senate 683788 int64
33 nta 683788 object
34 nta_name 683788 object
35 boro_ct 683788 int64
36 state 683788 object
37 latitude 683788 float64
38 longitude 683788 float64
39 x_sp 683788 float64
40 y_sp 683788 float64
41 council_district 677269 float64
42 census_tract 677269 float64
43 bin 674229 float64
44 bbl 674229 float64
Figure 2: Просмотр данных

Посмотрим по каким столбцам есть пропущенные значения, рисунок: Figure 3.

Code
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid")


nullable_df = df.isna().sum()
missing_data_percentage_df = nullable_df[nullable_df > 0] / len(df) * 100
missing_data_percentage_df = missing_data_percentage_df.sort_values()

_, ax = plt.subplots(1, 1, figsize=(10, 5), sharex=True)
x = missing_data_percentage_df.values
y = missing_data_percentage_df.index
sns.barplot(x=x, y=y, hue=y, palette="rocket", ax=ax, orient="y")
ax.set_ylabel("Столбцы")
ax.set_xlabel("Процент пропущенных значений, %")
plt.show()
Figure 3: Просмотр пропущенных значений в данных

Посмотрим на виды деревьев, рисунок: Figure 4.

Code
N_SPECIES = 10
tree_species = df["spc_common"].value_counts()[:N_SPECIES]
labels = list(tree_species.index) + ["other"]
other_value = sum(v for v in df["spc_common"].value_counts()[N_SPECIES:].values)
values = list(tree_species.values) + [other_value]
colors = sns.color_palette('pastel')
_, ax = plt.subplots(1, 1, figsize=(12, 8), sharex=True)
ax.pie(values, labels=labels, colors=colors, autopct='%.1f%%')
plt.show()
Figure 4: Диаграмма видов деревьев

Посмотрим на распределение источников данных, рисунок: Figure 5.

Code
_, ax = plt.subplots(1, 1, figsize=(9, 6), sharex=True)
data_sources = df["user_type"].value_counts()
x1 = data_sources.index
y1 = data_sources.values
sns.barplot(x=x1, y=y1, palette="rocket", hue=y1, ax=ax)
ax.set_ylabel("Количество зарегистрированных деревьев, ед.")
ax.set_xlabel("Тип источника данных")
plt.show()
Figure 5: Распределение источников данных

Посмотрим на распределение статуса деревьев, рисунок: Figure 6.

Code
_, ax = plt.subplots(1, 1, figsize=(9, 6), sharex=True)
statuses = df["status"].value_counts()
x2 = statuses.index
y2 = statuses.values
sns.barplot(x=x2, y=y2, palette="rocket", hue=y2, ax=ax)
ax.set_ylabel("Количество деревьев, ед.")
ax.set_xlabel("Статус дерева")
plt.show()
Figure 6: Распределение статуса деревьев

Анализ признаков

Проведем попарное сравнение некоторых признаков, рисунок: Figure 7.

Code
PAIRPLOT_FEATURES = ["tree_dbh", "stump_diam", "latitude", "longitude"]
PAIRPLOT_N_ROWS = int(len(df) * 0.1)
pairplot_df = df[PAIRPLOT_FEATURES].sample(PAIRPLOT_N_ROWS)

pairplot = sns.pairplot(pairplot_df, diag_kind="kde", corner=True)
Figure 7: Диаграмма попарного сравнения

Посмотрим на тепловую карту корреляций вещественных признаков, рисунок: Figure 8.

Code
FLOAT_FEATURES = ["tree_dbh", "stump_diam", "latitude", "longitude", "x_sp", "y_sp", "bbl", "bin"]
corr_df = df[FLOAT_FEATURES].corr()
_, ax = plt.subplots(figsize=(11, 9))
sns.heatmap(corr_df, annot=True, fmt=".1f", ax=ax)
plt.show()
Figure 8: Тепловая карта корреляции

Визуализация на карте

Посмотрим небольшую выборку деревьев на карте, рисунок: Figure 9.

Code
import folium  # type: ignore[import-untyped]
import numpy as np
from folium.plugins import GroupedLayerControl  # type: ignore[import-untyped]

MAP_N_ROWS = 500


def colorize_by_health(health_status: str) -> str: 
  """Get color by health status.
  
  :param health_status: 
  :return: color
  """
  color_dict = {
    "Fair": "green",
    "Good": "orange", 
    "Poor": "red" 
  }
  unknown_color = "gray" 
  return color_dict.get(health_status, unknown_color)

center = df["latitude"].mean(), df["longitude"].mean()
map = folium.Map(location=center, zoom_start = 10)

species_groups = dict()
for spec in tree_species.index:
  species_groups[spec] = folium.FeatureGroup(name=spec.lower())
other_group = folium.FeatureGroup(name='other')

indexes = np.random.choice(len(df), MAP_N_ROWS)
for idx in indexes:
  row = df.iloc[idx]
  popup = (
    f"tree_id={row.tree_id} "
    f"<br/> health={'Unknown' if pd.isna(row.health) else row.health} "
    f"<br/> status={row.status}"
  )
  location = [row.latitude, row.longitude]
  icon = folium.Icon(color = colorize_by_health(row.health))
  group = species_groups.get(row.spc_common, other_group)
  folium.Marker(location = location, popup = popup, icon = icon, fill_opacity = 0.9).add_to(group)
  
groups = list(species_groups.values()) + [other_group]
for group in groups:
  map.add_child(group)

GroupedLayerControl(
    groups={'Виды деревьев': groups},
    exclusive_groups=False,
    collapsed=False,
).add_to(map)
map
Make this Notebook Trusted to load map: File -> Trust Notebook
Figure 9: Карта распределения деревьев

Выводы

  • y_sp это преобразованный latitude
  • x_sp это преобразованный longitude
  • большинство деревьев живы