Skip to content

Latest commit

 

History

History
622 lines (445 loc) · 23.8 KB

서울시 내 이륜차 안전문제해결을 위한 취약지점 분석.md

File metadata and controls

622 lines (445 loc) · 23.8 KB

서울시 내 이륜차 안전문제해결을 위한 취약지점 분석

(후방무인단속카메라 입지선정)

서울시 교통사고 현황

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

plt.rc('font', family="Malgun Gothic")

file = pd.read_csv("./서울시 교통사고 현황(구별) 통계 (2016 ~ 2020).txt", sep="\t", index_col=0, encoding="UTF-8",
                   names=["지역", "발생건수", "자동차 1만대당 발생건수", "사망자수", "인구 10만명당 사망자수", "부상자수", "인구 10만명당 부상자수"])
file.drop(columns=["부상자수", "인구 10만명당 부상자수", "자동차 1만대당 발생건수", "인구 10만명당 사망자수"], inplace=True)
file = file[file.지역 == "합계"]
file["발생건수"] = file["발생건수"].str.replace(",", "")

file2 = pd.read_excel("./서울시 이륜차 교통사고 현황.xls")
file2.set_index("연도", inplace=True)
file2.drop(columns=["부상자수"], inplace=True)

fig, axes = plt.subplots(1, 2, figsize=(23, 8), constrained_layout=True)

ax1, ax2 = axes[0], axes[0].twinx()

file.iloc[:, 1].astype(np.int32).plot(ax=ax1, linewidth=3, color="blue")
ax1.legend(["전체 사고건수"], loc="upper left")
ax1.set_ylabel("사고건수", rotation=0, labelpad=40, fontdict={"fontsize": 15})
ax1.set_xticks(range(2016, 2021))

file2.iloc[:, 0].astype(np.int32).plot(ax=ax2, linewidth=3, color="orange")
ax2.legend(["이륜차 사고건수"], loc="upper right")

ax3, ax4 = axes[1], axes[1].twinx()

file.iloc[:, 2].astype(np.int32).plot(ax=ax3, linewidth=3, color="blue")
ax3.legend(["전체 사고 사망자"], loc="upper left")
ax3.set_ylabel("사망자수", rotation=0, labelpad=30, fontdict={"fontsize": 15})
axes[0].set_title("서울시 연도별 교통사고 건수", fontdict={"fontsize": 20})
ax3.set_xticks(range(2016, 2021))

file2.iloc[:, 1].astype(np.int32).plot(ax=ax4, linewidth=3, color="orange")
ax4.legend(["이륜차 사고 사망자"], loc="upper right")
axes[1].set_title("서울시 연도별 교통사고 사망자", fontdict={"fontsize": 20})

plt.show()
  • 서울시의 전체 교통사고와 이륜차에만 해당하는 2016 ~ 2020까지 5개년 데이터
  • 전체 교통사고는 40000건에서 35000건 정도까지 감소추세를 보임
  • 이에 비해 이륜차 사고로 특정하면 약 5500건에서 8500건 이상까지 가파르게 증가하는 모습을 보임
  • 사망자 또한 사고 전체를 보면 감소하는 추세지만 이륜차 사고로 특정하면 변동이 거의 없음

사고발생 도로형태 빈도

plt.rc("font", family="Malgun Gothic")

df = pd.read_excel("./서울시 동별 이륜차 사고건수 (2017~2019).xlsx")
df.fillna(0, inplace=True)

df["도로형태"].value_counts().plot.bar(rot=15)
plt.title("이륜차 사고 도로형태", fontdict={"fontsize": 20})
plt.show()
  • 단일로 - 기타나 교차로(일반도로) 에서 대다수의 사고가 발생
  • 지하차도, 교량, 터널, 고가도로, 주차장 등(나머지도로) 에서는 거의 사고가 발생하지 않음
  • 도로개수를 count 할 때 일반도로에 비해 나머지도로는 적은 영향을 미쳐야한다고 판단

사고발생 도로형태 별 사고 비율

df = pd.read_excel(",/서울시 동별 이륜차 사고건수 (2017~2019).xlsx")
df.fillna(0, inplace=True)

data = df["도로형태"].value_counts().values
x = ["일반도로\n(교차로, 단일로)", "나머지도로\n(터널, 대교, 지하차도, \n고가도로, 주차장)"]

fig, axes = plt.subplots(1, 2, figsize=(25, 13))

