Die Monte-Carlo-Simulation ist eine der bekanntesten Simulationsmethoden. Mit der mathematischen Wahrscheinlichkeitstheorie als Grundlage, ist es ihr Ziel, Probleme mithilfe einer großen Zahl von Zufallsexperimenten zu lösen.
Mit zufällig gewählten Parametern und vorgegebenen Zusammenhängen wird ein bestimmtes Ergebnis angenähert. Während die Zusammenhänge deterministisch vorgegeben sind (z.B. im Folgenden Einnahmen = Absatz * Preis), haben die Parameter einen Zufallscharakter (im Folgenden der ungewisse Absatz). Dadurch hat auch die Zielgröße einen Zufallscharakter.
Gemäß dem Gesetz der großen Zahlen wird davon ausgegangen, dass eine ausreichende Anzahl an Simulationen einen guten Näherungswert der Zielgröße ergibt. Die Methode kam im 20.Jahrhundert auf und wird heute zur Approximation analytischer Probleme oder zur Simulation komplexer Prozesse benutzt; in der Finanzindustrie wird sie z.B. für das Risikomanagement verwendet.
Für diese Zufallsexperimente bietet sich auch R an, da es scheinbar zufällige Zahlen beliebig oft ausgeben kann. Im Folgenden ein einfaches Beispiel mit einem Zweiproduktfall, bei welchem die Einnahmen angenähert werden sollen. Dies habe ich als interaktives Shiny-Dashboard aufbereitet.
Das folgende Beispiel ist ein Zusatz zu meinem Artikel über den Break-Even-Absatz im Mehrproduktfall. Nun geht es darum herauszufinden, wie ich trotz ungewissen Absatz trotzdem eine Planung erstellen kann. Hier wollen wir den einmaligen Umsatz herausbekommen (also keine zeitliche Fortschreibung über mehrere Perioden hinweg). Im Folgenden haben wir zwei Produkte, für welche wir einen Preis setzen müssen. Da der Absatz unsicher ist, können wir ihn über eine Monte-Carlo-Simulation annähern. Hinterlegt mit einer Normalverteilung müssen wir hierzu den Durchschnitt und die Standardabweichung angeben, die wir zB aus einer Preis-Absatz-Funktion bekommen können. Die Standardabweichung ist - simpel gesagt - die durchschnittliche Entfernung aller Ausprägungen vom Durchschnitt. Bei Normalverteilungen gilt die Faustformel, dass 68% aller Werte innerhalb der Entfernung einer Standardabweichung nach oben und unten vom Mittelwert liegen (und 95% innerhalb der Entfernung von zwei Standardabweichungen).
Was macht die Monte-Carlo-Simulation damit dann konkret?
Die Simulation spielt die hinterlegte Rechnung n Mal durch; mit n als der im Slider angegebenen Zahl. Dabei wird jedes Mal ein anderer zufälliger Wert für den Absatz gemäß der mit den Parametern angegebenen Verteilung genommen. Diese Werte für den Absatz werden schließlich mit dem Preis multipliziert. Die Menge an Ergebnissen, die wir als Resultat bekommen, wird schließlich dargestellt: die x-Achse sind die Einnahmen und die y-Achse die Anzahl an Ergebnissen mit diesem Wert. Die Grafik zeigt uns also, welcher Wert am häufigsten erzielt wurde (der Hochpunkt) als auch die Verteilung um diesen Wert herum. Daraus lässt sich wiederum die Wahrscheinlichkeit herleiten als auch der Bereich, der zu 95% herauskommt (wird hier aber nicht angezeigt). Das Ganze wird für die zwei Produkte A und B gemacht und schließlich werden noch die Gesamteinnahmen als deren Summe berechnet.
R Shiny ist eine Erweiterung/ein Paket für R, um interaktive Dashboards zu bauen. Diese können auch auf einem Webserver hochgeladen und aufgerufen werden. Im Video ist das lokale Popup-Fenster von RStudio zu sehen; hier der relativ einfache Code für das Dashboard:
library(shiny)
library(ggplot2)
library(dplyr)
library(reshape2)
# UI for the application
ui <- fluidPage(
titlePanel("Simulation"), # Application title
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(id = "tPanel", style = "overflow-y:scroll; max-height: 600px; position:relative;",
h4("Product A"),
#numericInput("price_a", label="Preis", value=150, min = 0, max = NA),
#sliderInput("n", "Number of simulations:", min = 500, max = 10000, value = 4000),
fluidRow(
div(style="display: inline-block; width: 5%;"),
div(style="display: inline-block; width: 30%;", h5("Preis")),
div(style="display: inline-block; width: 40%;", numericInput("price_a", label="Preis", value=15, min = 0, max = NA))
),
fluidRow(
div(style="display: inline-block; width: 5%;"),
div(style="display: inline-block; width: 30%;", h5("Absatz")),
div(style="display: inline-block; width: 25%;", numericInput("mean_sales_a", label = "mean", value = 120)),
div(style="display: inline-block; width: 25%;", numericInput("sd_sales_a", label = "sd", value = 12)),
),
br(),
br(),
h4("Product B"),
#numericInput("price_a", label="Preis", value=150, min = 0, max = NA),
#sliderInput("n", "Number of simulations:", min = 500, max = 10000, value = 4000),
fluidRow(
div(style="display: inline-block; width: 5%;"),
div(style="display: inline-block; width: 30%;", h5("Preis")),
div(style="display: inline-block; width: 40%;", numericInput("price_b", label="Preis", value=11, min = 0, max = NA))
),
fluidRow(
div(style="display: inline-block; width: 5%;"),
div(style="display: inline-block; width: 30%;", h5("Absatz")),
div(style="display: inline-block; width: 25%;", numericInput("mean_sales_b", label = "mean", value = 90)),
div(style="display: inline-block; width: 25%;", numericInput("sd_sales_b", label = "sd", value = 9)),
),
br(),
sliderInput("n", "Number of simulations:", min = 500, max = 10000, value = 4000),
submitButton("Submit")
),
# Showing the plots
mainPanel(
tabsetPanel(
tabPanel("Einnahmen gesamt",
plotOutput("total")
),
tabPanel("Einnahmen nach Produkten",
plotOutput("products"))
)
)
)
)
# Define server logic
server <- function(input, output) {
calc <- reactive({
set.seed(283) # for reproducibility
results = NULL
sales_a = rnorm(input$n, mean = input$mean_sales_a, sd=input$sd_sales_a) #normal distribution
contribution_a = sales_a * input$price_a
sales_b = rnorm(input$n, mean = input$mean_sales_b, sd=input$sd_sales_b) #normal distribution
contribution_b = sales_b * input$price_b
contribution_total = contribution_a + contribution_b
results = rbind(results, data.frame(contribution_a, contribution_b, contribution_total)) %>%
mutate(index = row_number()) %>%
as.data.frame() %>%
melt(. , id.vars="index")
})
output$total <- renderPlot({
calc() %>%
filter(variable == "contribution_total") %>%
ggplot(aes(x=value)) +
geom_histogram(aes(y=..density..), binwidth = 10, fill="white", color="grey") +
geom_density(color="black", size=1) +
geom_vline(aes(xintercept=mean(value)), color="blue", linetype="dashed", size=1) +
geom_text(aes(x=mean(value), label=round(mean(value),0), y=0), colour="blue", text=element_text(size=11)) +
scale_fill_grey() +
theme_classic()
})
output$products <- renderPlot({
calc() %>%
filter(variable != "contribution_total") %>%
ggplot(aes(x=value, color=variable)) +
geom_histogram(aes(y=..density..), binwidth = 10, fill="white") +
geom_density(size=1) +
scale_fill_grey() +
theme_classic()
})
}
# Run the application
shinyApp(ui = ui, server = server)