{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cc15dcb2",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "# Initialize Otter\n",
    "import otter\n",
    "grader = otter.Notebook(\"practice03.ipynb\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eb13d383-ae31-43f7-81f7-8bf29cba7ffe",
   "metadata": {},
   "source": [
    "**Student names and e-mails:**\n",
    "\n",
    "_YOUR NAME — your@calvin.edu_\n",
    "\n",
    "_YOUR NAME — your@calvin.edu_"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d3855c4-30e7-4e61-9689-edd5df2a1f8c",
   "metadata": {},
   "source": [
    "# Practice 03 — Reshaping and Joining Real-World Data\n",
    "\n",
    "*Adapted from an original practice by Prof. Ken Arnold.*\n",
    "\n",
    "In this practice you will work with the real [Gapminder](https://www.gapminder.org/data/) dataset — country-level GDP and life expectancy, tracked across decades. Each task is tagged with the SLO it covers:\n",
    "\n",
    "| SLO | Description |\n",
    "|-----|-------------|\n",
    "| **04A** | Describe the structure of relational data and identify key columns and relationships between tables |\n",
    "| **04B** | Join tables using different join types (inner, left, right, outer) and explain when each is appropriate |\n",
    "| **04C** | Reshape data between wide and long (tidy) formats |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00669178-d88f-4069-a18f-c224f3c59911",
   "metadata": {},
   "source": [
    "---\n",
    "## The Dataset: Gapminder\n",
    "\n",
    "![Global map](https://images.unsplash.com/photo-1502920514313-52581002a659?q=80&w=2067&auto=format&fit=crop)\n",
    "\n",
    "[Gapminder](https://www.gapminder.org/data/) is a Swedish foundation whose goal is to \"promote sustainable global development through the use of data and statistics.\" It's best known for the animated bubble charts that made Hans Rosling famous — the same charts you'll build at the end of this practice.\n",
    "\n",
    "Ordinarily, you'd visit gapminder.org, search for an indicator, and download a spreadsheet yourself. To keep this practice self-contained and reproducible, we've done that step for you: `plotly` — the same library you've used all semester — ships with a snapshot of this exact dataset built in (`px.data.gapminder()`), covering 142 countries from 1952 to 2007 in 5-year steps. Nothing to download."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "354aa5c7-1cb9-44da-b582-b01b9ad29f08",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import plotly.express as px"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fcd0c8ea-19d9-4f6a-a43c-97bb8f900720",
   "metadata": {},
   "source": [
    "### Setting Up Today's Tables (given — you don't need to modify this)\n",
    "\n",
    "The cell below builds two **wide** tables — `gdp_wide` and `life_wide`, one row per country, one column per year — formatted the way Gapminder's own website actually exports its data. Notice `gdp_wide` uses a `\"k\"` suffix for thousands (e.g. `\"5.9k\"` for $5,900 — a real quirk of Gapminder's downloads, not something we added). It also builds `country_region`, a small lookup table of each country's continent. Run it and look at the results — you don't need to modify any of this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e4f823e9-4e8c-4a93-8b0e-276e4593b5e8",
   "metadata": {},
   "outputs": [],
   "source": [
    "_gm = px.data.gapminder()\n",
    "\n",
    "def _format_gdp(x):\n",
    "    return f\"{x/1000:.1f}k\" if x >= 1000 else f\"{x:.0f}\"\n",
    "\n",
    "gdp_wide = (\n",
    "    _gm.assign(gdp_display=_gm[\"gdpPercap\"].map(_format_gdp))\n",
    "    .pivot(index=\"country\", columns=\"year\", values=\"gdp_display\")\n",
    "    .reset_index()\n",
    ")\n",
    "gdp_wide.columns.name = None\n",
    "\n",
    "life_wide = (\n",
    "    _gm.pivot(index=\"country\", columns=\"year\", values=\"lifeExp\")\n",
    "    .round(1)\n",
    "    .reset_index()\n",
    ")\n",
    "life_wide.columns.name = None\n",
    "\n",
    "# A real region lookup, as it would actually arrive: a handful of small\n",
    "# territories aren't covered at all, and two countries are spelled\n",
    "# differently than in the main indicator tables.\n",
    "country_region = _gm[[\"country\", \"continent\"]].drop_duplicates().reset_index(drop=True)\n",
    "country_region = country_region[\n",
    "    ~country_region[\"country\"].isin([\"Puerto Rico\", \"Hong Kong, China\", \"Reunion\"])\n",
    "].reset_index(drop=True)\n",
    "country_region[\"country\"] = country_region[\"country\"].replace({\n",
    "    \"Congo, Dem. Rep.\": \"Democratic Republic of the Congo\",\n",
    "    \"Korea, Rep.\": \"South Korea\",\n",
    "})\n",
    "\n",
    "gdp_wide.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b383f2e0-0180-4f42-bbea-fca456685744",
   "metadata": {},
   "outputs": [],
   "source": [
    "life_wide.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9e2d272-f0be-4d97-b227-451ae53d3d46",
   "metadata": {},
   "outputs": [],
   "source": [
    "country_region.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ab704d76-87a5-4d45-9a80-5357fbdcd82d",
   "metadata": {},
   "source": [
    "---\n",
    "## Part 1 — Reshaping: Wide to Long (SLO 04C)\n",
    "\n",
    "Both `gdp_wide` and `life_wide` currently answer \"what is one row about?\" with *one country's entire history*. To join or analyze this data year by year, we need one row to mean *one country, in one year* — the same melt you practiced on Monday."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c54742a-c166-4e68-8e03-6a333e67e75f",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "source": [
    "### Task 04C.1 — Melt the GDP Table *(1 pt)*\n",
    "\n",
    "Melt `gdp_wide` into long format. Every column except `\"country\"` should be unstacked into two new columns: `\"year\"` (from the column names) and `\"gdp_pcap\"` (from the cell values). Assign the result to `gdp_long`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "524983a1-18ca-4986-85c6-c63454e55444",
   "metadata": {
    "tags": [
     "otter_answer_cell"
    ]
   },
   "outputs": [],
   "source": [
    "gdp_long = ...\n",
    "    ...\n",
    "    id_vars= ...\n",
    "    var_name= ...\n",
    "    value_name= ...\n",
    "...\n",
    "gdp_long.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f8400fc",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "grader.check(\"04C.1\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd3d7b77-9c34-4a8e-9b5e-34abd71de326",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "source": [
    "### Task 04C.2 — Melt the Life Expectancy Table *(2 pts)*\n",
    "\n",
    "Now do the same for `life_wide`: melt it so that one row means *one country, in one year*, with a `\"year\"` column and a `\"life_exp\"` column. Assign the result to `life_long`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ac0e4849-4a5e-4bfd-a351-cb1a61141e20",
   "metadata": {
    "tags": [
     "otter_answer_cell"
    ]
   },
   "outputs": [],
   "source": [
    "life_long = ...\n",
    "    ...\n",
    "    id_vars= ...\n",
    "    var_name= ...\n",
    "    value_name= ...\n",
    "...\n",
    "life_long.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9681adae",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "grader.check(\"04C.2\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9668ebc2-d3a3-42a3-b086-0706b84580e6",
   "metadata": {},
   "source": [
    "### Adjusting Data Types (given — you don't need to modify this)\n",
    "\n",
    "Check `gdp_long.dtypes` and you'll find something odd: `year` isn't a number, and `gdp_pcap` is still text like `\"5.9k\"`. Both are artifacts of melting columns whose *names* were a mix of a string (`\"country\"`) and numbers (the years) — pandas can't assume they're all the same type. The `year` fix is one line each:\n",
    "\n",
    "```python\n",
    "gdp_long[\"year\"] = gdp_long[\"year\"].astype(int)\n",
    "life_long[\"year\"] = life_long[\"year\"].astype(int)\n",
    "```\n",
    "\n",
    "The `\"k\"` suffix in `gdp_pcap` needs a little more care — it means *thousands*, exactly as Gapminder's own website would export it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f489df6-7af9-4fad-8a86-5770f3a66b26",
   "metadata": {},
   "outputs": [],
   "source": [
    "gdp_long[\"year\"] = gdp_long[\"year\"].astype(int)\n",
    "life_long[\"year\"] = life_long[\"year\"].astype(int)\n",
    "\n",
    "def parse_number_with_units(num):\n",
    "    if not isinstance(num, str):\n",
    "        return num\n",
    "    if num.endswith(\"k\"):\n",
    "        return float(num[:-1]) * 1000\n",
    "    return float(num)\n",
    "\n",
    "gdp_long[\"gdp_pcap\"] = gdp_long[\"gdp_pcap\"].map(parse_number_with_units)\n",
    "gdp_long.tail()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a051830-104a-473a-9e67-930ec2692b10",
   "metadata": {},
   "source": [
    "---\n",
    "## Part 2 — Keys and Relational Structure (SLO 04A)\n",
    "\n",
    "You now have three tables: `gdp_long`, `life_long`, and `country_region`. Before joining anything, ask Wednesday's question: what column (or columns) reliably identifies \"which row is this about,\" in a way you can check against another table?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4b99544-beb2-4fa2-9eb2-11b1d61b3bad",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "source": [
    "### Task 04A.1 — A Key Can Be More Than One Column *(2 pts)*\n",
    "\n",
    "`country` alone does **not** uniquely identify a row of `gdp_long` — each country appears once per year. Check this two ways, using `.duplicated(subset=[...])`, which flags every row whose combination of values in those columns already appeared in an earlier row:\n",
    "\n",
    "1. Count how many rows share their `country` value with an earlier row: `gdp_long.duplicated(subset=[\"country\"]).sum()`. Assign it to `n_duplicate_country_rows`.\n",
    "2. Count the same thing for `[\"country\", \"year\"]` together: `gdp_long.duplicated(subset=[\"country\", \"year\"]).sum()`. Assign it to `n_duplicate_country_year_rows`.\n",
    "\n",
    "Which one is actually a valid key for this table — `country` alone, or the pair `[\"country\", \"year\"]`?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a963c923-112e-49dd-9d4b-e60d1b67cc3a",
   "metadata": {
    "tags": [
     "otter_answer_cell"
    ]
   },
   "outputs": [],
   "source": [
    "n_duplicate_country_rows = ...\n",
    "n_duplicate_country_year_rows = ...\n",
    "print(f'Duplicates on country alone: {n_duplicate_country_rows}')\n",
    "print(f'Duplicates on country + year: {n_duplicate_country_year_rows}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aba3e0ca",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "grader.check(\"04A.1\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c69a389d-3508-4236-8776-4e995ff35565",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "source": [
    "### Task 04A.2 — Which Countries Won't Find a Match? *(2 pts)*\n",
    "\n",
    "Before joining `gdp_long` to `country_region`, check whether every country in `gdp_long` actually has a matching row in `country_region`. Using set subtraction or `.isin()`, find every country that's in `gdp_long` but **not** in `country_region`. Assign the sorted list to `missing_from_region`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c2fa0054-2126-4339-bcb8-b6f9dd099c8d",
   "metadata": {
    "tags": [
     "otter_answer_cell"
    ]
   },
   "outputs": [],
   "source": [
    "missing_from_region = ...\n",
    "missing_from_region"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "049815d6",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "grader.check(\"04A.2\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4a2837d-499b-4a1a-a92e-d950d908e39d",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "source": [
    "### Task 04A.3 — Fix What Can Be Fixed *(2 pts)*\n",
    "\n",
    "Look closely at your list from Task 04A.2. Two of those five names — `\"Congo, Dem. Rep.\"` and `\"Korea, Rep.\"` — are really just *spelled differently* in `country_region` (as `\"Democratic Republic of the Congo\"` and `\"South Korea\"`). The other three simply aren't in `country_region` at all.\n",
    "\n",
    "1. Build a small lookup dictionary that renames `\"Democratic Republic of the Congo\"` → `\"Congo, Dem. Rep.\"` and `\"South Korea\"` → `\"Korea, Rep.\"` in `country_region`'s `country` column, the same kind of fix from Wednesday's class. Apply it with `.replace()` to a **copy** of `country_region`, and call the result `country_region_fixed`.\n",
    "2. Recompute the missing-countries check from Task 04A.2 against `country_region_fixed`. Assign the new (shorter) list to `still_missing_countries`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4262c6ab-c828-4cc7-9563-e2a1ee3cb04d",
   "metadata": {
    "tags": [
     "otter_answer_cell"
    ]
   },
   "outputs": [],
   "source": [
    "country_region_fixed = ...\n",
    "country_region_fixed[\"country\"] = ...\n",
    "    ...\n",
    "    ...\n",
    "...\n",
    "\n",
    "still_missing_countries = ...\n",
    "still_missing_countries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "368a8354",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "grader.check(\"04A.3\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f0d393e8-729e-4922-af62-ca0e6e7081f3",
   "metadata": {},
   "source": [
    "---\n",
    "## Part 3 — Joining (SLO 04B)\n",
    "\n",
    "Two joins left: combine `gdp_long` and `life_long` into one table, then attach `country_region_fixed` to bring in each country's continent."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfa74c2f-e16a-4284-8257-e271139690b3",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "source": [
    "### Task 04B.1 — Join GDP and Life Expectancy *(2 pts)*\n",
    "\n",
    "Join `gdp_long` and `life_long` on **both** `country` and `year` — the compound key from Task 04A.1. Every row in both tables has a match (they were built from the same source), so any `how=` would technically work here — use `\"inner\"`. Assign the result to `gapminder`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cfd7996b-c9ac-454c-9799-e4842b59b85b",
   "metadata": {
    "tags": [
     "otter_answer_cell"
    ]
   },
   "outputs": [],
   "source": [
    "gapminder = ...\n",
    "gapminder.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ee711f10",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "grader.check(\"04B.1\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "740695c4-034c-45fa-8972-bf51e8a448fd",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "source": [
    "### Task 04B.2 — Attach Regions Without Losing Data *(3 pts)*\n",
    "\n",
    "Join `gapminder` with `country_region_fixed` on `country`. Choose the `how=` that keeps **every** row of `gapminder`, even the countries `country_region_fixed` still doesn't cover — you don't want a garden coordinator, or a data analyst, silently losing real data because a lookup table was incomplete. Assign the result to `gapminder_with_regions`.\n",
    "\n",
    "Then count how many rows ended up with a missing `continent`. Assign the count to `n_missing_continent`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "89ece4e5-1929-4627-9305-c8fc1514340e",
   "metadata": {
    "tags": [
     "otter_answer_cell"
    ]
   },
   "outputs": [],
   "source": [
    "gapminder_with_regions = ...\n",
    "    gapminder, country_region_fixed, on= ...\n",
    "...\n",
    "n_missing_continent = ...\n",
    "print(f'Rows with no continent: {n_missing_continent}')\n",
    "gapminder_with_regions.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39985e47",
   "metadata": {
    "deletable": false,
    "editable": false
   },
   "outputs": [],
   "source": [
    "grader.check(\"04B.2\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5fd0e92c-a675-45ca-b0ff-ea8d8067519c",
   "metadata": {},
   "source": [
    "---\n",
    "## Just for Fun: The Famous Gapminder Chart\n",
    "\n",
    "*(Not graded — this is the payoff for all that reshaping and joining.)*\n",
    "\n",
    "This is the animated bubble chart that made Hans Rosling's TED talks famous — the same one, built from the same data you just wrangled by hand."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7226ac1e-4bec-49bc-a211-c5e40dcd8393",
   "metadata": {},
   "outputs": [],
   "source": [
    "px.scatter(\n",
    "    gapminder_with_regions,\n",
    "    x=\"gdp_pcap\",\n",
    "    y=\"life_exp\",\n",
    "    color=\"continent\",\n",
    "    animation_frame=\"year\",\n",
    "    hover_name=\"country\",\n",
    "    log_x=True,\n",
    "    range_x=[100, 100000],\n",
    "    range_y=[20, 90],\n",
    "    labels={\"gdp_pcap\": \"GDP per capita\", \"life_exp\": \"Life expectancy (at birth)\", \"continent\": \"Continent\"},\n",
    "    title=\"Life Expectancy vs. GDP per Capita, 1952-2007\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b1d27555-77f7-406e-b130-cfd4592cd38e",
   "metadata": {},
   "source": [
    "Look closely at what's *not* there: `px.scatter()` silently drops every row with a missing `color` value — so none of the 36 rows still missing a `continent` show up as a bubble at all, not even a gray or unlabeled one. Hong Kong, Puerto Rico, and Réunion are just... absent, every single year, with nothing in the chart to flag it. That's the same silent disappearance from Wednesday's class and this week's reading, now costing you a country in a chart instead of a row in a table."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2e09093",
   "metadata": {},
   "source": [
    "---\n",
    "## Submission\n",
    "\n",
    "Save your notebook, then upload the **`.ipynb` file** directly to our Moodle assignment page. Don't export, zip, or submit anything else — just the notebook."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.10.0"
  },
  "otter": {
   "OK_FORMAT": true,
   "assignment_name": "practice03",
   "tests": {
    "04A.1": {
     "name": "04A.1",
     "points": 2,
     "suites": [
      {
       "cases": [
        {
         "code": ">>> assert n_duplicate_country_rows > n_duplicate_country_year_rows, 'country alone should have far more duplicates than the country+year pair'\n>>> assert n_duplicate_country_year_rows == 0, 'country + year together should uniquely identify every row'\n",
         "hidden": false,
         "locked": false
        }
       ],
       "scored": true,
       "setup": "",
       "teardown": "",
       "type": "doctest"
      }
     ]
    },
    "04A.2": {
     "name": "04A.2",
     "points": 2,
     "suites": [
      {
       "cases": [
        {
         "code": ">>> assert isinstance(missing_from_region, list), 'missing_from_region should be a list'\n>>> assert len(missing_from_region) > 0, 'at least a few countries should be missing from country_region'\n",
         "hidden": false,
         "locked": false
        }
       ],
       "scored": true,
       "setup": "",
       "teardown": "",
       "type": "doctest"
      }
     ]
    },
    "04A.3": {
     "name": "04A.3",
     "points": 2,
     "suites": [
      {
       "cases": [
        {
         "code": ">>> assert len(still_missing_countries) < len(missing_from_region), 'fixing the two spelling mismatches should shrink the missing-countries list'\n>>> assert 'Congo, Dem. Rep.' not in still_missing_countries, 'Congo, Dem. Rep. should now be matched after the rename'\n",
         "hidden": false,
         "locked": false
        }
       ],
       "scored": true,
       "setup": "",
       "teardown": "",
       "type": "doctest"
      }
     ]
    },
    "04B.1": {
     "name": "04B.1",
     "points": 2,
     "suites": [
      {
       "cases": [
        {
         "code": ">>> assert set(gapminder.columns) == {'country', 'year', 'gdp_pcap', 'life_exp'}\n>>> assert len(gapminder) == len(gdp_long), 'every gdp_long row should have found a life_long match'\n",
         "hidden": false,
         "locked": false
        }
       ],
       "scored": true,
       "setup": "",
       "teardown": "",
       "type": "doctest"
      }
     ]
    },
    "04B.2": {
     "name": "04B.2",
     "points": 3,
     "suites": [
      {
       "cases": [
        {
         "code": ">>> assert len(gapminder_with_regions) == len(gapminder), 'gapminder_with_regions should keep every row from gapminder'\n>>> assert n_missing_continent > 0, 'some rows should still be missing a continent, from the 3 countries not in country_region_fixed'\n",
         "hidden": false,
         "locked": false
        }
       ],
       "scored": true,
       "setup": "",
       "teardown": "",
       "type": "doctest"
      }
     ]
    },
    "04C.1": {
     "name": "04C.1",
     "points": 1,
     "suites": [
      {
       "cases": [
        {
         "code": ">>> assert list(gdp_long.columns) == ['country', 'year', 'gdp_pcap'], 'gdp_long should have exactly the columns country, year, gdp_pcap'\n>>> assert len(gdp_long) == len(gdp_wide) * 12, 'gdp_long should have one row per country per year'\n",
         "hidden": false,
         "locked": false
        }
       ],
       "scored": true,
       "setup": "",
       "teardown": "",
       "type": "doctest"
      }
     ]
    },
    "04C.2": {
     "name": "04C.2",
     "points": 2,
     "suites": [
      {
       "cases": [
        {
         "code": ">>> assert list(life_long.columns) == ['country', 'year', 'life_exp'], 'life_long should have exactly the columns country, year, life_exp'\n>>> assert len(life_long) == len(life_wide) * 12, 'life_long should have one row per country per year'\n",
         "hidden": false,
         "locked": false
        }
       ],
       "scored": true,
       "setup": "",
       "teardown": "",
       "type": "doctest"
      }
     ]
    }
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