# 일반도로의 중앙값과 나머지 도로
y = [np.median(data[:5]), np.sum(data[5:])]
df = pd.DataFrame(y, index=x, columns=["사고비율"])
df = df / (np.median(data[:5]) + np.sum(data[5:]))
df.plot.pie(rot=0, fontsize=15, subplots=True, autopct='%1.0f%%', ax=axes[0])
axes[0].set_title("도로형태별 사고 비율 (일반도로 median)", fontdict={"fontsize": 17})

# 일반도로의 평균값과 나머지 도로
y = [np.mean(data[:5]), np.sum(data[5:])]
df = pd.DataFrame(y, index=x, columns=["사고비율"])
df = df / (np.mean(data[:5]) + np.sum(data[5:]))
df.plot.pie(rot=0, fontsize=15, subplots=True, autopct='%1.0f%%', ax=axes[1])
axes[1].set_title("도로형태별 사고 비율 (일반도로 mean)", fontdict={"fontsize": 17})

plt.show()
  • 일반적으로 일반도로에서 발생하는 사고건수에 비해 나머지도로들에서 발생하는 사고건수의 비율
  • 8 ~ 10배 정도의 차이가 발생
  • 나머지 도로는 개수 count시 0.1로 계산

시간대별 이륜차 사고 비율

df = pd.read_excel("./서울시 동별 이륜차 사고건수 (2017~2019).xlsx")
df.fillna(0, inplace=True)

df["사고일시"] = df["사고일시"].apply(lambda x: x.split(" ")[3])
p_df = df["사고일시"].value_counts() / df.shape[0] * 100

q1 = p_df[p_df.cumsum() > 25].index[0]
q2 = p_df[p_df.cumsum() > 50].index[0]
q3 = p_df[p_df.cumsum() > 75].index[0]
q1_index = p_df.index.tolist().index(q1)
q2_index = p_df.index.tolist().index(q2)
q3_index = p_df.index.tolist().index(q3)

plt.plot(range(q1_index, 24), [p_df[p_df.index == q1]] * (24 - q1_index), "r--")
plt.text(14, p_df[p_df.index == q1], "누적 25%", fontdict={"fontsize" : 15, "fontfamily": "Malgun Gothic"})

plt.plot(range(q2_index, 24), [p_df[p_df.index == q2]] * (24 - q2_index), "r--")
plt.text(14, p_df[p_df.index == q2], "누적 50%", fontdict={"fontsize" : 15, "fontfamily": "Malgun Gothic"})

plt.plot(range(q3_index, 24), [p_df[p_df.index == q3]] * (24 - q3_index), "r--")
plt.text(14, p_df[p_df.index == q3], "누적 75%", fontdict={"fontsize" : 15, "fontfamily": "Malgun Gothic"})

p_df.plot.bar(rot=45)
plt.ylabel("(%)", labelpad=15, rotation=0, fontdict={"fontsize": 17})
plt.xlabel("(시간)", fontdict={"fontsize": 17})
plt.title("시간대별 이륜차 사고 비율", fontdict={"fontsize": 20})

plt.show()
  • 18 ~ 20시 저녁시간대에 가장 많은 사고가 일어남
  • 11 ~ 24시까지 이륜차 사고 전체의 75%가 발생

시간대별 교통량

import pandas as pd
import matplotlib.pyplot as plt

