[{"data":1,"prerenderedAt":2387},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-box-and-whisker-chart-in-excel-with-python":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-box-and-whisker-chart-in-excel-with-python":2353,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-box-and-whisker-chart-in-excel-with-python":2362,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-box-and-whisker-chart-in-excel-with-python":2367,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-box-and-whisker-chart-in-excel-with-python":2368},{"id":4,"title":5,"body":6,"dateModified":2319,"datePublished":2319,"description":2320,"extension":2321,"faq":2322,"keyword":2331,"kind":2332,"meta":2333,"navigation":107,"path":2342,"seo":2343,"slug":2346,"stem":2347,"topics":2348,"type":2351,"__hash__":2352},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-box-and-whisker-chart-in-excel-with-python\u002Findex.md","Create a Box and Whisker Chart in Excel with Python",{"type":7,"value":8,"toc":2305},"minimark",[9,24,29,61,75,79,400,420,561,565,568,781,784,960,964,1175,1186,1190,1193,1440,1447,1451,1454,1684,1695,1699,2028,2035,2187,2191,2194,2198,2233,2237,2240,2244,2250,2256,2262,2268,2272,2301],[10,11,12,13,17,18,23],"p",{},"Averages hide the shape of a distribution; a box plot shows it. For delivery times, call durations or order values, the box (the middle half of the data), the line inside it (the median) and the whiskers (the spread) tell a reader in one glance which region is consistently slow and which one is usually fine but has a long tail. Excel 2016 added a box-and-whisker chart type, but it uses a newer chart format that neither openpyxl nor XlsxWriter can write. The good news is that a classic chart type already draws exactly those shapes: a ",[14,15,16],"em",{},"stock"," chart. Its up-down bars become the boxes and its high-low lines the whiskers. This page builds an editable box plot that way, with the statistics computed in pandas. It belongs to ",[19,20,22],"a",{"href":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002F","Creating Charts in Excel with openpyxl",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas xlsxwriter pillow\n","bash","",[37,38,39],"code",{"__ignoreMap":35},[40,41,44,48,52,55,58],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMTad","pip",[40,49,51],{"class":50},"srMev"," install",[40,53,54],{"class":50}," pandas",[40,56,57],{"class":50}," xlsxwriter",[40,59,60],{"class":50}," pillow\n",[10,62,63,66,67,70,71,74],{},[37,64,65],{},"delivery_days.xlsx"," has 160 deliveries — 40 per region — with the number of days each took. LibreOffice (",[37,68,69],{},"soffice",") and ",[37,72,73],{},"pdftoppm"," render the chart in Step 6; they are optional.",[25,76,78],{"id":77},"step-1-compute-the-five-numbers-per-group","Step 1 — Compute the five numbers per group",[30,80,84],{"className":81,"code":82,"language":83,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndeliveries = pd.read_excel(\"delivery_days.xlsx\")\norder = [\"North\", \"South\", \"East\", \"West\"]\n\ndef box_stats(s):\n    q1, med, q3 = s.quantile([0.25, 0.5, 0.75])\n    iqr = q3 - q1\n    low_fence, high_fence = q1 - 1.5 * iqr, q3 + 1.5 * iqr\n    inside = s[(s >= low_fence) & (s \u003C= high_fence)]\n    return pd.Series({\"Q1\": q1, \"Median\": med, \"Q3\": q3,\n                      \"LowWhisker\": inside.min(), \"HighWhisker\": inside.max(),\n                      \"Outliers\": int(((s \u003C low_fence) | (s > high_fence)).sum())})\n\nstats = deliveries.groupby(\"Region\")[\"Days\"].apply(box_stats).unstack().loc[order]\nprint(stats.round(2))\n","python",[37,85,86,102,109,127,160,165,178,206,223,255,284,311,326,357,362,385],{"__ignoreMap":35},[40,87,88,92,96,99],{"class":42,"line":43},[40,89,91],{"class":90},"s-kum","import",[40,93,95],{"class":94},"skGVy"," pandas ",[40,97,98],{"class":90},"as",[40,100,101],{"class":94}," pd\n",[40,103,105],{"class":42,"line":104},2,[40,106,108],{"emptyLinePlaceholder":107},true,"\n",[40,110,112,115,118,121,124],{"class":42,"line":111},3,[40,113,114],{"class":94},"deliveries ",[40,116,117],{"class":90},"=",[40,119,120],{"class":94}," pd.read_excel(",[40,122,123],{"class":50},"\"delivery_days.xlsx\"",[40,125,126],{"class":94},")\n",[40,128,130,133,135,138,141,144,147,149,152,154,157],{"class":42,"line":129},4,[40,131,132],{"class":94},"order ",[40,134,117],{"class":90},[40,136,137],{"class":94}," [",[40,139,140],{"class":50},"\"North\"",[40,142,143],{"class":94},", ",[40,145,146],{"class":50},"\"South\"",[40,148,143],{"class":94},[40,150,151],{"class":50},"\"East\"",[40,153,143],{"class":94},[40,155,156],{"class":50},"\"West\"",[40,158,159],{"class":94},"]\n",[40,161,163],{"class":42,"line":162},5,[40,164,108],{"emptyLinePlaceholder":107},[40,166,168,171,175],{"class":42,"line":167},6,[40,169,170],{"class":90},"def",[40,172,174],{"class":173},"s_Opv"," box_stats",[40,176,177],{"class":94},"(s):\n",[40,179,181,184,186,189,193,195,198,200,203],{"class":42,"line":180},7,[40,182,183],{"class":94},"    q1, med, q3 ",[40,185,117],{"class":90},[40,187,188],{"class":94}," s.quantile([",[40,190,192],{"class":191},"sP0c6","0.25",[40,194,143],{"class":94},[40,196,197],{"class":191},"0.5",[40,199,143],{"class":94},[40,201,202],{"class":191},"0.75",[40,204,205],{"class":94},"])\n",[40,207,209,212,214,217,220],{"class":42,"line":208},8,[40,210,211],{"class":94},"    iqr ",[40,213,117],{"class":90},[40,215,216],{"class":94}," q3 ",[40,218,219],{"class":90},"-",[40,221,222],{"class":94}," q1\n",[40,224,226,229,231,234,236,239,242,245,248,250,252],{"class":42,"line":225},9,[40,227,228],{"class":94},"    low_fence, high_fence ",[40,230,117],{"class":90},[40,232,233],{"class":94}," q1 ",[40,235,219],{"class":90},[40,237,238],{"class":191}," 1.5",[40,240,241],{"class":90}," *",[40,243,244],{"class":94}," iqr, q3 ",[40,246,247],{"class":90},"+",[40,249,238],{"class":191},[40,251,241],{"class":90},[40,253,254],{"class":94}," iqr\n",[40,256,258,261,263,266,269,272,275,278,281],{"class":42,"line":257},10,[40,259,260],{"class":94},"    inside ",[40,262,117],{"class":90},[40,264,265],{"class":94}," s[(s ",[40,267,268],{"class":90},">=",[40,270,271],{"class":94}," low_fence) ",[40,273,274],{"class":90},"&",[40,276,277],{"class":94}," (s ",[40,279,280],{"class":90},"\u003C=",[40,282,283],{"class":94}," high_fence)]\n",[40,285,287,290,293,296,299,302,305,308],{"class":42,"line":286},11,[40,288,289],{"class":90},"    return",[40,291,292],{"class":94}," pd.Series({",[40,294,295],{"class":50},"\"Q1\"",[40,297,298],{"class":94},": q1, ",[40,300,301],{"class":50},"\"Median\"",[40,303,304],{"class":94},": med, ",[40,306,307],{"class":50},"\"Q3\"",[40,309,310],{"class":94},": q3,\n",[40,312,314,317,320,323],{"class":42,"line":313},12,[40,315,316],{"class":50},"                      \"LowWhisker\"",[40,318,319],{"class":94},": inside.min(), ",[40,321,322],{"class":50},"\"HighWhisker\"",[40,324,325],{"class":94},": inside.max(),\n",[40,327,329,332,335,338,341,344,346,349,351,354],{"class":42,"line":328},13,[40,330,331],{"class":50},"                      \"Outliers\"",[40,333,334],{"class":94},": ",[40,336,337],{"class":191},"int",[40,339,340],{"class":94},"(((s ",[40,342,343],{"class":90},"\u003C",[40,345,271],{"class":94},[40,347,348],{"class":90},"|",[40,350,277],{"class":94},[40,352,353],{"class":90},">",[40,355,356],{"class":94}," high_fence)).sum())})\n",[40,358,360],{"class":42,"line":359},14,[40,361,108],{"emptyLinePlaceholder":107},[40,363,365,368,370,373,376,379,382],{"class":42,"line":364},15,[40,366,367],{"class":94},"stats ",[40,369,117],{"class":90},[40,371,372],{"class":94}," deliveries.groupby(",[40,374,375],{"class":50},"\"Region\"",[40,377,378],{"class":94},")[",[40,380,381],{"class":50},"\"Days\"",[40,383,384],{"class":94},"].apply(box_stats).unstack().loc[order]\n",[40,386,388,391,394,397],{"class":42,"line":387},16,[40,389,390],{"class":191},"print",[40,392,393],{"class":94},"(stats.round(",[40,395,396],{"class":191},"2",[40,398,399],{"class":94},"))\n",[10,401,402,403,406,407,411,412,415,416,419],{},"The quartiles use pandas' default method, which matches Excel's ",[37,404,405],{},"QUARTILE.INC"," — the difference from the \"exclusive\" method is explained in ",[19,408,410],{"href":409},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fmedian-mode-and-quartile-equivalents-in-pandas\u002F","MEDIAN, MODE and QUARTILE Equivalents in pandas",". The whiskers follow the usual Tukey rule: they reach the most extreme data point within 1.5 times the interquartile range of the box; anything beyond is an outlier, counted here and listed in Step 5. If your audience expects whiskers from the minimum to the maximum, use ",[37,413,414],{},"s.min()"," and ",[37,417,418],{},"s.max()"," instead and say so in the chart title.",[421,422,430,431,430,435,430,439,430,447,430,457,430,468,430,475,430,480,430,485,430,490,430,494,430,501,430,506,430,510,430,514,430,517,430,520,430,525,430,529,430,533,430,537,430,540,430,545,430,552,430,557],"svg",{"viewBox":423,"role":424,"ariaLabelledBy":425,"xmlns":428,"style":429},"84 0 592 512","img",[426,427],"bx-anatomy-t","bx-anatomy-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[432,433,434],"title",{"id":426},"What each part of the box plot shows",[436,437,438],"desc",{"id":427},"From top to bottom, the high whisker ends at the largest value within 1.5 times the IQR above Q3, the box spans Q1 to Q3 with the median line inside, and the low whisker ends at the smallest value within 1.5 times the IQR below Q1. Points beyond the whiskers are outliers.",[440,441],"rect",{"x":442,"y":443,"width":444,"height":445,"fill":446},"84","0","592","512","#ffffff",[440,448],{"x":449,"y":450,"width":451,"height":452,"rx":453,"fill":454,"stroke":455,"style":456},"100.0","16.0","560.0","93.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[458,459,461],"g",{"transform":460},"translate(368.0,29.0) scale(1.000)",[462,463],"path",{"d":464,"fill":465,"stroke":466,"style":467},"M4 20h16 M4 20V4 M6 16l4-5 4 3 6-7","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[469,470,474],"text",{"x":471,"y":472,"style":473},"380.0","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","High whisker",[469,476,479],{"x":471,"y":477,"style":478},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","largest value ≤ Q3 + 1.5 × IQR",[42,481],{"x1":471,"y1":482,"x2":471,"y2":483,"stroke":484,"style":456},"114.0","136.0","var(--brand,#5b5cf0)",[486,487],"polygon",{"points":488,"fill":489},"380.0,140.0 375.0,131.0 385.0,131.0","#5b5cf0",[440,491],{"x":449,"y":492,"width":451,"height":452,"rx":453,"fill":493,"stroke":484,"style":456},"145.0","#f0f4ff",[458,495,497],{"transform":496},"translate(368.0,158.0) scale(1.000)",[462,498],{"d":499,"fill":465,"stroke":500,"style":467},"M4 20h16 M7 17v-5 M12 17V7 M17 17v-8","var(--brand-strong,#4338ca)",[469,502,505],{"x":471,"y":503,"style":504},"204.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Box: Q1 to Q3, median inside",[469,507,509],{"x":471,"y":508,"style":478},"224.0","the middle half of the data",[42,511],{"x1":471,"y1":512,"x2":471,"y2":513,"stroke":484,"style":456},"243.0","265.0",[486,515],{"points":516,"fill":489},"380.0,269.0 375.0,260.0 385.0,260.0",[440,518],{"x":449,"y":519,"width":451,"height":452,"rx":453,"fill":454,"stroke":455,"style":456},"274.0",[458,521,523],{"transform":522},"translate(368.0,287.0) scale(1.000)",[462,524],{"d":464,"fill":465,"stroke":466,"style":467},[469,526,528],{"x":471,"y":527,"style":473},"333.5","Low whisker",[469,530,532],{"x":471,"y":531,"style":478},"353.0","smallest value ≥ Q1 − 1.5 × IQR",[42,534],{"x1":471,"y1":535,"x2":471,"y2":536,"stroke":484,"style":456},"372.0","394.0",[486,538],{"points":539,"fill":489},"380.0,398.0 375.0,389.0 385.0,389.0",[440,541],{"x":449,"y":542,"width":451,"height":452,"rx":453,"fill":543,"stroke":544,"style":456},"403.0","#fdefd8","var(--gold,#b4740a)",[458,546,548],{"transform":547},"translate(368.0,416.0) scale(1.000)",[462,549],{"d":550,"fill":465,"stroke":551,"style":467},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)",[469,553,556],{"x":471,"y":554,"style":555},"462.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Outliers",[469,558,560],{"x":471,"y":559,"style":478},"482.0","beyond the whiskers — listed separately",[25,562,564],{"id":563},"step-2-map-the-numbers-onto-a-stock-chart","Step 2 — Map the numbers onto a stock chart",[10,566,567],{},"A stock chart expects four series per category in a fixed order — open, high, low, close — and draws a vertical line from low to high plus a bar between open and close. Feed it the box plot's numbers in that order:",[30,569,571],{"className":81,"code":570,"language":83,"meta":35,"style":35},"import xlsxwriter\n\nbook = xlsxwriter.Workbook(\"delivery_boxplot.xlsx\")\nsheet = book.add_worksheet(\"Box plot\")\nheaders = [\"Region\", \"Q1 (open)\", \"High whisker (high)\", \"Low whisker (low)\", \"Q3 (close)\", \"Median\", \"Outliers\"]\nsheet.write_row(0, 0, headers)\nfor r, (region, row) in enumerate(stats.iterrows(), start=1):\n    sheet.write_row(r, 0, [region, row[\"Q1\"], row[\"HighWhisker\"], row[\"LowWhisker\"], row[\"Q3\"],\n                           row[\"Median\"], row[\"Outliers\"]])\nn = len(stats)\ncats = [\"Box plot\", 1, 0, n, 0]\n",[37,572,573,580,584,599,614,656,670,699,728,742,755],{"__ignoreMap":35},[40,574,575,577],{"class":42,"line":43},[40,576,91],{"class":90},[40,578,579],{"class":94}," xlsxwriter\n",[40,581,582],{"class":42,"line":104},[40,583,108],{"emptyLinePlaceholder":107},[40,585,586,589,591,594,597],{"class":42,"line":111},[40,587,588],{"class":94},"book ",[40,590,117],{"class":90},[40,592,593],{"class":94}," xlsxwriter.Workbook(",[40,595,596],{"class":50},"\"delivery_boxplot.xlsx\"",[40,598,126],{"class":94},[40,600,601,604,606,609,612],{"class":42,"line":129},[40,602,603],{"class":94},"sheet ",[40,605,117],{"class":90},[40,607,608],{"class":94}," book.add_worksheet(",[40,610,611],{"class":50},"\"Box plot\"",[40,613,126],{"class":94},[40,615,616,619,621,623,625,627,630,632,635,637,640,642,645,647,649,651,654],{"class":42,"line":162},[40,617,618],{"class":94},"headers ",[40,620,117],{"class":90},[40,622,137],{"class":94},[40,624,375],{"class":50},[40,626,143],{"class":94},[40,628,629],{"class":50},"\"Q1 (open)\"",[40,631,143],{"class":94},[40,633,634],{"class":50},"\"High whisker (high)\"",[40,636,143],{"class":94},[40,638,639],{"class":50},"\"Low whisker (low)\"",[40,641,143],{"class":94},[40,643,644],{"class":50},"\"Q3 (close)\"",[40,646,143],{"class":94},[40,648,301],{"class":50},[40,650,143],{"class":94},[40,652,653],{"class":50},"\"Outliers\"",[40,655,159],{"class":94},[40,657,658,661,663,665,667],{"class":42,"line":167},[40,659,660],{"class":94},"sheet.write_row(",[40,662,443],{"class":191},[40,664,143],{"class":94},[40,666,443],{"class":191},[40,668,669],{"class":94},", headers)\n",[40,671,672,675,678,681,684,687,691,693,696],{"class":42,"line":180},[40,673,674],{"class":90},"for",[40,676,677],{"class":94}," r, (region, row) ",[40,679,680],{"class":90},"in",[40,682,683],{"class":191}," enumerate",[40,685,686],{"class":94},"(stats.iterrows(), ",[40,688,690],{"class":689},"sa561","start",[40,692,117],{"class":90},[40,694,695],{"class":191},"1",[40,697,698],{"class":94},"):\n",[40,700,701,704,706,709,711,714,716,718,721,723,725],{"class":42,"line":208},[40,702,703],{"class":94},"    sheet.write_row(r, ",[40,705,443],{"class":191},[40,707,708],{"class":94},", [region, row[",[40,710,295],{"class":50},[40,712,713],{"class":94},"], row[",[40,715,322],{"class":50},[40,717,713],{"class":94},[40,719,720],{"class":50},"\"LowWhisker\"",[40,722,713],{"class":94},[40,724,307],{"class":50},[40,726,727],{"class":94},"],\n",[40,729,730,733,735,737,739],{"class":42,"line":225},[40,731,732],{"class":94},"                           row[",[40,734,301],{"class":50},[40,736,713],{"class":94},[40,738,653],{"class":50},[40,740,741],{"class":94},"]])\n",[40,743,744,747,749,752],{"class":42,"line":257},[40,745,746],{"class":94},"n ",[40,748,117],{"class":90},[40,750,751],{"class":191}," len",[40,753,754],{"class":94},"(stats)\n",[40,756,757,760,762,764,766,768,770,772,774,777,779],{"class":42,"line":286},[40,758,759],{"class":94},"cats ",[40,761,117],{"class":90},[40,763,137],{"class":94},[40,765,611],{"class":50},[40,767,143],{"class":94},[40,769,695],{"class":191},[40,771,143],{"class":94},[40,773,443],{"class":191},[40,775,776],{"class":94},", n, ",[40,778,443],{"class":191},[40,780,159],{"class":94},[10,782,783],{},"Mapping Q1 to \"open\" and Q3 to \"close\" makes the up-down bar span exactly the box, and the whisker ends as \"high\" and \"low\" make the high-low line span the whiskers. Writing the numbers to the sheet — rather than embedding them in the chart — keeps the chart linked to visible, checkable cells.",[421,785,430,791,430,794,430,797,430,802,430,809,430,815,430,819,430,823,430,826,430,831,430,833,430,836,430,839,430,842,430,844,430,847,430,850,430,853,430,855,430,858,430,861,430,865,430,867,430,870,430,874,430,878,430,882,430,886,430,889,430,893,430,895,430,898,430,900,430,903,430,905,430,908,430,910,430,913,430,915,430,918,430,920,430,923,430,925,430,928,430,934,430,938,430,943,430,946,430,954],{"viewBox":786,"role":424,"ariaLabelledBy":787,"xmlns":428,"style":790},"85 0 589 196",[788,789],"bx-map-t","bx-map-d","width:100%;max-width:589px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[432,792,793],{"id":788},"Box plot numbers mapped to stock-chart series",[436,795,796],{"id":789},"On the left the statistics for North — Q1 4.55, median 6.05, Q3 7.78 and whiskers at 1.7 and 12.5. On the right the same numbers as the stock chart's open, high, low and close series, with the median as an extra series.",[440,798],{"x":799,"y":443,"width":800,"height":801,"fill":446},"85","589","196",[440,803],{"x":804,"y":805,"width":806,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},"101.7","40.0","94.5","28.0","stroke-width:1px",[469,810,814],{"x":811,"y":812,"style":813},"148.9","59.0","font-size:10.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","statistic",[440,816],{"x":817,"y":805,"width":818,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},"196.2","90.5",[469,820,822],{"x":821,"y":812,"style":813},"241.4","North",[440,824],{"x":804,"y":825,"width":806,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},"68.0",[469,827,830],{"x":811,"y":828,"style":829},"87.0","font-size:10.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Q1",[440,832],{"x":817,"y":825,"width":818,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},[469,834,835],{"x":821,"y":828,"style":829},"4.55",[440,837],{"x":804,"y":838,"width":806,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},"96.0",[469,840,474],{"x":811,"y":841,"style":829},"115.0",[440,843],{"x":817,"y":838,"width":818,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},[469,845,846],{"x":821,"y":841,"style":829},"max ≤ fence",[440,848],{"x":804,"y":849,"width":806,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},"124.0",[469,851,528],{"x":811,"y":852,"style":829},"143.0",[440,854],{"x":817,"y":849,"width":818,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},[469,856,857],{"x":821,"y":852,"style":829},"min ≥ fence",[440,859],{"x":804,"y":860,"width":806,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},"152.0",[469,862,864],{"x":811,"y":863,"style":829},"171.0","Q3",[440,866],{"x":817,"y":860,"width":818,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},[469,868,869],{"x":821,"y":863,"style":829},"7.78",[440,871],{"x":872,"y":805,"width":873,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},"469.8","103.3",[469,875,877],{"x":876,"y":812,"style":813},"521.4","series",[440,879],{"x":880,"y":805,"width":881,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},"573.0","85.3",[469,883,885],{"x":884,"y":812,"style":813},"615.7","draws",[440,887],{"x":872,"y":825,"width":873,"height":807,"rx":443,"fill":493,"stroke":484,"style":888},"stroke-width:1.5px",[469,890,892],{"x":876,"y":828,"style":891},"font-size:10.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","open = Q1",[440,894],{"x":880,"y":825,"width":881,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},[469,896,897],{"x":884,"y":828,"style":829},"box bottom",[440,899],{"x":872,"y":838,"width":873,"height":807,"rx":443,"fill":454,"stroke":455,"style":888},[469,901,902],{"x":876,"y":841,"style":813},"high = whisker",[440,904],{"x":880,"y":838,"width":881,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},[469,906,907],{"x":884,"y":841,"style":829},"line top",[440,909],{"x":872,"y":849,"width":873,"height":807,"rx":443,"fill":454,"stroke":455,"style":888},[469,911,912],{"x":876,"y":852,"style":813},"low = whisker",[440,914],{"x":880,"y":849,"width":881,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},[469,916,917],{"x":884,"y":852,"style":829},"line bottom",[440,919],{"x":872,"y":860,"width":873,"height":807,"rx":443,"fill":493,"stroke":484,"style":888},[469,921,922],{"x":876,"y":863,"style":891},"close = Q3",[440,924],{"x":880,"y":860,"width":881,"height":807,"rx":443,"fill":446,"stroke":455,"style":808},[469,926,927],{"x":884,"y":863,"style":829},"box top",[469,929,933],{"x":930,"y":931,"style":932},"194.2","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","box statistics",[469,935,937],{"x":936,"y":931,"style":932},"564.1","stock series",[42,939],{"x1":940,"y1":941,"x2":942,"y2":941,"stroke":484,"style":456},"296.6","110.0","455.8",[486,944],{"points":945,"fill":489},"459.8,110.0 450.8,115.0 450.8,105.0",[440,947],{"x":948,"y":949,"width":950,"height":951,"rx":952,"fill":446,"stroke":455,"style":953},"321.0","77.0","114.5","22.0","11","stroke-width:1.2px",[469,955,959],{"x":956,"y":957,"style":958},"378.2","92.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","O · H · L · C",[25,961,963],{"id":962},"step-3-boxes-from-up-down-bars-whiskers-from-high-low-lines","Step 3 — Boxes from up-down bars, whiskers from high-low lines",[30,965,967],{"className":81,"code":966,"language":83,"meta":35,"style":35},"box = book.add_chart({\"type\": \"stock\"})\nfor col in range(1, 5):\n    box.add_series({\"name\": [\"Box plot\", 0, col], \"categories\": cats,\n                    \"values\": [\"Box plot\", 1, col, n, col],\n                    \"line\": {\"none\": True}, \"marker\": {\"type\": \"none\"}})\nbox.set_high_low_lines({\"line\": {\"color\": \"#334155\", \"width\": 1.25}})\nsame = {\"fill\": {\"color\": \"#E0E7FF\"}, \"border\": {\"color\": \"#4338CA\"}}\nbox.set_up_down_bars({\"up\": same, \"down\": same})\n",[37,968,969,990,1014,1040,1056,1089,1119,1158],{"__ignoreMap":35},[40,970,971,974,976,979,982,984,987],{"class":42,"line":43},[40,972,973],{"class":94},"box ",[40,975,117],{"class":90},[40,977,978],{"class":94}," book.add_chart({",[40,980,981],{"class":50},"\"type\"",[40,983,334],{"class":94},[40,985,986],{"class":50},"\"stock\"",[40,988,989],{"class":94},"})\n",[40,991,992,994,997,999,1002,1005,1007,1009,1012],{"class":42,"line":104},[40,993,674],{"class":90},[40,995,996],{"class":94}," col ",[40,998,680],{"class":90},[40,1000,1001],{"class":191}," range",[40,1003,1004],{"class":94},"(",[40,1006,695],{"class":191},[40,1008,143],{"class":94},[40,1010,1011],{"class":191},"5",[40,1013,698],{"class":94},[40,1015,1016,1019,1022,1025,1027,1029,1031,1034,1037],{"class":42,"line":111},[40,1017,1018],{"class":94},"    box.add_series({",[40,1020,1021],{"class":50},"\"name\"",[40,1023,1024],{"class":94},": [",[40,1026,611],{"class":50},[40,1028,143],{"class":94},[40,1030,443],{"class":191},[40,1032,1033],{"class":94},", col], ",[40,1035,1036],{"class":50},"\"categories\"",[40,1038,1039],{"class":94},": cats,\n",[40,1041,1042,1045,1047,1049,1051,1053],{"class":42,"line":129},[40,1043,1044],{"class":50},"                    \"values\"",[40,1046,1024],{"class":94},[40,1048,611],{"class":50},[40,1050,143],{"class":94},[40,1052,695],{"class":191},[40,1054,1055],{"class":94},", col, n, col],\n",[40,1057,1058,1061,1064,1067,1069,1072,1075,1078,1080,1082,1084,1086],{"class":42,"line":162},[40,1059,1060],{"class":50},"                    \"line\"",[40,1062,1063],{"class":94},": {",[40,1065,1066],{"class":50},"\"none\"",[40,1068,334],{"class":94},[40,1070,1071],{"class":191},"True",[40,1073,1074],{"class":94},"}, ",[40,1076,1077],{"class":50},"\"marker\"",[40,1079,1063],{"class":94},[40,1081,981],{"class":50},[40,1083,334],{"class":94},[40,1085,1066],{"class":50},[40,1087,1088],{"class":94},"}})\n",[40,1090,1091,1094,1097,1099,1102,1104,1107,1109,1112,1114,1117],{"class":42,"line":167},[40,1092,1093],{"class":94},"box.set_high_low_lines({",[40,1095,1096],{"class":50},"\"line\"",[40,1098,1063],{"class":94},[40,1100,1101],{"class":50},"\"color\"",[40,1103,334],{"class":94},[40,1105,1106],{"class":50},"\"#334155\"",[40,1108,143],{"class":94},[40,1110,1111],{"class":50},"\"width\"",[40,1113,334],{"class":94},[40,1115,1116],{"class":191},"1.25",[40,1118,1088],{"class":94},[40,1120,1121,1124,1126,1129,1132,1134,1136,1138,1141,1143,1146,1148,1150,1152,1155],{"class":42,"line":180},[40,1122,1123],{"class":94},"same ",[40,1125,117],{"class":90},[40,1127,1128],{"class":94}," {",[40,1130,1131],{"class":50},"\"fill\"",[40,1133,1063],{"class":94},[40,1135,1101],{"class":50},[40,1137,334],{"class":94},[40,1139,1140],{"class":50},"\"#E0E7FF\"",[40,1142,1074],{"class":94},[40,1144,1145],{"class":50},"\"border\"",[40,1147,1063],{"class":94},[40,1149,1101],{"class":50},[40,1151,334],{"class":94},[40,1153,1154],{"class":50},"\"#4338CA\"",[40,1156,1157],{"class":94},"}}\n",[40,1159,1160,1163,1166,1169,1172],{"class":42,"line":208},[40,1161,1162],{"class":94},"box.set_up_down_bars({",[40,1164,1165],{"class":50},"\"up\"",[40,1167,1168],{"class":94},": same, ",[40,1170,1171],{"class":50},"\"down\"",[40,1173,1174],{"class":94},": same})\n",[10,1176,1177,1178,1181,1182,1185],{},"The four series themselves are invisible — no line, no marker — because only their derived shapes matter. ",[37,1179,1180],{},"set_high_low_lines"," draws the whiskers. ",[37,1183,1184],{},"set_up_down_bars"," draws a bar from open to close; normally \"up\" and \"down\" bars are coloured differently (rising and falling prices), but Q3 is always above Q1, so giving both the same style keeps every box identical.",[25,1187,1189],{"id":1188},"step-4-add-the-median-and-finish-the-axes","Step 4 — Add the median and finish the axes",[10,1191,1192],{},"The median is a fifth number that a stock chart has no slot for, so add it as a separate line series with a dash marker and combine it with the stock chart:",[30,1194,1196],{"className":81,"code":1195,"language":83,"meta":35,"style":35},"median = book.add_chart({\"type\": \"line\"})\nmedian.add_series({\"name\": \"Median\", \"categories\": cats, \"values\": [\"Box plot\", 1, 5, n, 5],\n                   \"line\": {\"none\": True},\n                   \"marker\": {\"type\": \"long_dash\", \"size\": 16, \"fill\": {\"color\": \"#4338CA\"},\n                              \"border\": {\"color\": \"#4338CA\"}}})\nbox.combine(median)\nbox.set_title({\"name\": \"Delivery days by region (whiskers: 1.5 × IQR)\"})\nbox.set_legend({\"none\": True})\nbox.set_y_axis({\"min\": 0, \"name\": \"Days\", \"major_gridlines\": {\"visible\": True, \"line\": {\"color\": \"#E5E7EB\"}}})\nbox.set_size({\"width\": 620, \"height\": 340})\nsheet.insert_chart(\"A8\", box)\n",[37,1197,1198,1215,1254,1270,1308,1324,1329,1343,1356,1405,1429],{"__ignoreMap":35},[40,1199,1200,1203,1205,1207,1209,1211,1213],{"class":42,"line":43},[40,1201,1202],{"class":94},"median ",[40,1204,117],{"class":90},[40,1206,978],{"class":94},[40,1208,981],{"class":50},[40,1210,334],{"class":94},[40,1212,1096],{"class":50},[40,1214,989],{"class":94},[40,1216,1217,1220,1222,1224,1226,1228,1230,1233,1236,1238,1240,1242,1244,1246,1248,1250,1252],{"class":42,"line":104},[40,1218,1219],{"class":94},"median.add_series({",[40,1221,1021],{"class":50},[40,1223,334],{"class":94},[40,1225,301],{"class":50},[40,1227,143],{"class":94},[40,1229,1036],{"class":50},[40,1231,1232],{"class":94},": cats, ",[40,1234,1235],{"class":50},"\"values\"",[40,1237,1024],{"class":94},[40,1239,611],{"class":50},[40,1241,143],{"class":94},[40,1243,695],{"class":191},[40,1245,143],{"class":94},[40,1247,1011],{"class":191},[40,1249,776],{"class":94},[40,1251,1011],{"class":191},[40,1253,727],{"class":94},[40,1255,1256,1259,1261,1263,1265,1267],{"class":42,"line":111},[40,1257,1258],{"class":50},"                   \"line\"",[40,1260,1063],{"class":94},[40,1262,1066],{"class":50},[40,1264,334],{"class":94},[40,1266,1071],{"class":191},[40,1268,1269],{"class":94},"},\n",[40,1271,1272,1275,1277,1279,1281,1284,1286,1289,1291,1294,1296,1298,1300,1302,1304,1306],{"class":42,"line":129},[40,1273,1274],{"class":50},"                   \"marker\"",[40,1276,1063],{"class":94},[40,1278,981],{"class":50},[40,1280,334],{"class":94},[40,1282,1283],{"class":50},"\"long_dash\"",[40,1285,143],{"class":94},[40,1287,1288],{"class":50},"\"size\"",[40,1290,334],{"class":94},[40,1292,1293],{"class":191},"16",[40,1295,143],{"class":94},[40,1297,1131],{"class":50},[40,1299,1063],{"class":94},[40,1301,1101],{"class":50},[40,1303,334],{"class":94},[40,1305,1154],{"class":50},[40,1307,1269],{"class":94},[40,1309,1310,1313,1315,1317,1319,1321],{"class":42,"line":162},[40,1311,1312],{"class":50},"                              \"border\"",[40,1314,1063],{"class":94},[40,1316,1101],{"class":50},[40,1318,334],{"class":94},[40,1320,1154],{"class":50},[40,1322,1323],{"class":94},"}}})\n",[40,1325,1326],{"class":42,"line":167},[40,1327,1328],{"class":94},"box.combine(median)\n",[40,1330,1331,1334,1336,1338,1341],{"class":42,"line":180},[40,1332,1333],{"class":94},"box.set_title({",[40,1335,1021],{"class":50},[40,1337,334],{"class":94},[40,1339,1340],{"class":50},"\"Delivery days by region (whiskers: 1.5 × IQR)\"",[40,1342,989],{"class":94},[40,1344,1345,1348,1350,1352,1354],{"class":42,"line":208},[40,1346,1347],{"class":94},"box.set_legend({",[40,1349,1066],{"class":50},[40,1351,334],{"class":94},[40,1353,1071],{"class":191},[40,1355,989],{"class":94},[40,1357,1358,1361,1364,1366,1368,1370,1372,1374,1376,1378,1381,1383,1386,1388,1390,1392,1394,1396,1398,1400,1403],{"class":42,"line":225},[40,1359,1360],{"class":94},"box.set_y_axis({",[40,1362,1363],{"class":50},"\"min\"",[40,1365,334],{"class":94},[40,1367,443],{"class":191},[40,1369,143],{"class":94},[40,1371,1021],{"class":50},[40,1373,334],{"class":94},[40,1375,381],{"class":50},[40,1377,143],{"class":94},[40,1379,1380],{"class":50},"\"major_gridlines\"",[40,1382,1063],{"class":94},[40,1384,1385],{"class":50},"\"visible\"",[40,1387,334],{"class":94},[40,1389,1071],{"class":191},[40,1391,143],{"class":94},[40,1393,1096],{"class":50},[40,1395,1063],{"class":94},[40,1397,1101],{"class":50},[40,1399,334],{"class":94},[40,1401,1402],{"class":50},"\"#E5E7EB\"",[40,1404,1323],{"class":94},[40,1406,1407,1410,1412,1414,1417,1419,1422,1424,1427],{"class":42,"line":257},[40,1408,1409],{"class":94},"box.set_size({",[40,1411,1111],{"class":50},[40,1413,334],{"class":94},[40,1415,1416],{"class":191},"620",[40,1418,143],{"class":94},[40,1420,1421],{"class":50},"\"height\"",[40,1423,334],{"class":94},[40,1425,1426],{"class":191},"340",[40,1428,989],{"class":94},[40,1430,1431,1434,1437],{"class":42,"line":286},[40,1432,1433],{"class":94},"sheet.insert_chart(",[40,1435,1436],{"class":50},"\"A8\"",[40,1438,1439],{"class":94},", box)\n",[10,1441,1442,1443,23],{},"The median dash sits across each box. The title states the whisker rule, because readers interpret box plots differently. A fixed axis minimum of zero keeps the boxes comparable; the maximum is left automatic so long whiskers are never cut off. Stock-chart details are in ",[19,1444,1446],{"href":1445},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-stock-chart-with-openpyxl\u002F","Create a Stock Chart with openpyxl",[25,1448,1450],{"id":1449},"step-5-list-the-outliers-beside-the-chart","Step 5 — List the outliers beside the chart",[10,1452,1453],{},"Points beyond the whiskers are not drawn by this construction, so list them in a small table — which is often more useful than dots anyway, because each one is a delivery someone may want to look up:",[30,1455,1457],{"className":81,"code":1456,"language":83,"meta":35,"style":35},"outliers = []\nfor region, s in deliveries.groupby(\"Region\")[\"Days\"]:\n    q1, q3 = s.quantile([0.25, 0.75])\n    fence = q3 + 1.5 * (q3 - q1)\n    outliers += [(region, d) for d in s[s > fence].sort_values(ascending=False)]\nsheet.write_row(0, 9, [\"Region\", \"Outlier (days)\"])\nfor r, (region, d) in enumerate(outliers, start=1):\n    sheet.write_row(r, 9, [region, d])\nsheet.set_column(\"A:A\", 10)\nsheet.set_column(\"B:G\", 12)\nbook.close()\nprint(f\"{len(outliers)} outliers:\", outliers)\n",[37,1458,1459,1469,1489,1506,1529,1566,1589,1611,1620,1635,1648,1653],{"__ignoreMap":35},[40,1460,1461,1464,1466],{"class":42,"line":43},[40,1462,1463],{"class":94},"outliers ",[40,1465,117],{"class":90},[40,1467,1468],{"class":94}," []\n",[40,1470,1471,1473,1476,1478,1480,1482,1484,1486],{"class":42,"line":104},[40,1472,674],{"class":90},[40,1474,1475],{"class":94}," region, s ",[40,1477,680],{"class":90},[40,1479,372],{"class":94},[40,1481,375],{"class":50},[40,1483,378],{"class":94},[40,1485,381],{"class":50},[40,1487,1488],{"class":94},"]:\n",[40,1490,1491,1494,1496,1498,1500,1502,1504],{"class":42,"line":111},[40,1492,1493],{"class":94},"    q1, q3 ",[40,1495,117],{"class":90},[40,1497,188],{"class":94},[40,1499,192],{"class":191},[40,1501,143],{"class":94},[40,1503,202],{"class":191},[40,1505,205],{"class":94},[40,1507,1508,1511,1513,1515,1517,1519,1521,1524,1526],{"class":42,"line":129},[40,1509,1510],{"class":94},"    fence ",[40,1512,117],{"class":90},[40,1514,216],{"class":94},[40,1516,247],{"class":90},[40,1518,238],{"class":191},[40,1520,241],{"class":90},[40,1522,1523],{"class":94}," (q3 ",[40,1525,219],{"class":90},[40,1527,1528],{"class":94}," q1)\n",[40,1530,1531,1534,1537,1540,1542,1545,1547,1550,1552,1555,1558,1560,1563],{"class":42,"line":162},[40,1532,1533],{"class":94},"    outliers ",[40,1535,1536],{"class":90},"+=",[40,1538,1539],{"class":94}," [(region, d) ",[40,1541,674],{"class":90},[40,1543,1544],{"class":94}," d ",[40,1546,680],{"class":90},[40,1548,1549],{"class":94}," s[s ",[40,1551,353],{"class":90},[40,1553,1554],{"class":94}," fence].sort_values(",[40,1556,1557],{"class":689},"ascending",[40,1559,117],{"class":90},[40,1561,1562],{"class":191},"False",[40,1564,1565],{"class":94},")]\n",[40,1567,1568,1570,1572,1574,1577,1580,1582,1584,1587],{"class":42,"line":167},[40,1569,660],{"class":94},[40,1571,443],{"class":191},[40,1573,143],{"class":94},[40,1575,1576],{"class":191},"9",[40,1578,1579],{"class":94},", [",[40,1581,375],{"class":50},[40,1583,143],{"class":94},[40,1585,1586],{"class":50},"\"Outlier (days)\"",[40,1588,205],{"class":94},[40,1590,1591,1593,1596,1598,1600,1603,1605,1607,1609],{"class":42,"line":180},[40,1592,674],{"class":90},[40,1594,1595],{"class":94}," r, (region, d) ",[40,1597,680],{"class":90},[40,1599,683],{"class":191},[40,1601,1602],{"class":94},"(outliers, ",[40,1604,690],{"class":689},[40,1606,117],{"class":90},[40,1608,695],{"class":191},[40,1610,698],{"class":94},[40,1612,1613,1615,1617],{"class":42,"line":208},[40,1614,703],{"class":94},[40,1616,1576],{"class":191},[40,1618,1619],{"class":94},", [region, d])\n",[40,1621,1622,1625,1628,1630,1633],{"class":42,"line":225},[40,1623,1624],{"class":94},"sheet.set_column(",[40,1626,1627],{"class":50},"\"A:A\"",[40,1629,143],{"class":94},[40,1631,1632],{"class":191},"10",[40,1634,126],{"class":94},[40,1636,1637,1639,1642,1644,1646],{"class":42,"line":257},[40,1638,1624],{"class":94},[40,1640,1641],{"class":50},"\"B:G\"",[40,1643,143],{"class":94},[40,1645,453],{"class":191},[40,1647,126],{"class":94},[40,1649,1650],{"class":42,"line":286},[40,1651,1652],{"class":94},"book.close()\n",[40,1654,1655,1657,1659,1662,1665,1669,1672,1675,1678,1681],{"class":42,"line":313},[40,1656,390],{"class":191},[40,1658,1004],{"class":94},[40,1660,1661],{"class":90},"f",[40,1663,1664],{"class":50},"\"",[40,1666,1668],{"class":1667},"sSjpA","{",[40,1670,1671],{"class":191},"len",[40,1673,1674],{"class":94},"(outliers)",[40,1676,1677],{"class":1667},"}",[40,1679,1680],{"class":50}," outliers:\"",[40,1682,1683],{"class":94},", outliers)\n",[10,1685,1686,1687,1690,1691,23],{},"Only high outliers occur here, because delivery times cannot go far below zero; the same loop with ",[37,1688,1689],{},"s \u003C q1 - 1.5 * iqr"," finds low ones. Finding outliers in general is covered in ",[19,1692,1694],{"href":1693},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fdetect-outliers-in-excel-data-with-pandas\u002F","Detect Outliers in Excel Data with pandas",[25,1696,1698],{"id":1697},"step-6-check-the-rendered-chart","Step 6 — Check the rendered chart",[30,1700,1702],{"className":81,"code":1701,"language":83,"meta":35,"style":35},"import subprocess\nfrom PIL import Image\n\nsubprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"pdf\", \"delivery_boxplot.xlsx\"], check=True, capture_output=True)\nsubprocess.run([\"pdftoppm\", \"-png\", \"-r\", \"60\", \"-f\", \"1\", \"-l\", \"1\", \"delivery_boxplot.pdf\", \"boxplot\"], check=True)\ncounts = {c: n for n, c in Image.open(\"boxplot-1.png\").convert(\"RGB\").getcolors(maxcolors=1_000_000)}\nfor label, rgb in {\"boxes\": (0xE0, 0xE7, 0xFF), \"medians\": (0x43, 0x38, 0xCA), \"whiskers\": (0x33, 0x41, 0x55)}.items():\n    print(f\"{label:9} {'drawn' if counts.get(rgb, 0) > 20 else 'MISSING'}\")\n",[37,1703,1704,1711,1725,1729,1777,1838,1881,1974],{"__ignoreMap":35},[40,1705,1706,1708],{"class":42,"line":43},[40,1707,91],{"class":90},[40,1709,1710],{"class":94}," subprocess\n",[40,1712,1713,1716,1719,1722],{"class":42,"line":104},[40,1714,1715],{"class":90},"from",[40,1717,1718],{"class":191}," PIL",[40,1720,1721],{"class":90}," import",[40,1723,1724],{"class":94}," Image\n",[40,1726,1727],{"class":42,"line":111},[40,1728,108],{"emptyLinePlaceholder":107},[40,1730,1731,1734,1737,1739,1742,1744,1747,1749,1752,1754,1756,1759,1762,1764,1766,1768,1771,1773,1775],{"class":42,"line":129},[40,1732,1733],{"class":94},"subprocess.run([",[40,1735,1736],{"class":50},"\"soffice\"",[40,1738,143],{"class":94},[40,1740,1741],{"class":50},"\"--headless\"",[40,1743,143],{"class":94},[40,1745,1746],{"class":50},"\"--convert-to\"",[40,1748,143],{"class":94},[40,1750,1751],{"class":50},"\"pdf\"",[40,1753,143],{"class":94},[40,1755,596],{"class":50},[40,1757,1758],{"class":94},"], ",[40,1760,1761],{"class":689},"check",[40,1763,117],{"class":90},[40,1765,1071],{"class":191},[40,1767,143],{"class":94},[40,1769,1770],{"class":689},"capture_output",[40,1772,117],{"class":90},[40,1774,1071],{"class":191},[40,1776,126],{"class":94},[40,1778,1779,1781,1784,1786,1789,1791,1794,1796,1799,1801,1804,1806,1809,1811,1814,1816,1818,1820,1823,1825,1828,1830,1832,1834,1836],{"class":42,"line":162},[40,1780,1733],{"class":94},[40,1782,1783],{"class":50},"\"pdftoppm\"",[40,1785,143],{"class":94},[40,1787,1788],{"class":50},"\"-png\"",[40,1790,143],{"class":94},[40,1792,1793],{"class":50},"\"-r\"",[40,1795,143],{"class":94},[40,1797,1798],{"class":50},"\"60\"",[40,1800,143],{"class":94},[40,1802,1803],{"class":50},"\"-f\"",[40,1805,143],{"class":94},[40,1807,1808],{"class":50},"\"1\"",[40,1810,143],{"class":94},[40,1812,1813],{"class":50},"\"-l\"",[40,1815,143],{"class":94},[40,1817,1808],{"class":50},[40,1819,143],{"class":94},[40,1821,1822],{"class":50},"\"delivery_boxplot.pdf\"",[40,1824,143],{"class":94},[40,1826,1827],{"class":50},"\"boxplot\"",[40,1829,1758],{"class":94},[40,1831,1761],{"class":689},[40,1833,117],{"class":90},[40,1835,1071],{"class":191},[40,1837,126],{"class":94},[40,1839,1840,1843,1845,1848,1850,1853,1855,1858,1861,1864,1867,1870,1873,1875,1878],{"class":42,"line":167},[40,1841,1842],{"class":94},"counts ",[40,1844,117],{"class":90},[40,1846,1847],{"class":94}," {c: n ",[40,1849,674],{"class":90},[40,1851,1852],{"class":94}," n, c ",[40,1854,680],{"class":90},[40,1856,1857],{"class":94}," Image.open(",[40,1859,1860],{"class":50},"\"boxplot-1.png\"",[40,1862,1863],{"class":94},").convert(",[40,1865,1866],{"class":50},"\"RGB\"",[40,1868,1869],{"class":94},").getcolors(",[40,1871,1872],{"class":689},"maxcolors",[40,1874,117],{"class":90},[40,1876,1877],{"class":191},"1_000_000",[40,1879,1880],{"class":94},")}\n",[40,1882,1883,1885,1888,1890,1892,1895,1898,1901,1904,1906,1908,1911,1913,1915,1918,1921,1924,1926,1928,1931,1933,1935,1938,1940,1942,1945,1947,1950,1952,1954,1957,1959,1961,1964,1966,1968,1971],{"class":42,"line":180},[40,1884,674],{"class":90},[40,1886,1887],{"class":94}," label, rgb ",[40,1889,680],{"class":90},[40,1891,1128],{"class":94},[40,1893,1894],{"class":50},"\"boxes\"",[40,1896,1897],{"class":94},": (",[40,1899,1900],{"class":90},"0x",[40,1902,1903],{"class":191},"E0",[40,1905,143],{"class":94},[40,1907,1900],{"class":90},[40,1909,1910],{"class":191},"E7",[40,1912,143],{"class":94},[40,1914,1900],{"class":90},[40,1916,1917],{"class":191},"FF",[40,1919,1920],{"class":94},"), ",[40,1922,1923],{"class":50},"\"medians\"",[40,1925,1897],{"class":94},[40,1927,1900],{"class":90},[40,1929,1930],{"class":191},"43",[40,1932,143],{"class":94},[40,1934,1900],{"class":90},[40,1936,1937],{"class":191},"38",[40,1939,143],{"class":94},[40,1941,1900],{"class":90},[40,1943,1944],{"class":191},"CA",[40,1946,1920],{"class":94},[40,1948,1949],{"class":50},"\"whiskers\"",[40,1951,1897],{"class":94},[40,1953,1900],{"class":90},[40,1955,1956],{"class":191},"33",[40,1958,143],{"class":94},[40,1960,1900],{"class":90},[40,1962,1963],{"class":191},"41",[40,1965,143],{"class":94},[40,1967,1900],{"class":90},[40,1969,1970],{"class":191},"55",[40,1972,1973],{"class":94},")}.items():\n",[40,1975,1976,1979,1981,1983,1985,1987,1990,1993,1995,1997,2000,2003,2006,2008,2011,2013,2016,2019,2022,2024,2026],{"class":42,"line":208},[40,1977,1978],{"class":191},"    print",[40,1980,1004],{"class":94},[40,1982,1661],{"class":90},[40,1984,1664],{"class":50},[40,1986,1668],{"class":1667},[40,1988,1989],{"class":94},"label",[40,1991,1992],{"class":90},":9",[40,1994,1677],{"class":1667},[40,1996,1128],{"class":1667},[40,1998,1999],{"class":50},"'drawn'",[40,2001,2002],{"class":90}," if",[40,2004,2005],{"class":94}," counts.get(rgb, ",[40,2007,443],{"class":191},[40,2009,2010],{"class":94},") ",[40,2012,353],{"class":90},[40,2014,2015],{"class":191}," 20",[40,2017,2018],{"class":90}," else",[40,2020,2021],{"class":50}," 'MISSING'",[40,2023,1677],{"class":1667},[40,2025,1664],{"class":50},[40,2027,126],{"class":94},[10,2029,2030,2031,2034],{},"Counting pixels of each colour confirms that the boxes, median dashes and whiskers were all drawn. That check matters for this chart in particular: an earlier construction of the same plot, using stacked columns with ",[14,2032,2033],{},"custom"," error bars for the whiskers, rendered boxes without any whiskers in LibreOffice, which does not draw custom error-bar values from these files. The stock-chart construction uses only standard elements and renders the same in both applications.",[421,2036,430,2042,430,2045,430,2048,430,2053,430,2059,430,2064,430,2067,430,2071,430,2076,430,2079,430,2084,430,2089,430,2093,430,2096,430,2099,430,2102,430,2105,430,2109,430,2112,430,2116,430,2120,430,2124,430,2128,430,2134,430,2139,430,2144,430,2149,430,2154,430,2157,430,2162,430,2167,430,2172,430,2177,430,2182],{"viewBox":2037,"role":424,"ariaLabelledBy":2038,"xmlns":428,"style":2041},"4 0 748 280",[2039,2040],"bx-medians-t","bx-medians-d","width:100%;max-width:748px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[432,2043,2044],{"id":2039},"Median and quartiles per region",[436,2046,2047],{"id":2040},"Q1, median and Q3 delivery days per region. North 4.55, 6.05 and 7.78; South 4.97, 6.05 and 8.93; East 1.58, 2.85 and 4.03; West 5.55, 8.6 and 12.55. West is slowest and most variable, East fastest.",[440,2049],{"x":2050,"y":443,"width":2051,"height":2052,"fill":446},"4","748","280",[440,2054],{"x":2055,"y":2056,"width":2057,"height":2057,"rx":2058,"fill":454,"stroke":455,"style":888},"64.0","18.0","14.0","3",[469,2060,830],{"x":2061,"y":2062,"style":2063},"84.0","29.5","font-size:12px;font-weight:600;fill:var(--text,#172033);text-anchor:start",[440,2065],{"x":2066,"y":2056,"width":2057,"height":2057,"rx":2058,"fill":493,"stroke":484,"style":888},"127.2",[469,2068,2070],{"x":2069,"y":2062,"style":2063},"147.2","Median",[440,2072],{"x":2073,"y":2056,"width":2057,"height":2057,"rx":2058,"fill":2074,"stroke":2075,"style":888},"221.0","#d9f4f1","var(--teal,#0f9488)",[469,2077,864],{"x":2078,"y":2062,"style":2063},"241.0",[42,2080],{"x1":2081,"y1":2082,"x2":2083,"y2":2082,"stroke":455,"style":808},"44.0","242.0","736.0",[469,2085,443],{"x":2086,"y":2087,"style":2088},"26.0","246.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle",[42,2090],{"x1":2081,"y1":2091,"x2":2083,"y2":2091,"stroke":455,"style":2092},"178.7","stroke-width:1px;stroke-dasharray:5 4",[469,2094,1011],{"x":2086,"y":2095,"style":2088},"182.7",[42,2097],{"x1":2081,"y1":2098,"x2":2083,"y2":2098,"stroke":455,"style":2092},"115.3",[469,2100,1632],{"x":2086,"y":2101,"style":2088},"119.3",[42,2103],{"x1":2081,"y1":2104,"x2":2083,"y2":2104,"stroke":455,"style":2092},"52.0",[469,2106,2108],{"x":2086,"y":2107,"style":2088},"56.0","15",[42,2110],{"x1":2081,"y1":2082,"x2":2083,"y2":2082,"stroke":2111,"style":888},"var(--muted,#5b6780)",[469,2113,822],{"x":2114,"y":2115,"style":478},"130.5","260.0",[469,2117,2119],{"x":2118,"y":2115,"style":478},"303.5","South",[469,2121,2123],{"x":2122,"y":2115,"style":478},"476.5","East",[469,2125,2127],{"x":2126,"y":2115,"style":478},"649.5","West",[440,2129],{"x":2130,"y":2131,"width":2132,"height":2133,"rx":2058,"fill":454,"stroke":455,"style":888},"74.5","184.4","36.0","57.6",[440,2135],{"x":2136,"y":2137,"width":2132,"height":2138,"rx":2058,"fill":454,"stroke":455,"style":888},"247.5","179.0","63.0",[440,2140],{"x":2141,"y":2142,"width":2132,"height":2143,"rx":2058,"fill":454,"stroke":455,"style":888},"420.5","222.0","20.0",[440,2145],{"x":2146,"y":2147,"width":2132,"height":2148,"rx":2058,"fill":454,"stroke":455,"style":888},"593.5","171.7","70.3",[440,2150],{"x":2151,"y":2152,"width":2132,"height":2153,"rx":2058,"fill":493,"stroke":484,"style":888},"112.5","165.4","76.6",[440,2155],{"x":2156,"y":2152,"width":2132,"height":2153,"rx":2058,"fill":493,"stroke":484,"style":888},"285.5",[440,2158],{"x":2159,"y":2160,"width":2132,"height":2161,"rx":2058,"fill":493,"stroke":484,"style":888},"458.5","205.9","36.1",[440,2163],{"x":2164,"y":2165,"width":2132,"height":2166,"rx":2058,"fill":493,"stroke":484,"style":888},"631.5","133.1","108.9",[440,2168],{"x":2169,"y":2170,"width":2132,"height":2171,"rx":2058,"fill":2074,"stroke":2075,"style":888},"150.5","143.5","98.5",[440,2173],{"x":2174,"y":2175,"width":2132,"height":2176,"rx":2058,"fill":2074,"stroke":2075,"style":888},"323.5","128.9","113.1",[440,2178],{"x":2179,"y":2180,"width":2132,"height":2181,"rx":2058,"fill":2074,"stroke":2075,"style":888},"496.5","191.0","51.0",[440,2183],{"x":2184,"y":2185,"width":2132,"height":2186,"rx":2058,"fill":2074,"stroke":2075,"style":888},"669.5","83.0","159.0",[25,2188,2190],{"id":2189},"performance-notes","Performance notes",[10,2192,2193],{},"The statistics are a group-by over the raw data and take milliseconds even for millions of rows; the chart itself only ever holds five numbers per group, so it stays small and fast however much data sits behind it. That is a real advantage over pasting raw data into Excel's own box-plot type, which recalculates from every point. Keep the raw data in its own sheet (or out of the workbook entirely) and write only the summary table the chart needs.",[25,2195,2197],{"id":2196},"common-pitfalls","Common pitfalls",[2199,2200,2201,2209,2215,2221,2227],"ul",{},[2202,2203,2204,2208],"li",{},[2205,2206,2207],"strong",{},"Expecting a native box-plot type."," The Excel 2016 box-and-whisker chart cannot be written by openpyxl or XlsxWriter; build it from a stock chart.",[2202,2210,2211,2214],{},[2205,2212,2213],{},"Wrong series order."," Stock charts need open, high, low, close in that order; anything else draws the wrong shapes.",[2202,2216,2217,2220],{},[2205,2218,2219],{},"Different up and down colours."," Style both identically, since Q3 is always above Q1.",[2202,2222,2223,2226],{},[2205,2224,2225],{},"Unstated whisker rule."," Say whether whiskers are min–max or 1.5 × IQR.",[2202,2228,2229,2232],{},[2205,2230,2231],{},"Custom error bars for whiskers."," They do not render in every application; high-low lines do.",[25,2234,2236],{"id":2235},"conclusion","Conclusion",[10,2238,2239],{},"Compute Q1, median, Q3 and the whisker ends per group with pandas, write them to the sheet as open (Q1), high (upper whisker), low (lower whisker) and close (Q3), and build a stock chart with invisible series: up-down bars draw the boxes and high-low lines the whiskers. Add the median as a line series with dash markers, state the whisker rule in the title, list outliers in a table beside the chart, and render it once to confirm every element appears.",[25,2241,2243],{"id":2242},"frequently-asked-questions","Frequently asked questions",[10,2245,2246,2249],{},[2205,2247,2248],{},"Can openpyxl or XlsxWriter create Excel's box and whisker chart?","\nNo. The box-and-whisker type added in Excel 2016 uses a newer chart format that these libraries do not write. A stock chart with up-down bars and high-low lines produces an equivalent, editable box plot.",[10,2251,2252,2255],{},[2205,2253,2254],{},"How do I make a box plot from a stock chart?","\nUse four series in the order open, high, low, close with Q1, upper whisker, lower whisker and Q3. Up-down bars then draw the box from Q1 to Q3 and high-low lines draw the whiskers.",[10,2257,2258,2261],{},[2205,2259,2260],{},"How do I add the median to the box plot?","\nAdd the median values as a line series with no line and a long-dash marker, and combine that line chart with the stock chart so the dash sits across each box.",[10,2263,2264,2267],{},[2205,2265,2266],{},"How are box plot whiskers calculated?","\nCommonly with Tukey's rule: each whisker reaches the most extreme data point within 1.5 times the interquartile range from the box. Points beyond are outliers. Some charts use the minimum and maximum instead, so state the rule.",[25,2269,2271],{"id":2270},"related","Related",[2199,2273,2274,2280,2287,2294],{},[2202,2275,2276,2277,2279],{},"Up one level: ",[19,2278,22],{"href":21}," — native charts from Python.",[2202,2281,2282,2286],{},[19,2283,2285],{"href":2284},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-histogram-in-excel-with-python\u002F","Create a Histogram in Excel with Python"," — another view of a distribution.",[2202,2288,2289,2293],{},[19,2290,2292],{"href":2291},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-bullet-chart-in-excel-with-python\u002F","Create a Bullet Chart in Excel with Python"," — combining series into a custom chart.",[2202,2295,2296,2300],{},[19,2297,2299],{"href":2298},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-a-matplotlib-chart-in-an-excel-report\u002F","Embed a matplotlib Chart in an Excel Report"," — box plots with outlier dots as pictures.",[2302,2303,2304],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":35,"searchDepth":104,"depth":104,"links":2306},[2307,2308,2309,2310,2311,2312,2313,2314,2315,2316,2317,2318],{"id":27,"depth":104,"text":28},{"id":77,"depth":104,"text":78},{"id":563,"depth":104,"text":564},{"id":962,"depth":104,"text":963},{"id":1188,"depth":104,"text":1189},{"id":1449,"depth":104,"text":1450},{"id":1697,"depth":104,"text":1698},{"id":2189,"depth":104,"text":2190},{"id":2196,"depth":104,"text":2197},{"id":2235,"depth":104,"text":2236},{"id":2242,"depth":104,"text":2243},{"id":2270,"depth":104,"text":2271},"2026-10-02","Excel's built-in box-and-whisker chart cannot be written by openpyxl or XlsxWriter, but a native, editable box plot can be built from a stock chart: compute quartiles and whisker ends with pandas, map them to open, high, low and close, let up-down bars draw the boxes and high-low lines the whiskers, add a median marker, and list outliers beside the chart.","md",[2323,2325,2327,2329],{"q":2248,"a":2324},"No. The box-and-whisker type added in Excel 2016 uses a newer chart format that these libraries do not write. A stock chart with up-down bars and high-low lines produces an equivalent, editable box plot.",{"q":2254,"a":2326},"Use four series in the order open, high, low, close with Q1, upper whisker, lower whisker and Q3. Up-down bars then draw the box from Q1 to Q3 and high-low lines draw the whiskers.",{"q":2260,"a":2328},"Add the median values as a line series with no line and a long-dash marker, and combine that line chart with the stock chart so the dash sits across each box.",{"q":2266,"a":2330},"Commonly with Tukey's rule: each whisker reaches the most extreme data point within 1.5 times the interquartile range from the box. Points beyond are outliers. Some charts use the minimum and maximum instead, so state the rule.","create a box and whisker chart in excel with python",null,{"breadcrumb":2334},[2335,2338,2341],{"name":2336,"item":2337},"Home","\u002F",{"name":2339,"item":2340},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":22,"item":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-box-and-whisker-chart-in-excel-with-python",{"title":2344,"description":2345},"Box and Whisker Chart in Excel from Python (Native Chart)","Build a box plot in Excel with Python: pandas quartiles and 1.5×IQR whiskers, a stock chart with Q1\u002Fhigh\u002Flow\u002FQ3 series, up-down bars as boxes, high-low lines as whiskers, a median marker series, an outlier table, and a rendered check.","create-a-box-and-whisker-chart-in-excel-with-python","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-box-and-whisker-chart-in-excel-with-python\u002Findex",[2349,2350],"charts-and-images","openpyxl","how-to","7YrN1lR9SHL7dGXLSz5EiSySgtE_iS17DSWEdHHY3vQ",[2354,2358],{"title":2355,"path":2356,"stem":2357,"children":-1},"Add a Line Chart to an Excel Report with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fadd-line-chart-to-excel-report-with-python","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fadd-line-chart-to-excel-report-with-python\u002Findex",{"title":2359,"path":2360,"stem":2361,"children":-1},"Create a Bubble Chart in Excel with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-bubble-chart-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-bubble-chart-with-openpyxl\u002Findex",[2363,2365],{"path":2364,"title":22},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl",{"path":2366,"title":2339},"\u002Fformatting-and-charting-excel-reports-with-python",[],[2369,2372,2375,2378,2381,2384],{"path":2370,"title":2371,"kind":2351},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs","Build a Dashboard Sheet with Charts from Multiple Tabs",{"path":2373,"title":2374,"kind":2351},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard","Build a Rolling 12-Month Chart on an Excel Dashboard",{"path":2376,"title":2377,"kind":2351},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fkeep-charts-and-images-when-filling-an-excel-template","Keep Charts and Images When Filling an Excel Template",{"path":2379,"title":2380,"kind":2351},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter","Add a Chart to an Excel File with xlsxwriter",{"path":2382,"title":2383,"kind":2351},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-sparklines-to-an-excel-report-with-xlsxwriter","Add Sparklines to an Excel Report with xlsxwriter",{"path":2385,"title":2386,"kind":2351},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python","Build a Waterfall Chart in Excel with Python",1790972108932]