Перейти к содержимому

RussianCities

RussianCities — встроенный датасет с данными о российских городах.

Он подходит для анализа городов по числовым характеристикам: населению, площади, географическому положению и году основания. В отличие от датасетов для классификации или регрессии, здесь нет заранее заданного ответа, который нужно предсказать.

Файл данных:

russian_cities.csv

В программе датасет доступен так:

var ds := Datasets.RussianCities;
var df := ds.Data;

В таблице есть признаки, описывающие город:

  • city — название города;
  • region — регион;
  • population — население;
  • area — площадь;
  • lat, lon — географические координаты;
  • founded — год основания.

В RussianCities нет столбца вроде target, class или price, который задаёт правильный ответ для обучения модели.

Это значит, что здесь не нужно предсказывать заранее известный класс или число. Вместо этого можно искать структуру в самих данных: например, группы городов, похожих по населению и плотности. Такая задача относится к кластеризации.

Представим, что у нас есть таблица городов, но нет заранее заданных классов вроде «крупный город», «малый город» или «плотный город». Эти группы нужно обнаружить по данным.

Например, можно попробовать разделить города по двум признакам:

  • численность населения;
  • плотность населения.

Но исходное население городов имеет очень большой разброс: от небольших городов до миллионников. Поэтому для кластеризации удобнее использовать не само население, а его логарифм:

log_population = Ln(population)

Также полезно добавить новый признак density — плотность населения:

density = population / area

После этого можно искать группы городов с помощью KMeans.

В этом примере мы сначала создаём два новых признака.

density показывает плотность населения: сколько жителей приходится на единицу площади города. Этот признак помогает отличать компактные плотные города от более “разреженных”.

log_population — логарифм населения. Он нужен потому, что население городов отличается очень сильно: небольшие города и миллионники находятся в совершенно разных масштабах. Логарифм сглаживает этот разброс и делает сравнение городов более устойчивым.

Эти новые признаки лучше описывают задачу кластеризации, чем сырые столбцы population и area по отдельности. После этого мы масштабируем признаки и применяем KMeans с тремя кластерами.

uses MLABC;
begin
var ds := Datasets.RussianCities;
var df := ds.Data;
df := df.WithColumnFloat(
'density',
row -> row.Float('population') / row.Float('area')
);
df := df.WithColumnFloat(
'log_population',
row -> Ln(row.Float('population'))
);
var features := ['log_population', 'density'];
var pipe :=
DataPipeline.BuildClustering(
features,
new StandardScaler,
new KMeans(3, seed := 42)
);
pipe.Fit(df);
// Вычисляем номера кластеров
var labels := pipe.Predict(df);
// Добавляем к DataFrame столбец с номером кластера
df := df.WithColumnInt('cluster', labels);
// Группируем города по кластерам
var clusters := df.GroupBy('cluster').Groups;
foreach var cluster in clusters do
begin
Println;
Println($'Кластер {cluster.Key.Int + 1}:');
cluster.Data
.SortBy('population', descending := True)
.Select(['city', 'population', 'density'])
.Head(5)
.Print;
end;
end.

Вывод:

Кластеризация городов с помощью DataPipeline
Используемые признаки: [log_population,density]
Число найденных кластеров: 3
Кластер 1:
city population density
Березники 139.20 0.32
Новый Уренгой 118.00 0.52
Новошахтинск 106.60 0.78
Северск 106.50 0.22
Артем 105.70 0.86
Кластер 2:
city population density
Москва 12655.10 4.94
Санкт-Петербург 5384.30 3.74
Новосибирск 1625.60 3.23
Екатеринбург 1493.70 1.34
Казань 1257.40 2.13
Кластер 3:
city population density
Балашиха 507.40 7.52
Подольск 308.10 7.63
Мытищи 235.50 6.81
Королев 225.90 6.28
Люберцы 205.30 15.95

KMeans(3) не знает заранее, какие группы городов нужно найти. Он просто делит объекты на три кластера так, чтобы города внутри одного кластера были похожи по выбранным признакам.

После кластеризации нужно посмотреть на сами кластеры и дать им содержательную интерпретацию. В таком учебном варианте обычно получаются группы вроде:

  • крупные города;
  • компактные города с высокой плотностью;
  • малые и средние города.

Номера кластеров 0, 1, 2 сами по себе ничего не означают. Это просто номера найденных групп. Смысл кластеров появляется только после анализа: сколько в них городов, какое среднее население, какая плотность и какие города попали в каждую группу.

KMeans сравнивает объекты по расстоянию. Если один признак имеет большие численные значения, он может начать слишком сильно влиять на результат.

Поэтому перед KMeans признаки обычно масштабируют. В примере это делает StandardScaler внутри конвейера:

new StandardScaler

Конвейер сначала масштабирует признаки, а затем передаёт подготовленные числовые данные в модель кластеризации.