def avg_speed(path):
    global col
    for idx, file in enumerate(os.listdir(path)):
        df = pd.read_excel(os.path.join(path, file), sheet_name=1)
        match = df["지점명"].apply(
            lambda x: re.search(
                r"이륜차가 지나가지 못하는 지점들의 명칭",
                str(x)))
        filtered = match.apply(lambda x: x is None)
        df = df.loc[filtered, :]
        if idx == 0:
            col = df.columns[6:]
        all_df.append(df)
    result = pd.concat(all_df, axis=0)
    re_df = result.pivot_table(col, index=["지점번호", "방향"], aggfunc="mean")
    re_df = re_df.loc[:, col]
    re_df = re_df.reset_index("방향")
    sum_df = re_df[re_df["방향"] == "유입"] + re_df[re_df["방향"] == "유출"]
    sum_df = sum_df.loc[:, col]
    
    fig, axes = plt.subplots(1, 2, figsize=(32, 14))
    
    # 비율로 변환
    mean_df = sum_df.mean(axis=0) / sum_df.mean(axis=0).sum()
    mean_df *= 100
    mean_df.plot.bar(ax=axes[0], rot=45, fontsize=15)
    axes[0].plot(range(24), [100 / 24] * 24, "r--")
    axes[0].set_title("모든 구간의 시간별 평균", fontdict={"fontsize": 15})
    axes[0].set_ylabel("(%)", labelpad=13, rotation=0, fontdict={"fontsize": 13})
    axes[0].set_xlabel("(시간)", fontdict={"fontsize": 13})
    
    # 비율로 변환
    median_df = sum_df.median(axis=0) / sum_df.median(axis=0).sum()
    median_df *= 100
    median_df.plot.bar(ax=axes[1], rot=45, fontsize=15)
    axes[1].set_title("모든 구간의 시간별 중앙값", fontdict={"fontsize": 15})
    axes[1].plot(range(24), [100 / 24] * 24, "r--")
    axes[1].set_ylabel("(%)", labelpad=13, rotation=0, fontdict={"fontsize": 13})
    axes[1].set_xlabel("(시간)", fontdict={"fontsize": 13})
    
    plt.show()
  • 2020.07 ~ 2021.06까지 1년치의 데이터를 바탕으로 함
  • 서울시의 교통량 조사지점 중 이륜차가 지나지 못하는 지점을 제외한 나머지 지점들의 교통량에 대한 시간대별 평균, 중앙값
  • 두 경우 모두 07 ~ 21시에 평균치를 상회했고 06시와 22시도 평균치에 근접한 비율로 교통량이 발생했음

시간대별 통행속도

  • 통행량은 일부 지점에만 존재
  • 서울 연구원에서 진행한 교통량과 통행속도간의 연구를 바탕으로 서울시 전역에 대한 데이터가 존재하는 통행속도 데이터를 대체 통행량으로 사용
  • 중간중간 결측치가 존재하는 부분들은 제하고 평균치를 산정
  • 2020.07 ~ 2021.06까지 1년치의 데이터를 바탕으로 함

dir = "./서울시 차량 통행 속도"

def avg(path):
    global df, sub_df
    for idx, file in enumerate(os.listdir(path)):
        df = pd.read_excel(os.path.join(path, file))
        df = df[df.기능유형구분 != "도시고속도로"]
        sel_df = df.pivot_table(df.columns[8:].tolist() + ["차선수"], index="링크아이디", aggfunc="mean")
        if idx == 0:
            sum_df = sel_df
        else:
            sum_df += sel_df
    avg_df = sum_df / 12
    avg_df["평균"] = avg_df.mean(axis=1).values
    
    avg_df.to_csv("서울시 통행속도(new).csv", encoding="CP949")
  • 이륜차의 통행이 불가능한 도시고속도로를 제외
  • 나머지 도로에 대한 시간대별 평균치와 지정된 시간에 대한 평균치를 추출

나머지 변수들은 Q-GIS를 이용해 연산 or count 진행하여 최종적인 dataset (5369 x 25) 구성

상관관계 heatmap

import matplotlib.pyplot as plt
import pandas as pd

plt.figure(figsize=(13, 13))
sns.heatmap(df.corr(), annot=True, square=True, fmt='.2f', cmap=plt.cm.Blues)
plt.xticks(rotation=45)
plt.show()
  • 버스정류장, 교통안전시설물, 상권과 인구 등은 서로 간에 밀접한 관계가 있기 때문에 0.5 ~ 0.8의 강한 양의 상관관계가 나타난 것으로 보임
  • 도로 중에서도 일반도로의 75% 정도를 1, 2, 3차선이 차지하기 때문에 0.5 ~ 0.7의 강한 양의 상관관계가 나타난 것으로 보임
  • 또한 평균통행속도는 느릴수록 교통량이 많다는 가정에 따라 도로가 없는 곳은 통행속도를 10000이라는 매우 큰 값을 주었기에 일반도로와 -0.6 정도의 강한 음의 상관관계가 나타난 것으로 보임

다중공선성

import matplotlib.pyplot as plt
import pandas as pd
from statsmodels.stats.outliers_influence import variance_inflation_factor

def check_vif(dataset):
    vif = pd.DataFrame()
    vif["VIF Factor"] = [variance_inflation_factor(dataset, i) for i in range(dataset.shape[1])]
    vif["features"] = dataset.columns
    vif.index = vif["features"]
    vif.plot.bar(rot=25, fontsize=17)
    plt.title("다중공선성 (Random forest input features)", fontdict={"fontsize": 17})
    plt.show()
  • 결과 값이 도출되지 않은 변수들은 INF값을 가지는 경우

3-fold Cross validation

