{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "04598fd5-c190-479e-a252-457a3ed02c52",
   "metadata": {},
   "source": [
    "# Stochastik programmieren"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b4163a3-d84b-42ec-b927-6888e3bc159b",
   "metadata": {},
   "source": [
    "Wir wollen uns nun kurz damit beschäftigen, wie sich mit Python Stichproben von Zufallsexperimenten simulieren lassen.\n",
    "\n",
    "## Wahrscheinlichkeitsmaß implementieren"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "1b9aa059-0486-4d8b-aff3-37a5f9cbb5d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "from fractions import Fraction\n",
    "\n",
    "def P(A, Omega):\n",
    "    \"\"\"Die Wahrscheinlichkeit für das Ereignis A,\n",
    "       gegeben gleich wahrscheinliche Ergebnisse aus einem Ergebnisraum Ω.\"\"\"\n",
    "    return Fraction(len(A & Omega), len(Omega))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2080149c-7800-4728-acb3-579a89f46971",
   "metadata": {},
   "source": [
    "Wir werden damit nun einen Würfelwurf programmieren:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "a03a347d-86fb-4098-98ba-c7c1cd3ed715",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Gerade Würfelaugen: {2, 4, 6}\n",
      "Wahrscheinlichkeit für gerade Augenzahl: 1/2\n"
     ]
    }
   ],
   "source": [
    "W = {1, 2, 3, 4, 5, 6}\n",
    "gerade = set((x*2 for x in range(1,4)))\n",
    "print(\"Gerade Würfelaugen:\",gerade)\n",
    "\n",
    "print(\"Wahrscheinlichkeit für gerade Augenzahl:\", P(gerade, W))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1a17807c-4adb-4482-8836-1a892b6af8d6",
   "metadata": {},
   "source": [
    "Damit haben wir nun das Wahrscheinlichkeitsmaß. Wenn wir eine Stichprobe ziehen wollen, müssen wir noch irgendwo den \"Zufall\" her bekommen.\n",
    "\n",
    "## Zufall importieren\n",
    "\n",
    "Python bietet mit dem `random`-Modul eine Schnittstelle zu Pseudozufallszahlen. Die Methode `random.random` ist ein direkt in C implementierter Mersenne Twister. Wenn man \"echte\" Zufallszahlen braucht, etwa für kryptografische Zwecke, gibt es dazu das `secrets`-Modul. Den Seed für den Mersenne Twister kann man angeben, und sollte man auch, um Zufallssimulationen reproduzierbar zu machen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "05a3b6f7-e7b6-4034-b3f9-807b20b47d6c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on built-in function random:\n",
      "\n",
      "random() method of random.Random instance\n",
      "    random() -> x in the interval [0, 1).\n",
      "\n",
      "0.7803255204450154\n",
      "0.13436424411240122\n"
     ]
    }
   ],
   "source": [
    "from random import random as r\n",
    "help(r)\n",
    "print(r())\n",
    "\n",
    "import random\n",
    "random.seed(1)\n",
    "very_random = r()\n",
    "print(very_random)\n",
    "assert very_random == 0.13436424411240122"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c862bd54-0c4f-4100-8654-4e86a6e3d3e1",
   "metadata": {},
   "source": [
    "![XKCD 221: Random Number](images/random_number.png \"RFC 1149.5 specifies 4 as the standard IEEE-vetted random number.\")\n",
    "\n",
    "[Link zum Comic (Randall Munroe, CC-BY-NC 2.5)](https://xkcd.com/221)\n",
    "\n",
    "Aus einer (Pseudo)zufallszahl zwischen $0$ und $1$ (man beachte: evtl. $0$ aber nie $1$) lassen sich zufällige Würfelwürfe erzeugen:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "c453b1a1-9693-401c-ab9e-fb39a13c81a2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[6, 5, 2, 3, 3, 4, 5, 1, 1, 6, 3, 5, 1, 3, 5, 2, 6, 6, 1, 1, 4, 6, 3, 2, 3, 1, 2, 3, 3, 2, 2, 2, 3, 2, 1, 6, 4, 4, 2, 6, 6, 1, 2, 5, 5, 6, 3, 5, 5, 2, 4, 6, 6, 4, 4, 1, 2, 5, 3, 2, 4, 5, 5, 3, 3, 4, 5, 4, 3, 3, 1, 1, 5, 6, 4, 3, 2, 4, 6, 5, 4, 6, 2, 4, 6, 4, 3, 2, 4, 6, 1, 5, 5, 6, 5, 5, 4, 4, 3, 1]\n"
     ]
    }
   ],
   "source": [
    "from math import floor\n",
    "def transform_unit_to_dice(x):\n",
    "    return floor(1 + 6*x)\n",
    "\n",
    "assert list(range(1,7)) == [transform_unit_to_dice((x-1)/6)\n",
    "                            for x in range(1,7)]\n",
    "\n",
    "print([transform_unit_to_dice(r()) for n in range(100)])\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "beac5e48-9b2d-410b-9ddd-a9898347d71b",
   "metadata": {},
   "source": [
    "Damit man solche Transformationen nicht andauernd programmieren muss, kann man hier auch auf `random.randint(1,6)` oder auch auf `random.choice(range(1,7))` oder `random.randrange(1,7)` zurückgreifen. Dabei ist `randint` ein Kürzel für das entsprechende `randrange` und `choice` ist etwas allgemeiner.\n",
    "\n",
    "Wir wollen aber festhalten: gegeben eine gleichverteilte \"Zufallsvariable\" $X=$`random.random` mit Werten in $[0,1)$ haben wir eine Abbildung $t =$`transform_unit_to_dice` konstruiert und implementiert, die Werte in $\\{1,2,3,4,5,6\\}$ hat und $t(X)$ ist gleichverteilt. Die mathematische Abbildung $t$ ist eine Zufallsvariable, wir behandeln die Verknüpfung $t \\circ X$ als Zufallsvariable, die den Würfel modelliert.\n",
    "\n",
    "Nun könnte man sich beschweren: `random.random()` nimmt gar keinen Parameter, ist also keine mathematische Abbildung von einem Definitionsbereich in die Menge $[0,1)$. Tatsächlich müssen wir uns vorstellen, dass es eine Abbildung $X \\colon \\Omega \\to [0,1)$ ist, und auf $\\Omega$ ein irgendwie geartetes Wahrscheinlichkeitsmaß definiert ist, sodass durch $X$ auf $[0,1)$ die Gleichverteilung induziert wird. Die Menge $\\Omega$ spielt für uns keine konkrete Rolle - da \"kommt der Zufall her\" und in der Notation `random.random()` sehen wir schon, dass wir eben kein konkretes Element von $\\Omega$ einsetzen, sondern pseudozufällig eins ziehen und das in $X$ einsetzen."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "77aab03c-2b4f-4e83-bf3c-bc7b8cb3d5f4",
   "metadata": {},
   "source": [
    "## Größere Stichproben"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2571d1b-b44c-436d-a72f-f8385a1e4255",
   "metadata": {},
   "source": [
    "Die Methode `random.sample(population, k)` erlaubt es eine Stichprobe der Größe $k$ aus einer Population (einer Urne) zu ziehen - ziehen mit Zurücklegen. Für $k=1$ entspricht das einer Gleichverteilung auf der Population.\n",
    "Mit der Methode `random.choices(population, weights=None, *, cum_weights=None, k=1)` kann man das ziehen aus der Population $k$-mal sampeln (und dabei anstelle einer geeigneten Population auch Gewichte vergeben)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}