853 KiB
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
df = pd.read_csv("..//static//csv//DiamondsPrices2022.csv", index_col="Unnamed: 0")
print(df.columns, "\n")
print(df.info, "\n")
Бизнес цели
- Оптимизация ценообразования. Анализ между характиристик и цен. Это поможет опеделять цену камня в зависимости от его качеств.
- Разделение камней для разного сигмента рынка. В зависимости от характиристик камня делать его более доступным или премиальным.
Цели технического проекта
Для первой БЦ
- Разработка модели предсказания стоимости бриллианта
- Анализ факторов, влияющих на стоимость
Для второй БЦ
- Создание системы кластеризации бриллиантов
Нужно выявить какие проблемы есть в данных. Начнем с поиском зашумленности
for column in df.select_dtypes(include=['float64', 'int64']).columns:
plt.figure(figsize=(6, 4))
sns.histplot(df[column], kde=True)
plt.title(f'Шум в {column}')
plt.show
for column in ['cut', 'color', 'clarity']:
plt.figure(figsize=(6,4))
sns.countplot(data=df, x=column)
plt.title(f'Распределение {column}')
plt.show
Ищем выбросы
from scipy.stats import zscore
outliers = df[(zscore(df.select_dtypes(include=['float64', 'int64'])) > 3).any(axis=1)]
print(f"Количество выбросов (z-score): {len(outliers)}")
df_copy = df.copy()
for column in df.select_dtypes(include=['float64', 'int64']).columns:
median = df[column].median()
std_dev = df[column].std()
df_copy[column] = np.where(zscore(df[column]) > 3, median, df[column])
outliers_after = df_copy[(zscore(df_copy.select_dtypes(include=['float64', 'int64'])) > 3).any(axis=1)]
print(f"Количество выбросов после замены на медиану: {len(outliers_after)}")
Скорее всего тут реальные данные, поэтому убрав самые большие остальные оставим.
Ищем корреляции
correlations = df.select_dtypes(include=['float64', 'int64']).corr()['price'].sort_values(ascending=False)
print("Корреляция признаков с целевой переменной 'price':")
print(correlations)
plt.figure(figsize=(10, 6))
sns.heatmap(df.select_dtypes(include=['float64', 'int64']).corr(), annot=True, fmt=".2f", cmap="coolwarm")
plt.title("Корреляционная матрица")
plt.show()
df_dummies = pd.get_dummies(df, columns=['cut', 'color', 'clarity'], drop_first=True)
selected_columns = ['price'] + [col for col in df_dummies.columns if 'cut_' in col or 'color_' in col or 'clarity_' in col]
correlation_matrix = df_dummies[selected_columns].corr()
correlations = df_dummies[selected_columns].corr()['price'].sort_values(ascending=False)
print("Корреляция признаков с целевой переменной 'price' для 'cut', 'color', 'clarity':")
print(correlations)
plt.figure(figsize=(10, 6))
sns.heatmap(correlation_matrix, annot=True, fmt=".2f", cmap="coolwarm")
plt.title("Корреляционная матрица для 'cut', 'color', 'clarity' и 'price'")
plt.show()
Вывод такое:
Сильное влияние на цену оказывают характиристики 'carat', 'x', 'y', 'z'.
Низкое влияние оказывают 'table', 'cut', 'color', 'clarity'.
А признак 'depth' не оказывает влияни вовсе.
Выполним разбиение каждого набора данных на обучающую, контрольную и тестовую выборки для устранения проблемы просачивания данных
train_data, temp_data = train_test_split(df, test_size=0.3, random_state=42)
validation_data, test_data = train_test_split(temp_data, test_size=0.5, random_state=42)
train_size = len(train_data)
validation_size = len(validation_data)
test_size = len(test_data)
train_size, validation_size, test_size
Оцень сбалансированность выборки
def plot_category_balance(train_data, validation_data, test_data, column, title):
fig, ax = plt.subplots(1, 3, figsize=(18, 5), sharey=True)
train_data[column].value_counts(normalize=True).plot(kind='bar', ax=ax[0])
ax[0].set_title(f'Training {title}')
ax[0].set_ylabel('Proportion')
validation_data[column].value_counts(normalize=True).plot(kind='bar', ax=ax[1])
ax[1].set_title(f'Validation {title}')
test_data[column].value_counts(normalize=True).plot(kind='bar', ax=ax[2])
ax[2].set_title(f'Test {title}')
plt.suptitle(f'Category Balance for {title}')
plt.show()
plot_category_balance(train_data, validation_data, test_data, 'carat', 'Carat')
plot_category_balance(train_data, validation_data, test_data, 'cut', 'Cut')
plot_category_balance(train_data, validation_data, test_data, 'color', 'Color')
plot_category_balance(train_data, validation_data, test_data, 'clarity', 'Clarity')
Буду использовать увеличение выборки (Oversampling) для "Clarity"
from imblearn.over_sampling import RandomOverSampler
print("До Oversampling: ", train_data['clarity'].value_counts())
ros = RandomOverSampler(sampling_strategy='auto', random_state=42)
X_train = train_data.drop(columns=['clarity']) # все данные, кроме столбца clarity, то есть признаки, используемые для предсказания
y_train = train_data['clarity'] # целевой столбец clarity, который содержит классы, которые нужно сбалансировать
X_resampled, y_resampled = ros.fit_resample(X_train, y_train)
train_data_resampled = X_resampled.copy()
train_data_resampled['clarity'] = y_resampled
print("После Oversampling: ", train_data_resampled['clarity'].value_counts())
Используем Undersampling для "Cut"
from imblearn.under_sampling import RandomUnderSampler
print("До Undersampling: ", train_data_resampled['cut'].value_counts())
undersampler = RandomUnderSampler(sampling_strategy='auto', random_state=42)
X_train_cut = train_data_resampled.drop(columns=['cut'])
y_train_cut = train_data_resampled['cut']
X_resampled_cut, y_resampled_cut = undersampler.fit_resample(X_train_cut, y_train_cut)
train_data_resampled_cut = X_resampled_cut.copy()
train_data_resampled_cut['cut'] = y_resampled_cut
print("После Undersampling: ", train_data_resampled_cut['cut'].value_counts())
print("Столбцы после Undersampling:", train_data_resampled_cut.columns)
И увеличиваем количество меньших значений в 'carat' с помощью Oversampling
train_data_resampled_cut['carat_binned'] = pd.cut(train_data_resampled_cut['carat'], bins=5, labels=False)
X_train_carat = train_data_resampled_cut.drop(columns=['carat_binned'])
y_train_carat = train_data_resampled_cut['carat_binned']
oversampler = RandomOverSampler(sampling_strategy='auto', random_state=42)
X_resampled_carat, y_resampled_carat = oversampler.fit_resample(X_train_carat, y_train_carat)
train_data_resampled_carat = X_resampled_carat.copy()
train_data_resampled_carat['carat_binned'] = y_resampled_carat
train_data_resampled_carat['carat_binned'].value_counts().plot(kind='bar')
plt.title("Distribution of Carat Bins after Oversampling")
plt.xlabel("Carat Bin")
plt.ylabel("Frequency")
plt.show()
print("Столбцы после Oversampling:", train_data_resampled_carat.columns)
Конструирование признаков
from sklearn.preprocessing import OneHotEncoder, StandardScaler, MinMaxScaler
categorical_features = ['cut', 'color', 'clarity']
encoder = OneHotEncoder(sparse_output=False, drop='first')
encoded_data = pd.DataFrame(encoder.fit_transform(train_data_resampled_carat[categorical_features]))
encoded_data.columns = encoder.get_feature_names_out(categorical_features)
train_data_encoded = pd.concat([train_data_resampled_carat.reset_index(drop=True), encoded_data], axis=1)
print(train_data_encoded.head())
num_bins = 5
train_data_encoded['carat_binned'] = pd.cut(train_data_encoded['carat'], bins=num_bins, labels=False)
train_data_encoded['depth_binned'] = pd.cut(train_data_encoded['depth'], bins=num_bins, labels=False)
train_data_encoded['table_binned'] = pd.cut(train_data_encoded['table'], bins=num_bins, labels=False)
train_data_encoded['price_binned'] = pd.cut(train_data_encoded['price'], bins=num_bins, labels=False)
print(train_data_encoded[['carat', 'carat_binned', 'depth', 'depth_binned',
'table', 'table_binned', 'price', 'price_binned']].head())
Ручной синтез
data = train_data_encoded.copy()
data['price_per_carat'] = data['price'] / data['carat']
data['volume'] = data['x'] * data['y'] * data['z']
data['surface_area'] = data['table'] * data['depth'] / 100
data['cut_score'] = data['cut'].map({'Fair': 1, 'Good': 2, 'Very Good': 3, 'Premium': 4, 'Ideal': 5})
data['color_score'] = data['color'].map({'J': 1, 'I': 2, 'H': 3, 'G': 4, 'F': 5, 'E': 6, 'D': 7})
data['clarity_score'] = data['clarity'].map({'I1': 1, 'SI2': 2, 'SI1': 3, 'VS2': 4, 'VS1': 5, 'VVS2': 6, 'VVS1': 7, 'IF': 8})
data['quality_score'] = data['cut_score'] + data['color_score'] + data['clarity_score']
Масштабирование признаков
features_to_scale = ['carat', 'price', 'price_per_carat', 'volume', 'surface_area', 'quality_score']
# Стандартизация признаков
scaler_standard = StandardScaler()
data_standardized = pd.DataFrame(scaler_standard.fit_transform(data[features_to_scale]), columns=[f"{col}_standard" for col in features_to_scale])
# Нормализация признаков
scaler_minmax = MinMaxScaler()
data_normalized = pd.DataFrame(scaler_minmax.fit_transform(data[features_to_scale]), columns=[f"{col}_norm" for col in features_to_scale])
data = pd.concat([data.reset_index(drop=True), data_standardized, data_normalized], axis=1) # теперь их объеденяем
print(data.head())
import featuretools as ft
data = train_data_encoded.copy() # Используем предобработанные данные
es = ft.EntitySet(id="diamonds")
es = es.add_dataframe(dataframe_name="diamonds_data", dataframe=data, index="index")
feature_matrix, feature_defs = ft.dfs(
entityset=es,
target_dataframe_name="diamonds_data",
max_depth=2
)
print(feature_matrix.head())
Оцениваем качество каждого набора
В коде есть комментарии указывающие что мы сейчас оцениваем.
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.metrics import mean_squared_error, accuracy_score, f1_score
import time
categorical_features = ['cut', 'color', 'clarity']
encoder = OneHotEncoder(sparse_output=False, drop='first')
encoded_data = pd.DataFrame(encoder.fit_transform(data[categorical_features]))
encoded_data.columns = encoder.get_feature_names_out(categorical_features)
data_encoded = pd.concat([data.drop(columns=categorical_features), encoded_data], axis=1)
X = data_encoded.drop(columns=['price']) # Признаки
y = data_encoded['price'] # Целевая переменная
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 1. Оценка предсказательной способности
model = LinearRegression()
cv_scores = cross_val_score(model, X_train, y_train, cv=5, scoring='neg_mean_squared_error')
mean_mse = -np.mean(cv_scores)
print("Предсказательная способность (MSE):", mean_mse)
# 2. Оценка скорости вычисления
start_time = time.time()
model.fit(X_train, y_train)
train_time = time.time() - start_time
start_time = time.time()
y_pred = model.predict(X_test)
predict_time = time.time() - start_time
print("Скорость обучения:", train_time, "секунд")
print("Скорость предсказания:", predict_time, "секунд")
# 3. Оценка надежности
std_mse = np.std(-cv_scores)
print("Надежность (стабильность MSE):", std_mse)
# 4. Оценка корреляции
correlation_matrix = X.corr()
print("Корреляционная матрица признаков:\n", correlation_matrix)
# 5. Оценка цельности
print("Пропуски в данных:\n", data.isnull().sum())
print("Сводка по данным:\n", data.describe())
По итогу MSE у меня равен 769447.43, что можно считать относительно высоким. В последующих работах я буду лучше больше уделять времени на выборки данных, для повышения точности предсказаний.