{"id":"l-pda-3-4","courseId":"python-data-analysis","lang":"en","kind":"lesson","summary":"Parse a date column that mixes ISO, day-first and month-first formats with explicit to_datetime formats, flag the ambiguous values, and verify the result against a trusted source.","html":"<p>The messy export writes dates three ways: <code dir=\"ltr\">2024-03-05</code>, <code dir=\"ltr\">05/03/2024</code> and <code dir=\"ltr\">03/05/2024</code>. The first is unambiguous. The other two are the same day written by a European and an American spreadsheet, and the trouble is that <code dir=\"ltr\">03/05/2024</code> might also be the European way of writing 3 May. Dates are the column where “it parsed without errors” and “it is correct” are furthest apart.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Letting pandas guess</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\">messy </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_messy.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\">(messy[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].sample(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">8</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).tolist())</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">try</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    pd.to_datetime(messy[</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:#CF222E;--shiki-dark:#FF7B72\">except</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> ValueError</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> error:</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\">str</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(error).splitlines()[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">])</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>pandas 2 infers one format from the first value, <code dir=\"ltr\">2024-01-02</code>, and applies it to the whole column. Row 6, <code dir=\"ltr\">08/04/2024</code>, does not fit, so the conversion stops. That strictness is a feature: older versions guessed row by row and could silently mix up days and months.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Parse each format explicitly</h2>\n<p>You know which formats exist, so say so. Format codes describe the layout: <code dir=\"ltr\">%Y</code> is a four-digit year, <code dir=\"ltr\">%m</code> a two-digit month, <code dir=\"ltr\">%d</code> a two-digit day. Parse the column once per format with <code dir=\"ltr\">errors=&quot;coerce&quot;</code>, which turns non-matching rows into <code dir=\"ltr\">NaT</code> (pandas’ “not a time”), then fill the gaps in order of preference:</p>\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\">messy </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_messy.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">raw </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> messy[</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:#1F2328;--shiki-dark:#E6EDF3\">iso </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.to_datetime(raw, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">format</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"%Y-%m-</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%d</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">errors</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coerce\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">dmy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.to_datetime(raw, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">format</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%d</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/%m/%Y\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">errors</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coerce\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">mdy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.to_datetime(raw, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">format</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"%m/</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%d</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/%Y\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">errors</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coerce\"</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\">(iso.notna().sum(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"ISO |\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, dmy.notna().sum(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fit day-first |\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, mdy.notna().sum(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"fit month-first\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">parsed </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> iso.fillna(dmy).fillna(mdy)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(parsed.isna().sum(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"unparsed\"</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\">(parsed.min(), parsed.max())</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>Every row parsed, and the range is right: January 2024 to December 2025. Day-first comes before month-first in the chain because most of Cartwheel’s countries write dates that way. pandas can do the same in one call with <code dir=\"ltr\">pd.to_datetime(raw, format=&quot;mixed&quot;, dayfirst=True)</code>, which parses each value on its own; the explicit version is longer but shows you exactly which rule decided each row.</p>\n<p>But look at the counts. 57 values fit day-first and 44 fit month-first, from only 68 slashed values. So 33 of them fit <strong>both</strong>.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 220\" role=\"img\" aria-labelledby=\"l-pda-3-4-t1\"><defs><marker id=\"arrow-l-pda-3-4-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-pda-3-4-t1\">Three boxes. 25/10/2025 can only be day-first because there is no month 25. 10/25/2025 can only be month-first. 05/08/2025 fits both readings, 5 August or 8 May, so it is ambiguous and needs outside information.</title>\n  <rect class=\"d-box-success\" x=\"20\" y=\"30\" width=\"200\" height=\"110\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"120\" y=\"65\" text-anchor=\"middle\">25/10/2025</text>\n  <text class=\"d-label\" x=\"120\" y=\"95\" text-anchor=\"middle\">no month 25</text>\n  <text class=\"d-label-strong\" x=\"120\" y=\"120\" text-anchor=\"middle\">day-first only</text>\n  <rect class=\"d-box-success\" x=\"250\" y=\"30\" width=\"200\" height=\"110\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"350\" y=\"65\" text-anchor=\"middle\">10/25/2025</text>\n  <text class=\"d-label\" x=\"350\" y=\"95\" text-anchor=\"middle\">no month 25</text>\n  <text class=\"d-label-strong\" x=\"350\" y=\"120\" text-anchor=\"middle\">month-first only</text>\n  <rect class=\"d-box-warn\" x=\"480\" y=\"30\" width=\"200\" height=\"110\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"580\" y=\"65\" text-anchor=\"middle\">05/08/2025</text>\n  <text class=\"d-label\" x=\"580\" y=\"95\" text-anchor=\"middle\">5 Aug or 8 May?</text>\n  <text class=\"d-label-strong\" x=\"580\" y=\"120\" text-anchor=\"middle\">ambiguous</text>\n  <text class=\"d-label-muted\" x=\"235\" y=\"180\" text-anchor=\"middle\">the parser can decide these</text>\n  <text class=\"d-label-muted\" x=\"580\" y=\"180\" text-anchor=\"middle\">only outside data can decide</text>\n  <path class=\"d-line\" d=\"M30 160 L440 160\"/>\n  <path class=\"d-line\" d=\"M490 160 L670 160\"/>\n</svg><figcaption>Three kinds of slashed date</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">Flag the ambiguity</h2>\n<p>A value that parses both ways to <strong>different</strong> dates is ambiguous. Three of the 33 are dates like <code dir=\"ltr\">05/05/2025</code>, where both readings agree, so they are harmless:</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\">messy </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_messy.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">raw </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> messy[</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:#1F2328;--shiki-dark:#E6EDF3\">dmy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.to_datetime(raw, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">format</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%d</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/%m/%Y\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">errors</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coerce\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">mdy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.to_datetime(raw, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">format</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"%m/</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%d</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/%Y\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">errors</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coerce\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">ambiguous </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> dmy.notna() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x26;</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> mdy.notna() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x26;</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (dmy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> mdy)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(ambiguous.sum(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"ambiguous dates\"</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\">(raw[ambiguous].head(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">6</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).tolist())</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p>Thirty rows depend on an assumption. You can make the assumption, as the <code dir=\"ltr\">fillna</code> chain did, but you should know you made it, and the person reading your report should too.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Verify against the source of truth</h2>\n<p>Cartwheel’s clean <code dir=\"ltr\">orders</code> table is the system of record: every order id in the export also appears there with its real timestamp. A lookup by order id lets you test the assumption directly. <code dir=\"ltr\">set_index(&quot;order_id&quot;)</code> turns the dates into a Series labelled by id, and <code dir=\"ltr\">map()</code> looks each export row up in it:</p>\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\">messy </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_messy.csv\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">raw </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> messy[</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:#1F2328;--shiki-dark:#E6EDF3\">iso </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.to_datetime(raw, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">format</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"%Y-%m-</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%d</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">errors</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coerce\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">dmy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.to_datetime(raw, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">format</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%d</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/%m/%Y\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">errors</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coerce\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">mdy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> pd.to_datetime(raw, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">format</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"%m/</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">%d</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/%Y\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">errors</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coerce\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">parsed </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> iso.fillna(dmy).fillna(mdy)</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:#1F2328;--shiki-dark:#E6EDF3\">true_dates </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orders.set_index(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</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\">].dt.normalize()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">truth </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> messy[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].map(true_dates)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">wrong </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> parsed </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> truth</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(wrong.sum(), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"dates disagree with the order system\"</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\">(messy.loc[wrong, [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"order_id\"</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\">]].assign(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">parsed</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">parsed[wrong].dt.date, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">real</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">truth[wrong].dt.date))</span></span></code></pre><div class=\"run-output\" data-run-output=\"3\" hidden></div></div>\n<p><code dir=\"ltr\">dt.normalize()</code> drops the time of day so the comparison is date to date. Five dates are wrong, and every one is an ambiguous value that was really month-first: <code dir=\"ltr\">02/08/2025</code> is 8 February, not 2 August. The day-first assumption was right for 25 of 30 ambiguous rows, which sounds good until you realise it moves five orders into the wrong month, and some into the wrong quarter.</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 dayfirst=True</span></p>\n<p><code dir=\"ltr\">dayfirst=True</code> is a preference, not a guarantee: pandas uses it only when a value is ambiguous. When a file mixes conventions, as exports assembled from several systems often do, some ambiguous dates will be wrong no matter which preference you choose. If you cannot verify, keep an <code dir=\"ltr\">is_ambiguous</code> flag in the cleaned data and report how much revenue depends on it.</p>\n</aside>\n<h3 id=\"sec-5\" tabindex=\"-1\">Times and time zones</h3>\n<p>The export carries dates only, but the <code dir=\"ltr\">orders</code> table has full timestamps such as <code dir=\"ltr\">2025-12-30 21:19:03</code>, and none of them say which time zone they are in. Cartwheel records everything in one store clock, so you can treat them as local times and move on. When a file does include offsets like <code dir=\"ltr\">+04:00</code>, pass <code dir=\"ltr\">utc=True</code> to <code dir=\"ltr\">pd.to_datetime</code> so every value lands on one common clock before you compare or group them; leaving zones mixed is how two orders placed at the same moment in Dubai and London end up on different dates, or even in different years around midnight on 31 December.</p>\n<h2 id=\"sec-6\" tabindex=\"-1\">What a real date column gives you</h2>\n<p>Once the column is <code dir=\"ltr\">datetime64</code>, the <code dir=\"ltr\">.dt</code> accessor exposes its parts:</p>\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 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:#1F2328;--shiki-dark:#E6EDF3\">d </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</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\">]</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(d.dt.year.value_counts().sort_index())</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(d.dt.day_name().value_counts().head(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</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\">(d.dt.to_period(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"M\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).value_counts().sort_index().tail(</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>Year, month, weekday name, calendar month as a period: these are the keys you will group by in Section 4, where Lesson 4.4 covers time series properly.</p>\n<p>In the exercise you parse, flag and repair the export’s dates. Then one problem remains before the export is clean: rows that appear twice.</p>\n","toc":[{"id":"sec-1","text":"Letting pandas guess","level":2},{"id":"sec-2","text":"Parse each format explicitly","level":2},{"id":"sec-3","text":"Flag the ambiguity","level":2},{"id":"sec-4","text":"Verify against the source of truth","level":2},{"id":"sec-5","text":"Times and time zones","level":3},{"id":"sec-6","text":"What a real date column gives you","level":2}],"takeaways":["Without <code dir=\"ltr\">format=</code>, pandas 2 guesses the format from the first value and fails on any row written differently.","Parse each known format explicitly with <code dir=\"ltr\">pd.to_datetime(col, format=..., errors=&quot;coerce&quot;)</code>, then combine the results with <code dir=\"ltr\">fillna</code>.","A date like <code dir=\"ltr\">05/08/2025</code> parses under both day-first and month-first rules; no parser can tell you which one is true.","Flag ambiguous values and resolve them from a trusted source (the system of record, another column, the person who made the file), never by assumption alone.","Once a column is <code dir=\"ltr\">datetime64</code>, the <code dir=\"ltr\">.dt</code> accessor gives you <code dir=\"ltr\">year</code>, <code dir=\"ltr\">month</code>, <code dir=\"ltr\">day_name()</code> and <code dir=\"ltr\">to_period()</code> for grouping."],"further":[{"title":"pandas.to_datetime","url":"https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html"},{"title":"strftime() and strptime() format codes","url":"https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes"},{"title":"Time series / date functionality","url":"https://pandas.pydata.org/docs/user_guide/timeseries.html"}],"quiz":[{"q":"<p><code dir=\"ltr\">pd.to_datetime(messy[&quot;order_date&quot;])</code> raises <code dir=\"ltr\">ValueError: time data &quot;08/04/2024&quot; doesn't match format &quot;%Y-%m-%d&quot;</code>. Why?</p>","options":[{"text":"The date 8 April 2024 does not exist in the data range.","why":"The value is a valid date; the problem is its format, not its meaning."},{"text":"pandas inferred the ISO format from the first value and then met a row written differently.","why":"Correct. pandas 2 infers one format for the whole column, and the first value, <code dir=\"ltr\">2024-01-02</code>, set it."},{"text":"Dates with slashes are not supported by pandas.","why":"Slashed dates parse fine with the right <code dir=\"ltr\">format</code>, for example <code dir=\"ltr\">%d/%m/%Y</code>."},{"text":"The column contains missing values.","why":"Missing values become <code dir=\"ltr\">NaT</code> (not a time); they do not trigger a format error."}],"answer":1},{"q":"<p>Which of these values is ambiguous, meaning both day-first and month-first readings give a valid but different date?</p>","options":[{"text":"<code dir=\"ltr\">25/10/2025</code>","why":"There is no month 25, so only the day-first reading is valid."},{"text":"<code dir=\"ltr\">10/25/2025</code>","why":"There is no month 25 in the day-first reading either, so this must be month-first."},{"text":"<code dir=\"ltr\">2025-10-05</code>","why":"ISO dates always put the year, then month, then day, so there is only one reading."},{"text":"<code dir=\"ltr\">05/08/2025</code>","why":"Correct. It could be 5 August or 8 May. Only outside information can decide."}],"answer":3},{"q":"<p>Your parser assumes day-first for ambiguous dates. How should you check whether that assumption is right?</p>","options":[{"text":"Compare the parsed dates with a trusted source for the same records, such as the order system, and count disagreements.","why":"Correct. Here the clean <code dir=\"ltr\">orders</code> table shows the assumption is wrong for 5 of the 30 ambiguous rows."},{"text":"Check that no parsed date is <code dir=\"ltr\">NaT</code>.","why":"An ambiguous date never fails to parse; it parses to the wrong date, which this check cannot see."},{"text":"Check that all dates fall between 2024 and 2025.","why":"Swapping day and month inside the same year stays inside the range, so this cannot catch the error."}],"answer":0}],"exercise":{"kind":"python","files":["orders_messy.csv","orders.csv"],"starter":"import pandas as pd\n\nmessy = pd.read_csv(\"orders_messy.csv\")\norders = pd.read_csv(\"orders.csv\", parse_dates=[\"order_date\"])\nraw = messy[\"order_date\"]\n\n# 1. Parse raw three ways (ISO, day-first, month-first) and combine: ISO, then day-first, then month-first\nparsed = None\n\n# 2. Column is_ambiguous: True where raw parses both day-first and month-first to DIFFERENT dates\n\n# 3. The real date of each export row, looked up by order_id in orders (date only, no time)\ntruth = None\n\n# 4. How many parsed dates disagree with truth\nn_wrong = None\n\n# 5. messy[\"order_date\"]: parsed dates, but use truth for the ambiguous rows\n","solution":"import pandas as pd\n\nmessy = pd.read_csv(\"orders_messy.csv\")\norders = pd.read_csv(\"orders.csv\", parse_dates=[\"order_date\"])\nraw = messy[\"order_date\"]\n\niso = pd.to_datetime(raw, format=\"%Y-%m-%d\", errors=\"coerce\")\ndmy = pd.to_datetime(raw, format=\"%d/%m/%Y\", errors=\"coerce\")\nmdy = pd.to_datetime(raw, format=\"%m/%d/%Y\", errors=\"coerce\")\nparsed = iso.fillna(dmy).fillna(mdy)\n\nmessy[\"is_ambiguous\"] = dmy.notna() & mdy.notna() & (dmy != mdy)\n\ntrue_dates = orders.set_index(\"order_id\")[\"order_date\"].dt.normalize()\ntruth = messy[\"order_id\"].map(true_dates)\n\nn_wrong = int((parsed != truth).sum())\n\nmessy[\"order_date\"] = parsed.where(~messy[\"is_ambiguous\"], truth)\nprint(n_wrong, messy[\"is_ambiguous\"].sum())\n","checks":[{"id":"parsed","test":"isinstance(parsed, pd.Series) and pd.api.types.is_datetime64_any_dtype(parsed) and int(parsed.isna().sum()) == 0"},{"id":"ambiguous","test":"'is_ambiguous' in messy.columns and int(messy['is_ambiguous'].sum()) == 30"},{"id":"wrong","test":"int(n_wrong) == 5"},{"id":"fixed-column","test":"pd.api.types.is_datetime64_any_dtype(messy['order_date']) and messy.loc[messy['order_id'] == 2401, 'order_date'].iloc[0] == pd.Timestamp('2025-02-08')"},{"id":"all-correct","test":"(messy['order_date'] == messy['order_id'].map(orders.set_index('order_id')['order_date'].dt.normalize())).all()"}],"title":"Parse, flag and repair the dates","prompt":"<p>Using <code dir=\"ltr\">raw = messy[&quot;order_date&quot;]</code>: build <code dir=\"ltr\">parsed</code> by parsing ISO (<code dir=\"ltr\">%Y-%m-%d</code>), then day-first (<code dir=\"ltr\">%d/%m/%Y</code>), then month-first (<code dir=\"ltr\">%m/%d/%Y</code>) and combining them in that order. Add a boolean column <code dir=\"ltr\">is_ambiguous</code> that is <code dir=\"ltr\">True</code> where the day-first and month-first parses both succeed and give different dates. Set <code dir=\"ltr\">truth</code> to each row’s real date from <code dir=\"ltr\">orders</code>, looked up by <code dir=\"ltr\">order_id</code> with the time removed, and <code dir=\"ltr\">n_wrong</code> to how many <code dir=\"ltr\">parsed</code> dates disagree with it. Finally, set <code dir=\"ltr\">messy[&quot;order_date&quot;]</code> to <code dir=\"ltr\">parsed</code>, except on ambiguous rows, where it takes the value from <code dir=\"ltr\">truth</code>.</p>\n","hints":["Parse once per format with <code dir=\"ltr\">pd.to_datetime(raw, format=..., errors=&quot;coerce&quot;)</code>, then chain <code dir=\"ltr\">.fillna()</code> in order of preference.","Ambiguous: <code dir=\"ltr\">dmy.notna() &amp; mdy.notna() &amp; (dmy != mdy)</code>. For the truth: <code dir=\"ltr\">orders.set_index(&quot;order_id&quot;)[&quot;order_date&quot;].dt.normalize()</code> is a lookup Series; <code dir=\"ltr\">messy[&quot;order_id&quot;].map(...)</code> uses it.","<code dir=\"ltr\">parsed.where(condition, other)</code> keeps <code dir=\"ltr\">parsed</code> where the condition is <code dir=\"ltr\">True</code> and takes <code dir=\"ltr\">other</code> elsewhere, so the condition is <code dir=\"ltr\">~messy[&quot;is_ambiguous&quot;]</code>."],"explanation":"<p>Explicit formats with <code dir=\"ltr\">errors=&quot;coerce&quot;</code> parse every row and record which rule matched. Thirty values fit both slash formats with different results; the day-first preference gets 25 of them right and 5 wrong, which you can only discover by checking against the order system. <code dir=\"ltr\">Series.where(cond, other)</code> keeps values where the condition holds and substitutes elsewhere, so only the ambiguous rows are replaced, and the repaired column now matches the source of truth on all 168 rows. In real projects, keep the <code dir=\"ltr\">is_ambiguous</code> flag even after repairing: it documents which values depended on an outside lookup.</p>\n","checkLabels":{"parsed":"<code dir=\"ltr\">parsed</code> is a datetime Series with no unparsed values","ambiguous":"<code dir=\"ltr\">is_ambiguous</code> flags 30 rows","wrong":"<code dir=\"ltr\">n_wrong</code> is 5","fixed-column":"Order 2401 is dated 8 February 2025, not 2 August","all-correct":"Every repaired date matches the order system"}},"words":701,"readMinutes":3}