import matplotlib.pyplot as plt
import pandas as pd
from sklearn.model_selection import KFold
from sklearn.metrics import mean_squared_error

forest_r2 = []
forest_mse = []

svr_r2 = []
svr_mse = []

linear_r2 = []
linear_mse = []

# scaled_data - Standard Scaler로 스케일링 된 dataset
# target - ARI (종속변수)
for train_idx, val_idx in kf.split(scaled_data, target):
    train_x, train_y = scaled_data[train_idx], target.iloc[train_idx]
    val_x, val_y = scaled_data[val_idx], target.iloc[val_idx]

    forest.fit(train_x, train_y)
    svr.fit(train_x, train_y)
    linear.fit(train_x, train_y)

    # random forest regressor score
    f_r2 = forest.score(val_x, val_y)
    f_Adj_r2 = 1 - ((1 - f_r2) * (len(val_y) - 1) / (len(val_y) - val_x.shape[1] - 1))
    forest_r2.append([f_r2, f_Adj_r2])
    f_mse = mean_squared_error(val_y, forest.predict(val_x))
    forest_mse.append(f_mse)

    # svr score
    s_r2 = svr.score(val_x, val_y)
    s_Adj_r2 = 1 - ((1 - s_r2) * (len(val_y) - 1) / (len(val_y) - val_x.shape[1] - 1))
    svr_r2.append([s_r2, s_Adj_r2])
    s_mse = mean_squared_error(val_y, svr.predict(val_x))
    svr_mse.append(s_mse)

    # linear regressor score
    l_r2 = linear.score(val_x, val_y)
    l_Adj_r2 = 1 - ((1 - l_r2) * (len(val_y) - 1) / (len(val_y) - val_x.shape[1] - 1))
    linear_r2.append([l_r2, l_Adj_r2])
    l_mse = mean_squared_error(val_y, linear.predict(val_x))
    linear_mse.append(l_mse)
    
def show_annotation(fig):
    for bar in fig.patches:
        fig.annotate(round(bar.get_height(), 3),
                     (bar.get_x() + bar.get_width() / 2,
                      bar.get_height()), ha='center', va='center',
                     size=12, xytext=(-1, 8),
                     textcoords='offset points')


# R2 결과 시각화
fig, axes = plt.subplots(1, 3, figsize=(22, 13), constrained_layout=True)

forest_r2.append([np.array(forest_r2)[:, 0].mean(), np.array(forest_r2)[:, 1].mean()])
f_df = pd.DataFrame(forest_r2, columns=["Random Forest R2", "Random Forest Adjusted_R2"],
                    index=["fold 1", "fold 2", "fold 3", "Avg"])
f_df.plot.bar(title="Random Forest", ax=axes[0], rot=0)
axes[0].set_yticks(np.arange(0, 0.8, 0.1))

svr_r2.append([np.array(svr_r2)[:, 0].mean(), np.array(svr_r2)[:, 1].mean()])
s_df = pd.DataFrame(svr_r2, columns=["SVR R2", "SVR Adjusted_R2"], index=["fold 1", "fold 2", "fold 3", "Avg"])
s_df.plot.bar(title="Support Vector Machine (RBF Kernel))", ax=axes[1], rot=0)
axes[1].set_yticks(np.arange(0, 0.8, 0.1))

linear_r2.append([np.array(linear_r2)[:, 0].mean(), np.array(linear_r2)[:, 1].mean()])
l_df = pd.DataFrame(linear_r2, columns=["linear R2", "linear Adjusted_R2"], index=["fold 1", "fold 2", "fold 3", "Avg"])
l_df.plot.bar(title="Linear Regression", ax=axes[2], rot=0)
axes[2].set_yticks(np.arange(0, 0.8, 0.1))

for ax in axes.flatten():
    show_annotation(ax)

fig.suptitle("3-Fold Cross Validation (R2)", fontsize=15)
plt.show()

# MSE 결과 시각화
fig, axes = plt.subplots(1, 3, figsize=(22, 13), constrained_layout=True)

forest_mse.append(np.mean(forest_mse))
f_mse_df = pd.DataFrame(forest_mse, columns=["Mean squared error"],
                        index=["fold 1", "fold 2", "fold 3", "Avg"])
f_mse_df.plot.bar(title="Random Forest", ax=axes[0], rot=0)
axes[0].set_yticks(np.arange(0, 0.9, 0.1))

svr_mse.append(np.mean(svr_mse))
s_mse_df = pd.DataFrame(svr_mse, columns=["Mean squared error"],
                        index=["fold 1", "fold 2", "fold 3", "Avg"])
