{"id":"l-pda-2-2","courseId":"python-data-analysis","lang":"en","kind":"lesson","summary":"Load a CSV into a DataFrame with read_csv and run a five-step inspection routine (shape, head, info, describe, value_counts) that catches problems before they reach your numbers.","html":"<p>The head of growth sends you <code dir=\"ltr\">orders.csv</code> and asks for “a quick revenue number”. The fastest way to give a wrong answer is to load the file and sum a column straight away. Every experienced analyst does the same thing first: look at the data. Not the whole thing, a few deliberate checks that take thirty seconds and catch most problems before they reach a slide.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Step 1: load it, then check shape and head()</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"0\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders.shape)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders.head().to_string())</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p><code dir=\"ltr\">read_csv</code> reads the header row as column names, guesses each column’s type, and returns a DataFrame. <code dir=\"ltr\">head()</code> shows the first five rows (<code dir=\"ltr\">head(10)</code> shows ten, <code dir=\"ltr\">tail()</code> the last ones, <code dir=\"ltr\">sample(5)</code> five at random, which is better at surfacing oddities than the first rows). <code dir=\"ltr\">.to_string()</code> prints every column instead of hiding the middle ones behind <code dir=\"ltr\">...</code>.</p>\n<p>2,066 rows and 7 columns. Before going further, say out loud what you expect: one row per order, an order id, the customer, a date, a status, a channel, an optional coupon and a shipping fee. The checks below confirm or refute each expectation.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Step 2: info() shows types and blanks</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"1\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders.info()</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p><code dir=\"ltr\">info()</code> prints by itself, no <code dir=\"ltr\">print()</code> needed. Read it column by column. <code dir=\"ltr\">Non-Null Count</code> tells you how many rows have a value: <code dir=\"ltr\">coupon_code</code> has 388 of 2,066, so most orders used no coupon. <code dir=\"ltr\">Dtype</code> tells you what pandas made of each column: the ids are <code dir=\"ltr\">int64</code>, the fee is <code dir=\"ltr\">float64</code>, and everything else is <code dir=\"ltr\">object</code>, meaning text.</p>\n<p>That includes <code dir=\"ltr\">order_date</code>. Stored as text, dates sort correctly only because this file happens to write them year first, and they cannot answer “which month?”. Ask <code dir=\"ltr\">read_csv</code> to parse them as they load:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"2\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">parse_dates</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders.dtypes)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].min(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"to\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, orders[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].max())</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p>Now the column is <code dir=\"ltr\">datetime64[ns]</code>, and <code dir=\"ltr\">min()</code> and <code dir=\"ltr\">max()</code> confirm the data runs from 2 January 2024 to 30 December 2025. Checking the date range of every new file is a habit worth forming: a missing month or a stray date in 1970 shows up immediately.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Step 3: describe() summarises numbers</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"3\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders.describe().round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span></code></pre><div class=\"run-output\" data-run-output=\"3\" hidden></div></div>\n<p><code dir=\"ltr\">describe()</code> gives count, mean, standard deviation, minimum, quartiles and maximum for every numeric column. Shipping fees run from 0 to 9.99 with a median of 4.99: plausible. But it also dutifully reports a mean <code dir=\"ltr\">customer_id</code> of 300.47, which means nothing. Ids are labels that happen to be numbers. Read <code dir=\"ltr\">describe()</code> only for columns where arithmetic makes sense, and use it to spot impossible values: negative quantities, prices of zero, a maximum a hundred times the median.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Step 4: value_counts() on the text columns</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"4\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"status\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].value_counts())</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coupon_code\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].value_counts(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">dropna</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">False</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"channel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].value_counts(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">normalize</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">True</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span></code></pre><div class=\"run-output\" data-run-output=\"4\" hidden></div></div>\n<p><code dir=\"ltr\">value_counts()</code> lists every distinct value with its count, most common first. It is how you find typos (<code dir=\"ltr\">&quot;Web&quot;</code> next to <code dir=\"ltr\">&quot;web&quot;</code>), unexpected categories, and how big each group is. <code dir=\"ltr\">dropna=False</code> includes the missing values as their own row, and <code dir=\"ltr\">normalize=True</code> gives shares instead of counts: 55% of orders came through the web.</p>\n<p>Notice that 105 orders were cancelled. Whether revenue should include them is a business decision, not a coding one, and you will make it explicitly in Section 4. Inspection is where those decisions first become visible.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Step 5: check what you are about to assume</h2>\n<p>Finally, turn your expectations into one-line checks:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"5\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pandas </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"orders.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">customers </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.read_csv(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customers.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].is_unique)            </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\"># one row per order?</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customer_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].nunique(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customers ordered\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">len</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(customers), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"customers in total\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(customers[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"referred_by\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].dtype)</span></span></code></pre><div class=\"run-output\" data-run-output=\"5\" hidden></div></div>\n<p>The order id is unique, so one row really is one order. 515 of 600 customers have ordered at least once; the other 85 signed up and never bought, which matters later when you compute “revenue per customer”. And <code dir=\"ltr\">referred_by</code> loaded as <code dir=\"ltr\">float64</code> even though it holds customer ids: most customers were not referred, the blanks became <code dir=\"ltr\">NaN</code>, and in classic pandas <code dir=\"ltr\">NaN</code> is a float, so the whole column became one.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Trusting the first five rows</span></p>\n<p><code dir=\"ltr\">head()</code> shows the tidiest part of many files, because exports are usually sorted by date or id. Problems such as blanks, odd spellings and outliers tend to live further down. Always follow <code dir=\"ltr\">head()</code> with <code dir=\"ltr\">info()</code> and <code dir=\"ltr\">value_counts()</code>, which look at every row.</p>\n</aside>\n<aside class=\"callout callout-why\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">Why this matters</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Why this matters</span></p>\n<p>Inspection is not busywork. Each finding here (coupons mostly missing, cancelled orders present, ids stored as floats, 85 customers with no orders) changes how a later calculation must be written. Spotting them now costs thirty seconds; spotting them after a number has been presented costs credibility.</p>\n</aside>\n<p>In the exercise you run the routine on <code dir=\"ltr\">customers.csv</code>. Next you learn to pull out exactly the rows and columns you need with <code dir=\"ltr\">.loc</code> and <code dir=\"ltr\">.iloc</code>.</p>\n","toc":[{"id":"sec-1","text":"Step 1: load it, then check shape and head()","level":2},{"id":"sec-2","text":"Step 2: info() shows types and blanks","level":2},{"id":"sec-3","text":"Step 3: describe() summarises numbers","level":2},{"id":"sec-4","text":"Step 4: value_counts() on the text columns","level":2},{"id":"sec-5","text":"Step 5: check what you are about to assume","level":2}],"takeaways":["<code dir=\"ltr\">pd.read_csv(&quot;file.csv&quot;)</code> loads a table; <code dir=\"ltr\">parse_dates=[...]</code> turns date columns into real datetimes as they load.","Run the same routine on every new file - <code dir=\"ltr\">shape</code>, <code dir=\"ltr\">head()</code>, <code dir=\"ltr\">info()</code>, <code dir=\"ltr\">describe()</code>, then <code dir=\"ltr\">value_counts()</code> on the text columns.","<code dir=\"ltr\">info()</code> shows each column’s dtype and non-null count, which reveals missing values and numbers stored as text.","An integer column that contains blanks loads as <code dir=\"ltr\">float64</code>, because classic <code dir=\"ltr\">NaN</code> is a float.","Check the facts you are about to rely on, such as an id being unique, with code like <code dir=\"ltr\">orders[&quot;order_id&quot;].is_unique</code>."],"further":[{"title":"pandas.read_csv","url":"https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html"},{"title":"pandas.DataFrame.info","url":"https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.info.html"},{"title":"pandas.Series.value_counts","url":"https://pandas.pydata.org/docs/reference/api/pandas.Series.value_counts.html"}],"quiz":[{"q":"<p><code dir=\"ltr\">info()</code> reports <code dir=\"ltr\">coupon_code  388 non-null  object</code> for a table of 2,066 rows. What does that tell you?</p>","options":[{"text":"The column has 388 different coupon codes.","why":"Non-null count is about missing values, not distinct values. <code dir=\"ltr\">nunique()</code> counts distinct values."},{"text":"388 rows failed to load and were dropped.","why":"<code dir=\"ltr\">read_csv</code> keeps every row; the other 1,678 rows are present with a missing coupon."},{"text":"1,678 orders have no coupon code, and the rest hold text.","why":"Correct. 2,066 minus 388 are missing, and <code dir=\"ltr\">object</code> means the stored values are strings (or a mix)."},{"text":"The column should be converted to numbers.","why":"Coupon codes like <code dir=\"ltr\">WELCOME10</code> are labels; <code dir=\"ltr\">object</code> is the right dtype for them in pandas 2."}],"answer":2},{"q":"<p><code dir=\"ltr\">describe()</code> shows <code dir=\"ltr\">mean 300.47</code> for <code dir=\"ltr\">customer_id</code>. How should you read that?</p>","options":[{"text":"The average customer placed about 300 orders.","why":"Nothing here counts orders per customer; this is arithmetic over id numbers."},{"text":"It is meaningless; ids are labels that happen to be numbers, so their average says nothing.","why":"Correct. <code dir=\"ltr\">describe()</code> summarises every numeric column, including ids. Read it only for columns where arithmetic makes sense."},{"text":"Customer 300 is the most typical customer.","why":"An average of labels does not identify a typical anything. You would need a different question and a different calculation."}],"answer":1},{"q":"<p>In <code dir=\"ltr\">customers.csv</code>, <code dir=\"ltr\">referred_by</code> holds customer ids but loads as <code dir=\"ltr\">float64</code>. Why?</p>","options":[{"text":"Some ids in the file are written with decimals.","why":"The file holds whole numbers only. The type change comes from the blanks."},{"text":"<code dir=\"ltr\">read_csv</code> always loads id columns as floats.","why":"<code dir=\"ltr\">customer_id</code> in the same file loads as <code dir=\"ltr\">int64</code>. The difference is missing values."},{"text":"The column has blanks, which load as <code dir=\"ltr\">NaN</code>, and classic <code dir=\"ltr\">NaN</code> only exists in float columns.","why":"Correct. 503 customers were not referred, so pandas uses <code dir=\"ltr\">float64</code> to hold <code dir=\"ltr\">NaN</code>. Lesson 3.1 shows the nullable <code dir=\"ltr\">Int64</code> type that avoids this."}],"answer":2}],"exercise":{"kind":"python","files":["customers.csv"],"starter":"import pandas as pd\n\n# Load customers.csv with signup_date parsed as a date\ncustomers = None\n\nn_rows = None             # number of customers (rows)\nn_countries = None        # how many distinct countries\ntop_country = None        # the country with the most customers\nmissing_referrals = None  # how many rows have no referred_by value\nfirst_signup = None       # the earliest signup_date\n","solution":"import pandas as pd\n\ncustomers = pd.read_csv(\"customers.csv\", parse_dates=[\"signup_date\"])\ncustomers.info()\n\nn_rows = len(customers)\nn_countries = customers[\"country\"].nunique()\ntop_country = customers[\"country\"].value_counts().idxmax()\nmissing_referrals = customers[\"referred_by\"].isna().sum()\nfirst_signup = customers[\"signup_date\"].min()\n","checks":[{"id":"loaded","test":"isinstance(customers, pd.DataFrame) and customers.shape == (600, 9)"},{"id":"dates-parsed","test":"pd.api.types.is_datetime64_any_dtype(customers['signup_date'])"},{"id":"counts","test":"n_rows == 600 and n_countries == 8"},{"id":"top-country","test":"top_country == 'United States'"},{"id":"missing","test":"int(missing_referrals) == 503"},{"id":"first-signup","test":"str(pd.Timestamp(first_signup).date()) == '2024-01-01'"}],"title":"Inspect the customers table","prompt":"<p>Load <code dir=\"ltr\">customers.csv</code> into <code dir=\"ltr\">customers</code>, parsing <code dir=\"ltr\">signup_date</code> as a date while loading. Then answer five questions with code: <code dir=\"ltr\">n_rows</code> (number of rows), <code dir=\"ltr\">n_countries</code> (number of distinct countries), <code dir=\"ltr\">top_country</code> (the country with the most customers), <code dir=\"ltr\">missing_referrals</code> (how many rows have no <code dir=\"ltr\">referred_by</code>), and <code dir=\"ltr\">first_signup</code> (the earliest signup date).</p>\n","hints":["<code dir=\"ltr\">pd.read_csv(&quot;customers.csv&quot;, parse_dates=[&quot;signup_date&quot;])</code> loads and converts in one step.","<code dir=\"ltr\">nunique()</code> counts distinct values; <code dir=\"ltr\">value_counts()</code> sorts by count, so its first label is the most common.","<code dir=\"ltr\">customers[&quot;referred_by&quot;].isna().sum()</code> counts blanks: <code dir=\"ltr\">True</code> counts as 1. <code dir=\"ltr\">customers[&quot;signup_date&quot;].min()</code> gives the earliest date."],"explanation":"<p><code dir=\"ltr\">len(df)</code> counts rows, and <code dir=\"ltr\">nunique()</code> confirms Cartwheel sells to exactly eight countries, so there are no spelling variants hiding in this clean table (the messy export is a different story). <code dir=\"ltr\">value_counts().idxmax()</code> returns the label with the highest count: the United States, with 104 customers. Summing a boolean Series counts its <code dir=\"ltr\">True</code> values, which is the standard way to count blanks. Because <code dir=\"ltr\">signup_date</code> was parsed, <code dir=\"ltr\">min()</code> compares real dates rather than text and returns a <code dir=\"ltr\">Timestamp</code>, pandas’ date-and-time type.</p>\n","checkLabels":{"loaded":"<code dir=\"ltr\">customers</code> has 600 rows and 9 columns","dates-parsed":"<code dir=\"ltr\">signup_date</code> is a datetime column","counts":"600 rows and 8 countries","top-country":"The top country is the United States","missing":"503 customers have no referral","first-signup":"The first signup was on 2024-01-01"}},"words":725,"readMinutes":3}