{
 "nbformat": 4,
 "nbformat_minor": 5,
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  },
  "colab": {
   "provenance": []
  }
 },
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cell-0000",
   "metadata": {},
   "source": [
    "# Variables & Data Types\n",
    "\n",
    "Master Python's core data types — numbers, strings, booleans, lists, tuples, dictionaries, and sets — the building blocks of every dataset you'll ever touch.\n",
    "\n",
    "*Part of the free [Python for Data Science](https://ramadnsyh.dev/courses/python-for-data-science) course by [Muhammad Ramadiansyah](https://ramadnsyh.dev). This notebook is generated from the interactive lesson — [read it online](https://ramadnsyh.dev/courses/python-for-data-science/variables-and-types).*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0001",
   "metadata": {},
   "source": [
    "Every dataset you will ever analyze is built from a handful of basic Python\n",
    "types: numbers, text, true/false values, and containers that group them\n",
    "together. In this lesson you'll learn each type, how to inspect and convert\n",
    "between them, and how to reach into nested structures — the exact skill you\n",
    "need before pandas ever enters the picture."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0002",
   "metadata": {},
   "source": [
    "## Variables and naming rules\n",
    "\n",
    "A variable is a name bound to a value with `=`. Python has a few hard rules\n",
    "and a few strong conventions:\n",
    "\n",
    "- Names can contain letters, digits, and underscores — but can't **start**\n",
    "  with a digit and can't contain spaces or symbols like `-` or `@`.\n",
    "- Names are **case-sensitive**: `number` and `numbeR` are two different\n",
    "  variables (an easy source of bugs — pick one casing and stick to it).\n",
    "- Multi-word names use `snake_case` in Python: `flight_schedules`, not\n",
    "  `flightSchedules` (camelCase is common in other languages, but underscores\n",
    "  are the Python convention).\n",
    "- Choose descriptive English names: `total_price` beats `tp`."
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0003",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "flight_schedules = 10   # snake_case: the Python way\n",
    "number1 = 12\n",
    "number2 = 3\n",
    "\n",
    "total = number1 * number2\n",
    "print(total)\n",
    "\n",
    "# Case-sensitive: these are DIFFERENT variables\n",
    "number = 10\n",
    "numbeR = 5\n",
    "print(number, numbeR)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0004",
   "metadata": {},
   "source": [
    "## Numbers: int and float\n",
    "\n",
    "Python has two everyday number types: `int` for whole numbers and `float`\n",
    "for decimals. The built-in `type()` function tells you what you're holding —\n",
    "use it whenever you're unsure:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0005",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "integer_number = 10\n",
    "float_number = 12.3\n",
    "\n",
    "print(type(integer_number))\n",
    "print(type(float_number))\n",
    "\n",
    "print(integer_number + 1)\n",
    "print(integer_number * 10)\n",
    "print(integer_number / 10)     # division always produces a float\n",
    "print(integer_number ** 0.5)   # fractional power = square root\n",
    "print(type(integer_number / 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0006",
   "metadata": {},
   "source": [
    "Note that `/` returns a `float` even when the result is a whole number —\n",
    "`10 / 10` is `1.0`, not `1`. That distinction matters when a library expects\n",
    "an integer (like an index or a count)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0007",
   "metadata": {},
   "source": [
    "## Booleans: True and False\n",
    "\n",
    "A `bool` is either `True` or `False` (capitalized!). Booleans usually come\n",
    "from **comparison operators**, and they're the fuel for every `if` statement\n",
    "you'll write in the next lessons:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0008",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "age = 32\n",
    "\n",
    "print(age > 30)    # greater than\n",
    "print(age < 32)    # less than\n",
    "print(age <= 32)   # less than or equal\n",
    "print(age == 32)   # equal (two = signs!)\n",
    "print(age != 32)   # not equal\n",
    "\n",
    "is_larger_than_30 = age > 30\n",
    "print(is_larger_than_30, type(is_larger_than_30))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0009",
   "metadata": {},
   "source": [
    "## Strings and f-strings\n",
    "\n",
    "A `str` is text between quotes — single or double both work. The killer\n",
    "feature for data work is the **f-string**: put an `f` before the opening\n",
    "quote and any expression inside curly braces gets evaluated and inserted:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0010",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "name = \"Budi\"\n",
    "city = \"Bandung\"\n",
    "\n",
    "greeting = f\"Hello, my name is {name} and I live in {city}\"\n",
    "print(greeting)\n",
    "\n",
    "# Strings come with useful methods\n",
    "print(greeting.upper())\n",
    "print(greeting.lower())\n",
    "print(greeting.replace(\"Budi\", \"Rama\"))\n",
    "\n",
    "message = \"hello hello my name is Rama\"\n",
    "print(message.count(\"hello\"))   # how many times a substring appears\n",
    "print(len(message))             # length in characters"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0011",
   "metadata": {},
   "source": [
    "Methods like `.upper()` return a **new** string — the original is unchanged.\n",
    "Strings in Python are immutable: you never edit one in place, you build a\n",
    "modified copy."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0012",
   "metadata": {},
   "source": [
    "## Lists: ordered, changeable collections\n",
    "\n",
    "A `list` holds multiple values in order, written with square brackets.\n",
    "Positions are counted from **zero**, and you can grab ranges with\n",
    "**slicing** — `list[start:stop]` takes items from `start` up to (but *not\n",
    "including*) `stop`:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0013",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "animals = [\"cat\", \"dog\", \"fish\"]\n",
    "\n",
    "print(animals[0])        # first item - indexing starts at 0!\n",
    "print(animals[2])        # third item\n",
    "print(animals[-1])       # negative index counts from the end\n",
    "print(len(animals))      # how many items\n",
    "\n",
    "animals.append(\"cow\")    # add to the end (modifies the list in place)\n",
    "print(animals)\n",
    "\n",
    "products = [\"laptop\", \"mouse\", \"keyboard\", \"headset\", \"headphone\", \"flash disk\"]\n",
    "print(products[2:5])     # index 2, 3, 4 - stop index NOT included\n",
    "print(products[1:])      # from index 1 to the end\n",
    "print(products[:-1])     # everything except the last item"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0014",
   "metadata": {},
   "source": [
    "That \"stop not included\" rule trips everyone up at first, but it has a nice\n",
    "property: `products[2:5]` contains exactly `5 - 2 = 3` items, and\n",
    "`products[:k]` plus `products[k:]` reassembles the whole list."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0015",
   "metadata": {},
   "source": [
    "## Tuples: lists that can't change\n",
    "\n",
    "A `tuple` looks like a list with parentheses — but it's **immutable**. Once\n",
    "created, you can't replace, add, or remove items. Use tuples for fixed\n",
    "records where accidental modification would be a bug (coordinates, RGB\n",
    "colors, database rows):"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0016",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "animals = [\"cat\", \"dog\", \"fish\"]\n",
    "animals[0] = \"cow\"       # lists allow this\n",
    "print(animals)\n",
    "\n",
    "point = (\"dog\", \"cat\", \"cow\")\n",
    "print(point.index(\"dog\"))\n",
    "print(point.count(\"cow\"))\n",
    "\n",
    "try:\n",
    "    point[0] = \"fish\"    # tuples do NOT allow this\n",
    "except TypeError as e:\n",
    "    print(\"Error:\", e)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0017",
   "metadata": {},
   "source": [
    "## Dictionaries: labeled data\n",
    "\n",
    "Lists find values by *position*; a `dict` finds them by **key**. This is the\n",
    "single most important container for data science — a JSON API response, a\n",
    "pandas row, a model's configuration: all dictionaries at heart. Values can be\n",
    "anything, including other dicts and lists, which is how real-world data nests:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0018",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "person = {\n",
    "    \"name\": \"Rama\",\n",
    "    \"age\": 25,\n",
    "}\n",
    "\n",
    "print(person[\"name\"])          # access by key instead of index\n",
    "\n",
    "person[\"hobbies\"] = [\"swimming\", \"reading\"]   # add a key\n",
    "person.update({\n",
    "    \"address\": \"Jakarta\",\n",
    "    \"occupation\": {\"company\": \"Jakarta Labs\", \"title\": \"developer\"},\n",
    "})\n",
    "\n",
    "print(person[\"occupation\"][\"title\"])   # chain keys to go deeper\n",
    "print(person[\"hobbies\"][0])            # mix dict and list access\n",
    "print(person.get(\"salary\", \"unknown\")) # .get avoids errors for missing keys\n",
    "print(list(person.keys()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0019",
   "metadata": {},
   "source": [
    "The chaining pattern is worth practicing: `users[1][\"address\"][\"city\"]`\n",
    "reads as \"take item 1 of the list, then its address dict, then the city\n",
    "inside that\". Work through it one bracket at a time."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0020",
   "metadata": {},
   "source": [
    "## Sets: unique values only\n",
    "\n",
    "A `set` (curly braces, no keys) keeps only **unique** values and has no\n",
    "order. Its main use in data work is deduplication and membership tests:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0021",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "animals = {\"dog\", \"cat\", \"cow\", \"dog\"}   # duplicate \"dog\" collapses\n",
    "print(animals)\n",
    "\n",
    "animals.add(\"fish\")\n",
    "animals.remove(\"cow\")\n",
    "print(animals)\n",
    "\n",
    "visitors = [\"ana\", \"budi\", \"ana\", \"citra\", \"budi\", \"ana\"]\n",
    "print(f\"{len(visitors)} visits from {len(set(visitors))} unique visitors\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0022",
   "metadata": {},
   "source": [
    "## Type conversion and None\n",
    "\n",
    "You can convert between types with `int()`, `float()`, `str()`, and\n",
    "`bool()` — essential when data arrives as text (which it very often does).\n",
    "Python also has `None`, a special value meaning \"nothing here yet\", which is\n",
    "how missing data is often represented before it becomes `NaN` in pandas:"
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0023",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "raw = \"42\"                 # a string, not a number!\n",
    "print(raw + \"1\")           # string \"concatenation\": gives 421, not 43\n",
    "print(int(raw) + 1)        # convert first: 43\n",
    "\n",
    "print(float(\"3.14\") * 2)\n",
    "print(str(99) + \" problems\")\n",
    "\n",
    "middle_name = None         # explicitly \"no value\"\n",
    "print(middle_name is None) # test for None with \"is\"\n",
    "print(type(None))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0024",
   "metadata": {},
   "source": [
    "> **The classic string-number bug**\n",
    "> \n",
    "> If numbers arrive as text (from a CSV, a form, an API), math on them silently\n",
    "> misbehaves: \"42\" + \"1\" is \"421\". When results look strange, print\n",
    "> type(value) first — a stray string is the most common culprit."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0025",
   "metadata": {},
   "source": [
    "### 🏋️ Exercise — Navigate a nested user database\n",
    "\n",
    "Create a list called `users` containing three dictionaries. Each has a\n",
    "`name` (string), an `age` (int), and an `address` key holding **another\n",
    "dict** with a `city`. Use the names Rama (25, Jakarta), Budi (30, Bandung),\n",
    "and Teguh (28, Jakarta). Then: **(1)** print Budi's city using chained\n",
    "access, **(2)** print an f-string like `\"Rama is 25 years old and lives in\n",
    "Jakarta\"` for the first user, and **(3)** collect all three cities into a\n",
    "list and use `set()` to print the unique cities."
   ]
  },
  {
   "cell_type": "code",
   "id": "cell-0026",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "# Your solution here"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0027",
   "metadata": {},
   "source": [
    "<details><summary>✅ Show solution</summary>\n",
    "\n",
    "```python\n",
    "users = [\n",
    "    {\"name\": \"Rama\", \"age\": 25, \"address\": {\"city\": \"Jakarta\"}},\n",
    "    {\"name\": \"Budi\", \"age\": 30, \"address\": {\"city\": \"Bandung\"}},\n",
    "    {\"name\": \"Teguh\", \"age\": 28, \"address\": {\"city\": \"Jakarta\"}},\n",
    "]\n",
    "\n",
    "# 1. Budi's city\n",
    "print(users[1][\"address\"][\"city\"])\n",
    "\n",
    "# 2. f-string summary of the first user\n",
    "first = users[0]\n",
    "print(f\"{first['name']} is {first['age']} years old and lives in {first['address']['city']}\")\n",
    "\n",
    "# 3. Unique cities\n",
    "cities = [users[0][\"address\"][\"city\"],\n",
    "          users[1][\"address\"][\"city\"],\n",
    "          users[2][\"address\"][\"city\"]]\n",
    "print(set(cities))\n",
    "```\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-0028",
   "metadata": {},
   "source": [
    "Next up: functions — how to package the logic you just wrote so you can\n",
    "reuse it with a single call."
   ]
  }
 ]
}