s_mse_df.plot.bar(title="Support Vector Machine (RBF kernel)", ax=axes[1], rot=0)
axes[1].set_yticks(np.arange(0, 0.9, 0.1))

linear_mse.append(np.mean(linear_mse))
l_mse_df = pd.DataFrame(linear_mse, columns=["Mean squared error"],
                        index=["fold 1", "fold 2", "fold 3", "Avg"])
l_mse_df.plot.bar(title="Linear Regression", ax=axes[2], rot=0)
axes[2].set_yticks(np.arange(0, 0.9, 0.1))

for ax in axes.flatten():
    show_annotation(ax)

fig.suptitle("3-Fold Cross Validation (MSE)", fontsize=15)
plt.show()
  • Linear regression, Random forest regressor, SVR (rbf kernel) 세 가지 모델을 사용
  • R-squared, Adjusted R-squared, Mean Squared Error (MSE) 세 가지 성능 평가 지표에 대해 교차 검증

feature selection (Linear regression)

df <- read.csv("./final_dataset.csv", fileEncoding="CP949")

target <- df[, "ARI"]
df <- df[, -c(1, 22)]

# standard scaling
df = data.frame(scale(df))

model <- lm(target ~ ., data=df)

summary(model)

new_model <- step(model, direction="both")

summary(new_model)
  • 단계적 선택법으로 적절한 변수 선택
  • 유의성 검증 결과 p-value가 높았던 변수들 제거
    • 어린이보호, 정류장, 신호등, 평균통행속도, 6차선, 7차선 , 등록이륜차, 일반도로, 경찰서

feature selection (Random forest)

import matplotlib.pyplot as plt
import pandas as pd
from sklearn.ensemble import RandomForestRegressor

forest = RandomForestRegressor(n_estimators=500, random_state=42)
forest.fit(scaled_data, target)
plt.figure(figsize=(20, 12))
plt.bar(range(scaled_data.shape[1]), forest.feature_importances_)
for x, fi in zip(range(scaled_data.shape[1]), forest.feature_importances_):
    plt.annotate(np.round(fi, 3),
                 (x, fi),
                 ha='center', va='center',
                 size=15, xytext=(-1, 8),
                 textcoords='offset points')
plt.xticks(range(scaled_data.shape[1]), df.columns, rotation=30, fontsize=15)
plt.title("Feature importance", fontdict={"fontsize": 17})
plt.show()

# Permutation importance
result = PermutationImportance(svr, scoring="r2", random_state=42).fit(scaled_data, target)
print(eli5.format_as_text(eli5.explain_weights(result, top=30, feature_names=df.columns.tolist())))
  • 중요도 하위 10개의 변수 제거
    • 무인단속카메라, 4차선, 어린이보호구역, 나머지도로, 5차선, 장애인보호구역, 경찰서, 도로없음, 6차선, 7차선

Select Optimal K

t_sne = TSNE(random_state=42)
df = t_sne.fit_transform(df)

plt.figure(figsize=(17, 12))
plt.rc('font', size=24)
ax1, ax2 = plt.gca(), plt.gca().twinx()

endpoint = 0
for k in range(2, 6):
    kmeans = KMeans(init="k-means++", random_state=42, n_clusters=k)
    kmeans.fit(df)
    label = kmeans.labels_
    inertias.append(kmeans.inertia_)
    silhouettes.append(silhouette_score(df, label))

line1 = ax1.plot(range(2, 6), inertias, "o-", color="b", linewidth=4.0, label="Inertia")
ax1.set_xlabel("K", labelpad=10, fontdict={"fontsize": 25})
ax1.set_ylabel("Inertia", labelpad=10, fontdict={"fontsize": 25})
line2 = ax2.plot(range(2, 6), silhouettes, "o-", color="r", linewidth=4.0, label="Silhouette_score")
ax2.set_ylabel("Silhouette_score", labelpad=10, fontdict={"fontsize": 25})
ax1.legend(line1 + line2, [x.get_label() for x in (line1 + line2)], fontsize=20)
ax2.annotate('Elbow',
             xy=(5, silhouettes[3]),
             xytext=(0.8, 0.4),
             textcoords='figure fraction',
             fontsize=20,
             arrowprops=dict(facecolor='black', shrink=0.1, width=3)
             )
