PyMCDA - 다기준의사결정

Sylen·2024년 11월 3일

Dive to MCDA

목록 보기
1/7

PyMCDA를 처음 공부하는 한국인 학생을 위한 튜토리얼: 자동차 선택 예제

기본 예제: 자동차 선택하기

먼저 노트북을 설정하여 그래프를 그릴 수 있도록 하고(첫 번째 줄), 자동 완성 기능도 활성화합니다.

%matplotlib inline
%config Completer.use_jedi = False

문제 정의

모든 문제 해결의 시작은 문제를 명확히 하는 것입니다. 다기준 의사결정 분석(MCDA)에서는 여러 대안 중 하나를 선택하는 것이 문제일 수 있습니다.

alternatives = ["Fiat 500", "Peugeot 309", "Renault Clio", "Opel Astra", "Honda Civic", "Toyota Corolla"]

자동차를 선택할 때 고려해야 할 여러 기준을 다음과 같이 나열할 수 있습니다:

criteria = ["비용", "연료 소비", "편안함", "색상", "주행 거리"]

이러한 기준들은 서로 다른 척도를 가지며 일반적으로 이질적입니다:

  • 비용(유로)은 최소화해야 함
  • 연료 소비(L/100km)는 최소화해야 함
  • 편안함은 별점으로 평가되며, 최대화해야 함
  • 색상은 주관적이며, 여기서는 빨간색을 가장 선호하고 회색을 가장 싫어하는 순위로 지정
  • 주행 거리는 최대화해야 함

따라서 해당 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 50095004.2**blue
Peugeot 309156004.5****black
Renault Clio68004.1***grey
Opel Astra102005.6****black
Honda Civic81005.2***red
Toyota Corolla120004.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 모듈을 사용할 수도 있지만, PerformanceTableto_numeric 속성을 통해 간단히 수치 값으로 변환할 수 있습니다:

performance_table.to_numeric.data
비용연료 소비편안함색상주행 거리
Fiat 50095004.222
Peugeot 309156004.543
Renault Clio68004.134
Opel Astra102005.643
Honda Civic81005.231
Toyota Corolla120004.944

그러나 이러한 수치 값들의 범위는 여전히 크게 다릅니다. 또한 각 기준의 선호 방향도 다를 수 있습니다. 즉, 값을 정규화할 필요가 있습니다.

척도에는 이러한 작업에 필요한 모든 정보가 포함되어 있으며, 각 기준의 선호 순서도 포함되어 있습니다(예: 비용은 최소화, 주행 거리는 최대화 등). 이를 통해 정규화된 기준 값이 모두 증가하는 순서로 정렬되도록 정규화를 수행할 수 있습니다:

from mcda import normalize
normalized_table = normalize(performance_table)
normalized_table.data
비용연료 소비편안함색상주행 거리
Fiat 5000.7500000.900.3333330.666667
Peugeot 3090.3142860.751.0000000.333333
Renault Clio0.9428570.950.6666670.000000
Opel Astra0.7000000.201.0000000.333333
Honda Civic0.8500000.400.6666671.000000
Toyota Corolla0.5714290.551.0000000.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 5000.7500001.81.01.333333
Peugeot 3090.3142861.53.00.666667
Renault Clio0.9428571.92.00.000000
Opel Astra0.7000000.43.00.666667
Honda Civic0.8500000.82.02.000000
Toyota Corolla0.5714291.13.00.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()

따라서 각 대안의 최종 점수와 순위를 시각화할 수 있습니다.

profile
AI가 재밌는 걸

0개의 댓글