{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Lab\n",
    "\n",
    "- Use this assignment to practice the chapter's concepts and coding skills in runnable Python code.\n",
    "- Sign in from the account menu before you start working on the assignment.\n",
    "- Write and run your code directly in the coding cells.\n",
    "- Multiple submissions are allowed. The highest score is kept.\n",
    "\n",
    "<div data-lab-answers-release-at=\"2026-11-03T23:59-06:00\"></div>\n",
    "\n",
    "Follow an online store's experiment from group summaries to a simulated comparison and a qualified business conclusion. Choose the direction of the test and significance level before examining results. Each question includes its own setup.\n",
    "\n",
    "The seeded simulations make your results reproducible. Use unrounded values for comparisons and decisions. Question 4 retains a two-decimal display for its submitted output: a displayed `0.0` is a rounded estimate, not a claim that the true probability is zero. In an analysis report, retain more decimal places and state the repetition count.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Question 1: Comparing Two Groups\n",
    "# An online store showed checkout page A or B to customers at random and recorded\n",
    "# each order's value in dollars.\n",
    "#   1. Use groupby to compute the mean order value for each version.\n",
    "#      Keep full precision for calculations; round to 2 decimals when printing.\n",
    "#   2. Compute the observed test statistic: mean of B minus mean of A, rounded to 2 decimals.\n",
    "# Print exactly:\n",
    "# Mean A: 41.67\n",
    "# Mean B: 48.17\n",
    "# Observed difference: 6.5\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "ab = pd.DataFrame({\n",
    "    \"version\": [\"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\"],\n",
    "    \"order_value\": [42, 46, 38, 50, 45, 49, 40, 45, 44, 52, 41, 47],\n",
    "})\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "tags": [
     "hide-input",
     "lab-answer"
    ]
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": "Mean A: 41.67\nMean B: 48.17\nObserved difference: 6.5\n"
    }
   ],
   "source": [
    "### Solution\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "ab = pd.DataFrame({\n",
    "    \"version\": [\"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\"],\n",
    "    \"order_value\": [42, 46, 38, 50, 45, 49, 40, 45, 44, 52, 41, 47],\n",
    "})\n",
    "\n",
    "means = ab.groupby('version')['order_value'].mean()\n",
    "observed = round(means['B'] - means['A'], 2)\n",
    "\n",
    "print(\"Mean A:\", round(means['A'], 2))\n",
    "print(\"Mean B:\", round(means['B'], 2))\n",
    "print(\"Observed difference:\", observed)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Question 2: Conversion Rates\n",
    "# A second test tracked whether each visitor bought something (1) or not (0).\n",
    "#   1. Use groupby to compute the conversion rate (mean of converted) for each version.\n",
    "#   2. Compute the difference in rates, B minus A, rounded to 2 decimals.\n",
    "# Print exactly:\n",
    "# Rate A: 0.2\n",
    "# Rate B: 0.3\n",
    "# Difference: 0.1\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "visits = pd.DataFrame({\n",
    "    \"version\": [\"A\"] * 10 + [\"B\"] * 10,\n",
    "    \"converted\": [0, 1, 0, 0, 0, 1, 0, 0, 0, 0,\n",
    "                  1, 0, 0, 1, 0, 0, 1, 0, 0, 0],\n",
    "})\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "tags": [
     "hide-input",
     "lab-answer"
    ]
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": "Rate A: 0.2\nRate B: 0.3\nDifference: 0.1\n"
    }
   ],
   "source": [
    "### Solution\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "visits = pd.DataFrame({\n",
    "    \"version\": [\"A\"] * 10 + [\"B\"] * 10,\n",
    "    \"converted\": [0, 1, 0, 0, 0, 1, 0, 0, 0, 0,\n",
    "                  1, 0, 0, 1, 0, 0, 1, 0, 0, 0],\n",
    "})\n",
    "\n",
    "rates = visits.groupby('version')['converted'].mean()\n",
    "\n",
    "print(\"Rate A:\", rates['A'])\n",
    "print(\"Rate B:\", rates['B'])\n",
    "print(\"Difference:\", round(rates['B'] - rates['A'], 2))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Question 3: One Shuffle of the Labels\n",
    "# Continue the order-value comparison from Question 1.\n",
    "# Under the null model, labels can be rearranged while outcomes and group sizes stay fixed.\n",
    "#   1. Use rng.permutation(ab['version'].to_numpy()) to generate shuffled labels.\n",
    "#   2. Add them as a new column named 'shuffled'.\n",
    "#   3. Compute the difference in means, B minus A, using the shuffled labels,\n",
    "#      rounded to 2 decimals for printing.\n",
    "# Print exactly:\n",
    "# Shuffled difference: -3.17\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "ab = pd.DataFrame({\n",
    "    \"version\": [\"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\"],\n",
    "    \"order_value\": [42, 46, 38, 50, 45, 49, 40, 45, 44, 52, 41, 47],\n",
    "})\n",
    "rng = np.random.default_rng(10)\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "tags": [
     "hide-input",
     "lab-answer"
    ]
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": "Shuffled difference: -3.17\n"
    }
   ],
   "source": [
    "### Solution\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "ab = pd.DataFrame({\n",
    "    \"version\": [\"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\"],\n",
    "    \"order_value\": [42, 46, 38, 50, 45, 49, 40, 45, 44, 52, 41, 47],\n",
    "})\n",
    "rng = np.random.default_rng(10)\n",
    "shuffled_labels = rng.permutation(ab['version'].to_numpy())\n",
    "\n",
    "ab['shuffled'] = shuffled_labels\n",
    "shuffled_means = ab.groupby('shuffled')['order_value'].mean()\n",
    "\n",
    "print(\"Shuffled difference:\", round(shuffled_means['B'] - shuffled_means['A'], 2))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Question 4: Permutation Test Decision\n",
    "# Complete the store's test. The alternative says B raises order values.\n",
    "# Equivalently to shuffling six A and six B labels, randomly rearrange the values\n",
    "# and assign the first six to B and the remaining six to A.\n",
    "#   1. Start an empty list. Repeat repetitions times: use rng.permutation(values),\n",
    "#      compute (sum of first six - sum of remaining six) / group_size, and append it.\n",
    "#   2. Convert the list to a pandas Series. Compute the proportion >= observed.\n",
    "#   3. Compare the UNROUNDED estimate with the preselected 5% cutoff.\n",
    "#      Print the estimate rounded to 2 decimals to match this exercise's format.\n",
    "# The displayed 0.0 is rounded: it does not mean zero extreme results or zero\n",
    "# probability. Report more digits and repetitions outside this formatting exercise.\n",
    "# Print exactly:\n",
    "# P-value: 0.0\n",
    "# Decision: reject the null\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "rng = np.random.default_rng(42)\n",
    "values = np.array([42, 46, 38, 50, 45, 49, 40, 45, 44, 52, 41, 47])\n",
    "group_size = 6\n",
    "observed = 6.5\n",
    "repetitions = 5000\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "tags": [
     "hide-input",
     "lab-answer"
    ]
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": "P-value: 0.0\nDecision: reject the null\n"
    }
   ],
   "source": [
    "### Solution\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "rng = np.random.default_rng(42)\n",
    "values = np.array([42, 46, 38, 50, 45, 49, 40, 45, 44, 52, 41, 47])\n",
    "group_size = 6\n",
    "observed = 6.5\n",
    "repetitions = 5000\n",
    "\n",
    "simulated = []\n",
    "for _ in range(repetitions):\n",
    "    shuffled = rng.permutation(values)\n",
    "    difference = (shuffled[:group_size].sum() - shuffled[group_size:].sum()) / group_size\n",
    "    simulated.append(difference)\n",
    "simulated = pd.Series(simulated)\n",
    "p_value = (simulated >= observed).mean()\n",
    "if p_value < 0.05:\n",
    "    decision = 'reject the null'\n",
    "else:\n",
    "    decision = 'do not reject the null'\n",
    "print('P-value:', round(p_value, 2))\n",
    "print('Decision:', decision)\n",
    "# There are 11 extreme results in these 5,000 seeded repetitions: p = 0.0022.\n",
    "# The rounded display is 0.0; the unrounded estimate drives the decision.\n",
    "# As in any simulation, another seed produces a slightly different estimate.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Question 5: What Can Each Study Claim?\n",
    "# The store reviews three studies with consistent outcome measurement and\n",
    "# comparable follow-up. Assume no interference between participants, and\n",
    "# that the randomized studies were conducted as planned.\n",
    "# Classify each study:\n",
    "#   - p-value of 0.05 or more             -> \"no evidence of a difference\"\n",
    "#     (meaning insufficient evidence at this cutoff, not proof of no effect)\n",
    "#   - p-value below 0.05 and randomized   -> \"evidence of a causal effect\"\n",
    "#   - p-value below 0.05, not randomized  -> \"association only\"\n",
    "# Print one line per study, in table order.\n",
    "# Print exactly:\n",
    "# Coupon email: evidence of a causal effect\n",
    "# Loyalty members: association only\n",
    "# New layout: no evidence of a difference\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "studies = pd.DataFrame({\n",
    "    \"study\": [\"Coupon email\", \"Loyalty members\", \"New layout\"],\n",
    "    \"randomized\": [True, False, True],\n",
    "    \"p_value\": [0.01, 0.002, 0.34],\n",
    "})\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "tags": [
     "hide-input",
     "lab-answer"
    ]
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": "Coupon email: evidence of a causal effect\nLoyalty members: association only\nNew layout: no evidence of a difference\n"
    }
   ],
   "source": [
    "### Solution\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "studies = pd.DataFrame({\n",
    "    \"study\": [\"Coupon email\", \"Loyalty members\", \"New layout\"],\n",
    "    \"randomized\": [True, False, True],\n",
    "    \"p_value\": [0.01, 0.002, 0.34],\n",
    "})\n",
    "\n",
    "for study, randomized, p_value in zip(studies['study'], studies['randomized'], studies['p_value']):\n",
    "    if p_value >= 0.05:\n",
    "        claim = \"no evidence of a difference\"\n",
    "    elif randomized:\n",
    "        claim = \"evidence of a causal effect\"\n",
    "    else:\n",
    "        claim = \"association only\"\n",
    "    print(study + \":\", claim)\n",
    "# Customers who chose to join the loyalty program may differ in many ways from\n",
    "# those who did not, so even a tiny p-value shows association, not causation.\n",
    "\n",
    "# These labels assume the design and follow-up conditions stated in the question.\n",
    "# A causal effect concerns the study participants; generalizing needs justification.\n",
    "# A statistically detectable effect can still be too small to justify its costs.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<style>\n",
    ".ch10-lab-grader { max-width: 900px; margin-top: 1rem; }\n",
    ".ch10-lab-feedback-list { display: grid; gap: 0.45rem; margin: 0 0 1rem; }\n",
    ".ch10-lab-feedback-list[hidden] { display: none; }\n",
    ".ch10-lab-feedback-row { border: 1px solid #d8dee4; border-radius: 6px; padding: 0.55rem 0.65rem; }\n",
    ".ch10-lab-feedback-row strong { margin-right: 0.35rem; }\n",
    ".ch10-lab-feedback-row.correct { border-color: #aceebb; background: #dafbe1; color: #116329; }\n",
    ".ch10-lab-feedback-row.incorrect { border-color: #ffcecb; background: #ffebe9; color: #cf222e; }\n",
    ".ch10-lab-actions { display: flex; gap: 0.75rem; align-items: center; margin: 1rem 0 0; flex-wrap: wrap; }\n",
    ".ch10-lab-actions button { border: 1px solid #176de8; border-radius: 6px; background: #176de8; color: white; padding: 0.55rem 0.9rem; cursor: pointer; font-weight: 600; }\n",
    ".ch10-lab-actions button.secondary { background: white; color: #176de8; }\n",
    ".ch10-lab-actions.is-locked { opacity: 0.55; }\n",
    ".ch10-lab-actions button:disabled { cursor: not-allowed; opacity: 0.65; }\n",
    ".ch10-lab-result { font-weight: 700; }\n",
    "</style>\n",
    "\n",
    "<div class='ch10-lab-grader' id='ch10-lab-grader'>\n",
    "  <div class='ch10-lab-feedback-list' aria-live='polite' hidden>\n",
    "    <div class='ch10-lab-feedback-row' data-question='q1'><strong>Question 1</strong><span></span></div>\n",
    "    <div class='ch10-lab-feedback-row' data-question='q2'><strong>Question 2</strong><span></span></div>\n",
    "    <div class='ch10-lab-feedback-row' data-question='q3'><strong>Question 3</strong><span></span></div>\n",
    "    <div class='ch10-lab-feedback-row' data-question='q4'><strong>Question 4</strong><span></span></div>\n",
    "    <div class='ch10-lab-feedback-row' data-question='q5'><strong>Question 5</strong><span></span></div>\n",
    "  </div>\n",
    "  <div class='ch10-lab-actions'>\n",
    "    <button type='button' id='ch10-lab-submit' disabled>Submit</button>\n",
    "    <button type='button' class='secondary' id='ch10-lab-reset'>Clear Results</button>\n",
    "    <span class='ch10-lab-result' id='ch10-lab-result' aria-live='polite'></span>\n",
    "  </div>\n",
    "</div>\n",
    "\n",
    "<script>\n",
    "(function () {\n",
    "  var labId = 'ch10-lab';\n",
    "\n",
    "  function getParams() {\n",
    "    var params = new URLSearchParams(window.location.search);\n",
    "    return {\n",
    "      canvas_course_id: params.get('canvas_course_id') || params.get('course_id') || '',\n",
    "      canvas_assignment_id: params.get('canvas_assignment_id') || params.get('assignment_id') || '',\n",
    "      canvas_user_id: params.get('canvas_user_id') || params.get('user_id') || ''\n",
    "    };\n",
    "  }\n",
    "\n",
    "  function loadSessionIdentity(identity, actions, submit, result) {\n",
    "    submit.disabled = true;\n",
    "    actions.classList.add('is-locked');\n",
    "    result.textContent = 'Sign in from the account menu to submit this assignment.';\n",
    "\n",
    "    return fetch('/api/v1/session.php', { credentials: 'same-origin' })\n",
    "      .then(function (response) { return response.ok ? response.json() : null; })\n",
    "      .then(function (payload) {\n",
    "        if (!payload || !payload.authenticated || !payload.identity) return identity;\n",
    "        identity.canvas_user_id = payload.identity.canvas_user_id || identity.canvas_user_id || '';\n",
    "        identity.student_identifier = payload.identity.student_identifier || '';\n",
    "        submit.disabled = false;\n",
    "        actions.classList.remove('is-locked');\n",
    "        result.textContent = payload.identity.display_name ? 'Signed in as ' + payload.identity.display_name + '.' : 'Signed in.';\n",
    "        return identity;\n",
    "      })\n",
    "      .catch(function () { return identity; });\n",
    "  }\n",
    "\n",
    "  function codeFromCell(cell) {\n",
    "    var cm = cell.querySelector('.CodeMirror');\n",
    "    if (cm && cm.CodeMirror && typeof cm.CodeMirror.getValue === 'function') return cm.CodeMirror.getValue();\n",
    "    var cmContent = cell.querySelector('.cm-content');\n",
    "    if (cmContent && cmContent.cmView && cmContent.cmView.view) return cmContent.cmView.view.state.doc.toString();\n",
    "    var textarea = cell.querySelector('textarea');\n",
    "    if (textarea) return textarea.value;\n",
    "    var pre = cell.querySelector('.cell_input pre');\n",
    "    return pre ? pre.textContent : '';\n",
    "  }\n",
    "\n",
    "  function collectCodeCells() {\n",
    "    var cells = Array.prototype.slice.call(document.querySelectorAll('div.cell.tag_thebe-interactive')).slice(0, 5);\n",
    "    var code = {};\n",
    "    cells.forEach(function (cell, index) {\n",
    "      code['q' + (index + 1)] = codeFromCell(cell);\n",
    "    });\n",
    "    return code;\n",
    "  }\n",
    "\n",
    "  function applyFeedback(grader, feedback) {\n",
    "    var list = grader.querySelector('.ch10-lab-feedback-list');\n",
    "    list.hidden = false;\n",
    "    ['q1', 'q2', 'q3', 'q4', 'q5'].forEach(function (question) {\n",
    "      var row = grader.querySelector('[data-question=' + question + ']');\n",
    "      var message = row.querySelector('span');\n",
    "      var item = feedback[question];\n",
    "      row.className = 'ch10-lab-feedback-row';\n",
    "      if (!item) return;\n",
    "      message.textContent = item.message + ' (' + item.score + ' / ' + item.max_score + ')';\n",
    "      row.classList.add(item.correct ? 'correct' : 'incorrect');\n",
    "    });\n",
    "  }\n",
    "\n",
    "  function clearFeedback(grader) {\n",
    "    var list = grader.querySelector('.ch10-lab-feedback-list');\n",
    "    list.hidden = true;\n",
    "    grader.querySelectorAll('.ch10-lab-feedback-row').forEach(function (row) {\n",
    "      row.className = 'ch10-lab-feedback-row';\n",
    "      var message = row.querySelector('span');\n",
    "      if (message) message.textContent = '';\n",
    "    });\n",
    "  }\n",
    "\n",
    "  function saveAttempt(payload) {\n",
    "    return fetch('/api/v1/lab-attempts.php', {\n",
    "      method: 'POST',\n",
    "      headers: { 'Content-Type': 'application/json' },\n",
    "      credentials: 'same-origin',\n",
    "      body: JSON.stringify(payload)\n",
    "    }).then(function (response) {\n",
    "      return response.text().then(function (text) {\n",
    "        var payload = text ? JSON.parse(text) : null;\n",
    "        if (!payload || !response.ok || !payload.ok) {\n",
    "          throw new Error((payload && (payload.message || payload.error)) || 'Submission API is unavailable.');\n",
    "        }\n",
    "        return payload;\n",
    "      });\n",
    "    });\n",
    "  }\n",
    "\n",
    "  function initLabGrader() {\n",
    "    var grader = document.getElementById('ch10-lab-grader');\n",
    "    if (!grader || grader.dataset.ready === 'true') return;\n",
    "    grader.dataset.ready = 'true';\n",
    "\n",
    "    var identity = getParams();\n",
    "    var actions = grader.querySelector('.ch10-lab-actions');\n",
    "    var result = document.getElementById('ch10-lab-result');\n",
    "    var submit = document.getElementById('ch10-lab-submit');\n",
    "    var reset = document.getElementById('ch10-lab-reset');\n",
    "\n",
    "    loadSessionIdentity(identity, actions, submit, result);\n",
    "\n",
    "    submit.addEventListener('click', function () {\n",
    "      clearFeedback(grader);\n",
    "      submit.disabled = true;\n",
    "      result.textContent = 'Submitting code.';\n",
    "\n",
    "      saveAttempt({\n",
    "        lab_id: labId,\n",
    "        code: collectCodeCells(),\n",
    "        canvas_course_id: identity.canvas_course_id,\n",
    "        canvas_assignment_id: identity.canvas_assignment_id,\n",
    "        canvas_user_id: identity.canvas_user_id\n",
    "      })\n",
    "        .then(function (payload) {\n",
    "          var best = (payload.best_score === null || typeof payload.best_score === 'undefined') ? payload.score : payload.best_score;\n",
    "          var attempt = (payload.attempt_count === null || typeof payload.attempt_count === 'undefined') ? '' : ' Attempt ' + payload.attempt_count + '.';\n",
    "          applyFeedback(grader, payload.feedback || {});\n",
    "          result.textContent = 'Submission saved.' + attempt + ' Score: ' + payload.score + ' / ' + payload.max_score + '. Best score: ' + best + ' / ' + payload.max_score + '. Canvas sync: ' + payload.canvas_sync_status + '.';\n",
    "        })\n",
    "        .catch(function (error) {\n",
    "          result.textContent = 'Could not save this attempt: ' + error.message;\n",
    "        })\n",
    "        .finally(function () {\n",
    "          submit.disabled = false;\n",
    "        });\n",
    "    });\n",
    "\n",
    "    reset.addEventListener('click', function () {\n",
    "      clearFeedback(grader);\n",
    "      result.textContent = '';\n",
    "    });\n",
    "  }\n",
    "\n",
    "  if (document.readyState === 'loading') {\n",
    "    document.addEventListener('DOMContentLoaded', initLabGrader);\n",
    "  } else {\n",
    "    initLabGrader();\n",
    "  }\n",
    "})();\n",
    "</script>\n"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "main_language": "python",
   "notebook_metadata_filter": "-all"
  },
  "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.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