plt.title("Select optimal K (K-means++)", fontdict={"fontsize": 30})
plt.savefig("k-means++ K.png")
  • T-SNE를 통해 고차원의 데이터셋을 2차원 데이터로 축소
  • 효율적인 시각화를 위해서는 5개 이하의 군집으로 설정하는 것이 좋다고 판단
  • K-means++가 Gaussian Mixture에 비해 전반적인 silhouette score가 높음
  • K-means++의 inertia가 비교적 원만하게 꺾이기 시작하고 (elbow 지점) GaussianMixture의 silhouette score가 0.4 정도로 비교적 높은 k(5)로 군집화

Clustering 결과 시각화

t_sne = TSNE(random_state=42)
decomp_data = t_sne.fit_transform(df)

kmeans5 = KMeans(init="k-means++", random_state=42, n_clusters=5)
kmeans5.fit(decomp_data)
K_label_5 = kmeans5.labels_


GM = GaussianMixture(n_components=5, n_init=10, random_state=42)
GM.fit(decomp_data)
G_label_5 = GM.predict(decomp_data)

fig, axes = plt.subplots(1, 2, figsize=(23, 10))

axes[0].scatter(decomp_data[:, 0], decomp_data[:, 1], c=K_label_5)
axes[0].set_title("K-means++ (5 cluster)", fontdict={"fontsize": 30})
axes[0].set_xticks([])
axes[0].set_yticks([])

axes[1].scatter(decomp_data[:, 0], decomp_data[:, 1], c=G_label_5)
axes[1].set_title("Gaussian Mixture (5 cluster)", fontdict={"fontsize": 30})
axes[1].set_xticks([])
axes[1].set_yticks([])

plt.show()

t_sne = TSNE(n_components=3, random_state=42)
decomp_data = t_sne.fit_transform(df)

kmeans5 = KMeans(init="k-means++", random_state=42, n_clusters=5)
kmeans5.fit(decomp_data)
K_label_5 = kmeans5.labels_


GM = GaussianMixture(n_components=5, n_init=10, random_state=42)
GM.fit(decomp_data)
G_label_5 = GM.predict(decomp_data)

fig, axes = plt.subplots(1, 2, figsize=(23, 10), subplot_kw={"projection": "3d"})

axes[0].scatter(decomp_data[:, 0], decomp_data[:, 1], decomp_data[:, 2], c=K_label_5)
axes[0].set_title("K-means++ (5 cluster)", fontdict={"fontsize": 30})
axes[0].set_xticks([])
axes[0].set_yticks([])

axes[1].scatter(decomp_data[:, 0], decomp_data[:, 1], decomp_data[:, 2], c=G_label_5)
axes[1].set_title("Gaussian Mixture (5 cluster)", fontdict={"fontsize": 30})
axes[1].set_xticks([])
axes[1].set_yticks([])

plt.show()

Cluster 별 평균치 확인

t_sne = TSNE(n_components=2, random_state=42)
decomp_data = t_sne.fit_transform(df)

kmeans5 = KMeans(init="k-means++", random_state=42, n_clusters=5)
kmeans5.fit(decomp_data)
K_label_5 = kmeans5.labels_

# Standard Scaling 역변환
inverse_df = scaler.inverse_transform(df)
inverse_df = pd.DataFrame(inverse_df, columns=df.columns)

# Min Max Scaling 적용
min_max = MinMaxScaler()
m_scaled = min_max.fit_transform(inverse_df)
m_scaled_df = pd.DataFrame(m_scaled, columns=df.columns)

m_scaled_df["cluster"] = K_label_5
m_scaled_df.pivot_table(df.columns[:-1], index="cluster", aggfunc="mean").plot.bar(width=0.7, cmap=plt.cm.tab10, rot=0, fontsize=17)

plt.xlabel("Cluster", fontdict={"fontsize": 17})
plt.title("Cluster별 평균치 (MinMaxScaled)", fontdict={"fontsize": 20})

plt.show()
  • 2와 3은 매우 높은 통행속도를 보이는데, 앞서 통행량을 구하기 위해 통행속도를 이용했으므로 통행속도가 매우 높다는 것은 교통량이 없음을 의미.
    • 주로 산, 강, 공원이나 인접지역
  • 4의 경우 통행속도가 매우 낮게 나오고, 다른 변수들은 평균적으로 높게 나타났습니다. 교통량도 많고, 인구도 많고, 다양한 시설이 있는 도심지역의 특성을 나타냄
    • 도심지역
  • 0과 1은 상대적으로 2와 3보다는 높지만 4에 비해서는 낮은 인구, 교통안전시설물, 교통량이 나타남
    • 주로 학교 및 아파트 단지