PyMCDA를 처음 공부하는 한국인 학생을 위한 튜토리얼: 자동차 선택 예제
먼저 노트북을 설정하여 그래프를 그릴 수 있도록 하고(첫 번째 줄), 자동 완성 기능도 활성화합니다.
%matplotlib inline
%config Completer.use_jedi = False
모든 문제 해결의 시작은 문제를 명확히 하는 것입니다. 다기준 의사결정 분석(MCDA)에서는 여러 대안 중 하나를 선택하는 것이 문제일 수 있습니다.
alternatives = ["Fiat 500", "Peugeot 309", "Renault Clio", "Opel Astra", "Honda Civic", "Toyota Corolla"]
자동차를 선택할 때 고려해야 할 여러 기준을 다음과 같이 나열할 수 있습니다:
criteria = ["비용", "연료 소비", "편안함", "색상", "주행 거리"]
이러한 기준들은 서로 다른 척도를 가지며 일반적으로 이질적입니다:
따라서 해당 MCDA 문제를 위해 이러한 척도의 범위를 설정하고, 나중에 계산을 하려면 비수치적 기준 값을 수치 값으로 변환해야 합니다.
from mcda import PerformanceTable
from mcda.scales import *
scale1 = QuantitativeScale(6000, 20000, preference_direction=MIN)
scale2 = QuantitativeScale(4, 6, preference_direction=MIN)
scale3 = QualitativeScale({"*": 1, "**": 2, "***": 3, "****": 4})
scale4 = QualitativeScale(
{"red": 1, "blue": 2, "black": 3, "grey": 4},
preference_direction=MIN
)
scale5 = QuantitativeScale(400, 1000)
scales = {
criteria[0]: scale1,
criteria[1]: scale2,
criteria[2]: scale3,
criteria[3]: scale4,
criteria[4]: scale5
}
다음으로 각 대안과 기준에 대한 데이터를 성과표로 수집할 수 있습니다:
performance_table = PerformanceTable(
[
[9500, 4.2, "**", "blue", 450],
[15600, 4.5, "****", "black", 900],
[6800, 4.1, "***", "grey", 700],
[10200, 5.6, "****", "black", 850],
[8100, 5.2, "***", "red", 750],
[12000, 4.9, "****", "grey", 850]
],
alternatives=alternatives,
criteria=criteria,
scales=scales,
)
performance_table.data
| 비용 | 연료 소비 | 편안함 | 색상 | 주행 거리 |
|---|---|---|---|---|
| Fiat 500 | 9500 | 4.2 | ** | blue |
| Peugeot 309 | 15600 | 4.5 | **** | black |
| Renault Clio | 6800 | 4.1 | *** | grey |
| Opel Astra | 10200 | 5.6 | **** | black |
| Honda Civic | 8100 | 5.2 | *** | red |
| Toyota Corolla | 12000 | 4.9 | **** | grey |
의사 결정을 지향하려면 성과표와 같은 데이터를 시각화하는 것이 중요합니다.
from mcda.plot import *
fig = Figure(ncols=2, figsize=(6.4, 9.6))
x = [*range(len(alternatives))]
xticks = x
xticklabels = alternatives
for i in criteria:
values = performance_table.criteria_values[i].data
ax = fig.create_add_axis()
ax.title = i
ax.xlabel = "alternatives"
ax.ylabel = "performances"
yticks = None
yticklabels = None
y = values
if isinstance(scales[i], QualitativeScale):
y = [scales[i].value(v) for v in values]
yticklabels = scales[i].range()
yticks = [
scales[i].value(yy) for yy in yticklabels
]
elif isinstance(scales[i], NominalScale):
yticklabels = scales[i].range()
yticks = [*range(len(yticklabels))]
y = [yticks[yticklabels.index(v)] for v in values]
ax.add_plot(
BarPlot(
x,
y,
xticks=xticks,
yticks=yticks,
xticklabels=xticklabels,
yticklabels=yticklabels,
xticklabels_tilted=True
)
)
fig.draw()
이 그래프만으로 결정을 내리기는 어렵기 때문에 다단계 의사 결정 과정을 시작할 수 있습니다.
우리의 성과표에 있는 모든 성과가 수치적이지 않다는 것을 쉽게 확인할 수 있습니다. 이는 비교하기 어렵게 만듭니다:
performance_table.is_numeric
False
이러한 성과를 비교하기 위해, 모든 값을 수치 값으로 변환할 수 있습니다. mcda.transformers 모듈을 사용할 수도 있지만, PerformanceTable의 to_numeric 속성을 통해 간단히 수치 값으로 변환할 수 있습니다:
performance_table.to_numeric.data
| 비용 | 연료 소비 | 편안함 | 색상 | 주행 거리 |
|---|---|---|---|---|
| Fiat 500 | 9500 | 4.2 | 2 | 2 |
| Peugeot 309 | 15600 | 4.5 | 4 | 3 |
| Renault Clio | 6800 | 4.1 | 3 | 4 |
| Opel Astra | 10200 | 5.6 | 4 | 3 |
| Honda Civic | 8100 | 5.2 | 3 | 1 |
| Toyota Corolla | 12000 | 4.9 | 4 | 4 |
그러나 이러한 수치 값들의 범위는 여전히 크게 다릅니다. 또한 각 기준의 선호 방향도 다를 수 있습니다. 즉, 값을 정규화할 필요가 있습니다.
척도에는 이러한 작업에 필요한 모든 정보가 포함되어 있으며, 각 기준의 선호 순서도 포함되어 있습니다(예: 비용은 최소화, 주행 거리는 최대화 등). 이를 통해 정규화된 기준 값이 모두 증가하는 순서로 정렬되도록 정규화를 수행할 수 있습니다:
from mcda import normalize
normalized_table = normalize(performance_table)
normalized_table.data
| 비용 | 연료 소비 | 편안함 | 색상 | 주행 거리 |
|---|---|---|---|---|
| Fiat 500 | 0.750000 | 0.90 | 0.333333 | 0.666667 |
| Peugeot 309 | 0.314286 | 0.75 | 1.000000 | 0.333333 |
| Renault Clio | 0.942857 | 0.95 | 0.666667 | 0.000000 |
| Opel Astra | 0.700000 | 0.20 | 1.000000 | 0.333333 |
| Honda Civic | 0.850000 | 0.40 | 0.666667 | 1.000000 |
| Toyota Corolla | 0.571429 | 0.55 | 1.000000 | 0.000000 |
그런 다음 레이더 혹은 스파이더 플롯을 사용하여 대안 및 기준별로 모든 정규화된 값을 시각화하는 것이 좋습니다:
create_radar_projection(len(alternatives), frame='polygon')
fig = Figure(ncols=2, figsize=(6, 10))
for i in criteria:
values = normalized_table.criteria_values[i].data
ax = fig.create_add_axis(
projection=radar_projection_name(len(alternatives)))
ax.title = i
ax.add_plot(
RadarPlot(
alternatives,
values
)
)
fig.draw()
사용자가 기준 간에 우선순위가 없다면, 각 대안의 점수를 모든 기준 값을 합산하여 계산할 수 있습니다:
alternatives_values = normalized_table.sum(axis=1)
alternatives_values.data
Fiat 500 2.733333
Peugeot 309 3.230952
Renault Clio 3.059524
Opel Astra 2.983333
Honda Civic 3.500000
Toyota Corolla 2.871429
dtype: float64
우승자가 나왔습니다! 또는 적어도 사용자가 선택을 위해 참고할 수 있는 순위가 나왔습니다.
x = [*range(len(alternatives))]
plot = BarPlot(
x, alternatives_values.data, xticks=x,
xticklabels=alternatives, xticklabels_tilted=True
)
plot.draw()
plot.axis.title = "alternatives score"
plot.axis.xlabel = "alternatives"
plot.axis.ylabel = "score"
plot.axis.figure.draw()
그러나 사용자는 각 기준에 다른 중요도를 부여할 수도 있습니다.
기준의 중요도를 다르게 설정하는 가장 간단한 방법은 각 기준에 가중치를 정의하는 것입니다.
사용자는 자동차의 주행 거리에 매우 관심이 많으며, 편안함도 중요하지만 비용은 큰 문제가 아닙니다:
from mcda.mavt.aggregators import WeightedSum
weighted_sum = WeightedSum({
criteria[0]: 1,
criteria[1]: 2,
criteria[2]: 3,
criteria[3]: 2,
criteria[4]: 4
})
그런 다음 성과표에서 각 정규화된 기준 값을 해당 기준 가중치와 곱할 수 있습니다. 대안 및 기준별 점수를 얻을 수 있습니다:
weighted_table = weighted_sum.weight_functions(normalized_table)
weighted_table.data
| 비용 | 연료 소비 | 편안함 | 색상 | 주행 거리 |
|---|---|---|---|---|
| Fiat 500 | 0.750000 | 1.8 | 1.0 | 1.333333 |
| Peugeot 309 | 0.314286 | 1.5 | 3.0 | 0.666667 |
| Renault Clio | 0.942857 | 1.9 | 2.0 | 0.000000 |
| Opel Astra | 0.700000 | 0.4 | 3.0 | 0.666667 |
| Honda Civic | 0.850000 | 0.8 | 2.0 | 2.000000 |
| Toyota Corolla | 0.571429 | 1.1 | 3.0 | 0.000000 |
이 점수를 시각화하기 위해, 예를 들어 레이더 플롯을 사용할 수 있습니다:
create_radar_projection(len(criteria), frame='polygon')
fig = Figure(ncols=2, figsize=(6, 10))
for i in alternatives:
values = weighted_table.alternatives_values[i].data
ax = fig.create_add_axis(
projection=radar_projection_name(len(criteria)))
ax.title = i
ax.add_plot(
RadarPlot(
criteria,
values
)
)
fig.draw()
대안의 전체 점수를 계산하면 다른 순위가 나옵니다:
alternatives_values = weighted_table.sum(axis=1)
alternatives_values.data
Fiat 500 5.216667
Peugeot 309 8.814286
Renault Clio 6.842857
Opel Astra 7.766667
Honda Civic 7.983333
Toyota Corolla 7.671429
dtype: float64
x = [*range(len(alternatives))]
plot = BarPlot(
x, alternatives_values.data, xticks=x,
xticklabels=alternatives, xticklabels_tilted=True
)
plot.draw()
plot.axis.title = "alternatives score"
plot.axis.xlabel = "alternatives"
plot.axis.ylabel = "score"
plot.axis.figure.draw()
따라서 각 대안의 최종 점수와 순위를 시각화할 수 있습니다.