[{"data":1,"prerenderedAt":2129},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-crosstab-from-excel-data-with-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-crosstab-from-excel-data-with-pandas":2098,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-crosstab-from-excel-data-with-pandas":2107,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-crosstab-from-excel-data-with-pandas":2112,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-crosstab-from-excel-data-with-pandas":2113},{"id":4,"title":5,"body":6,"dateModified":2063,"datePublished":2063,"description":2064,"extension":2065,"faq":2066,"keyword":2075,"kind":2076,"meta":2077,"navigation":454,"path":2087,"seo":2088,"slug":2091,"stem":2092,"topics":2093,"type":2096,"__hash__":2097},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-crosstab-from-excel-data-with-pandas\u002Findex.md","Build a Crosstab from Excel Data with pandas",{"type":7,"value":8,"toc":2048},"minimark",[9,24,394,399,426,690,694,701,756,763,767,774,838,852,945,949,964,1038,1059,1063,1070,1174,1180,1184,1189,1292,1298,1302,1309,1381,1384,1522,1526,1529,1611,1614,1618,1621,1901,1909,1913,1960,1964,1983,1987,1993,1999,2005,2011,2015,2044],[10,11,12,13,17,18,23],"p",{},"\"How many orders from each channel were refunded?\" \"What share of complaints in each region were resolved?\" Questions like these need a cross-tabulation: one category down the side, another across the top, and a count in every cell. In Excel you would build a pivot table with a count of rows; in pandas, ",[14,15,16],"code",{},"pd.crosstab"," does it in one call and adds percentages, totals and banding without helper columns. This guide, part of ",[19,20,22],"a",{"href":21},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002F","Creating Pivot Tables from Excel Data",", builds crosstabs from an Excel sheet and covers the traps that make counts quietly wrong.",[25,26,34,35,34,39,34,43,34,51,34,60,34,64,34,71,34,75,34,79,34,83,34,88,34,90,34,94,34,96,34,99,34,102,34,106,34,108,34,112,34,114,34,117,34,120,34,124,34,126,34,128,34,130,34,133,34,136,34,140,34,142,34,145,34,147,34,149,34,152,34,156,34,158,34,161,34,163,34,165,34,168,34,172,34,174,34,176,34,178,34,180,34,183,34,187,34,189,34,191,34,193,34,195,34,198,34,202,34,204,34,206,34,208,34,210,34,213,34,217,34,219,34,221,34,223,34,225,34,228,34,231,34,234,34,238,34,241,34,244,34,248,34,252,34,256,34,258,34,261,34,263,34,265,34,267,34,269,34,271,34,273,34,275,34,278,34,280,34,282,34,284,34,286,34,288,34,290,34,292,34,294,34,296,34,298,34,300,34,302,34,304,34,306,34,308,34,310,34,312,34,314,34,316,34,318,34,320,34,322,34,324,34,326,34,328,34,330,34,332,34,334,34,336,34,338,34,340,34,342,34,344,34,346,34,348,34,350,34,352,34,354,34,356,34,358,34,364,34,368,34,376,34,381,34,389],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"37 0 686 332","img",[30,31],"ct-overview-t","ct-overview-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:686px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"From one row per order to a table of counts",[40,41,42],"desc",{"id":31},"Eight order rows each carry a channel and a status. The crosstab counts every channel and status combination - Phone has one paid order, Store two paid and one refunded, Web three paid and one refunded - with totals of six paid and two refunded.",[44,45],"rect",{"x":46,"y":47,"width":48,"height":49,"fill":50},"37","0","686","332","#ffffff",[44,52],{"x":53,"y":54,"width":55,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"53.4","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[44,61],{"x":62,"y":54,"width":63,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"85.4","75.1",[65,66,70],"text",{"x":67,"y":68,"style":69},"122.9","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[44,72],{"x":73,"y":54,"width":74,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"160.5","83.9",[65,76,78],{"x":77,"y":68,"style":69},"202.4","B",[44,80],{"x":53,"y":81,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},"64.0","28.0",[65,84,87],{"x":85,"y":86,"style":69},"69.4","83.0","1",[44,89],{"x":62,"y":81,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,91,93],{"x":67,"y":86,"style":92},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Channel",[44,95],{"x":73,"y":81,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,97,98],{"x":77,"y":86,"style":92},"Status",[44,100],{"x":53,"y":101,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},"92.0",[65,103,105],{"x":85,"y":104,"style":69},"111.0","2",[44,107],{"x":62,"y":101,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,109,111],{"x":67,"y":104,"style":110},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Web",[44,113],{"x":73,"y":101,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,115,116],{"x":77,"y":104,"style":110},"Paid",[44,118],{"x":53,"y":119,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},"120.0",[65,121,123],{"x":85,"y":122,"style":69},"139.0","3",[44,125],{"x":62,"y":119,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,127,111],{"x":67,"y":122,"style":110},[44,129],{"x":73,"y":119,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,131,132],{"x":77,"y":122,"style":110},"Refunded",[44,134],{"x":53,"y":135,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},"148.0",[65,137,139],{"x":85,"y":138,"style":69},"167.0","4",[44,141],{"x":62,"y":135,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,143,144],{"x":67,"y":138,"style":110},"Store",[44,146],{"x":73,"y":135,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,148,116],{"x":77,"y":138,"style":110},[44,150],{"x":53,"y":151,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},"176.0",[65,153,155],{"x":85,"y":154,"style":69},"195.0","5",[44,157],{"x":62,"y":151,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,159,160],{"x":67,"y":154,"style":110},"Phone",[44,162],{"x":73,"y":151,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,164,116],{"x":77,"y":154,"style":110},[44,166],{"x":53,"y":167,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},"204.0",[65,169,171],{"x":85,"y":170,"style":69},"223.0","6",[44,173],{"x":62,"y":167,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,175,111],{"x":67,"y":170,"style":110},[44,177],{"x":73,"y":167,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,179,116],{"x":77,"y":170,"style":110},[44,181],{"x":53,"y":182,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},"232.0",[65,184,186],{"x":85,"y":185,"style":69},"251.0","7",[44,188],{"x":62,"y":182,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,190,144],{"x":67,"y":185,"style":110},[44,192],{"x":73,"y":182,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,194,116],{"x":77,"y":185,"style":110},[44,196],{"x":53,"y":197,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},"260.0",[65,199,201],{"x":85,"y":200,"style":69},"279.0","8",[44,203],{"x":62,"y":197,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,205,144],{"x":67,"y":200,"style":110},[44,207],{"x":73,"y":197,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,209,132],{"x":77,"y":200,"style":110},[44,211],{"x":53,"y":212,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},"288.0",[65,214,216],{"x":85,"y":215,"style":69},"307.0","9",[44,218],{"x":62,"y":212,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,220,111],{"x":67,"y":215,"style":110},[44,222],{"x":73,"y":212,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,224,116],{"x":77,"y":215,"style":110},[44,226],{"x":227,"y":54,"width":55,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"409.3",[44,229],{"x":230,"y":54,"width":63,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"441.3",[65,232,70],{"x":233,"y":68,"style":69},"478.9",[44,235],{"x":236,"y":54,"width":237,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"516.4","50.8",[65,239,78],{"x":240,"y":68,"style":69},"541.9",[44,242],{"x":243,"y":54,"width":74,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"567.3",[65,245,247],{"x":246,"y":68,"style":69},"609.2","C",[44,249],{"x":250,"y":54,"width":251,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"651.2","55.5",[65,253,255],{"x":254,"y":68,"style":69},"678.9","D",[44,257],{"x":227,"y":81,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},[65,259,87],{"x":260,"y":86,"style":69},"425.3",[44,262],{"x":230,"y":81,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,264,93],{"x":233,"y":86,"style":92},[44,266],{"x":236,"y":81,"width":237,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,268,116],{"x":240,"y":86,"style":92},[44,270],{"x":243,"y":81,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,272,132],{"x":246,"y":86,"style":92},[44,274],{"x":250,"y":81,"width":251,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,276,277],{"x":254,"y":86,"style":92},"Total",[44,279],{"x":227,"y":101,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},[65,281,105],{"x":260,"y":104,"style":69},[44,283],{"x":230,"y":101,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,285,160],{"x":233,"y":104,"style":110},[44,287],{"x":236,"y":101,"width":237,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,289,87],{"x":240,"y":104,"style":110},[44,291],{"x":243,"y":101,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,293,47],{"x":246,"y":104,"style":110},[44,295],{"x":250,"y":101,"width":251,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,297,87],{"x":254,"y":104,"style":110},[44,299],{"x":227,"y":119,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},[65,301,123],{"x":260,"y":122,"style":69},[44,303],{"x":230,"y":119,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,305,144],{"x":233,"y":122,"style":110},[44,307],{"x":236,"y":119,"width":237,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,309,105],{"x":240,"y":122,"style":110},[44,311],{"x":243,"y":119,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,313,87],{"x":246,"y":122,"style":110},[44,315],{"x":250,"y":119,"width":251,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,317,123],{"x":254,"y":122,"style":110},[44,319],{"x":227,"y":135,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},[65,321,139],{"x":260,"y":138,"style":69},[44,323],{"x":230,"y":135,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,325,111],{"x":233,"y":138,"style":110},[44,327],{"x":236,"y":135,"width":237,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,329,123],{"x":240,"y":138,"style":110},[44,331],{"x":243,"y":135,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,333,87],{"x":246,"y":138,"style":110},[44,335],{"x":250,"y":135,"width":251,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,337,139],{"x":254,"y":138,"style":110},[44,339],{"x":227,"y":151,"width":55,"height":82,"rx":47,"fill":57,"stroke":58,"style":59},[65,341,155],{"x":260,"y":154,"style":69},[44,343],{"x":230,"y":151,"width":63,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,345,277],{"x":233,"y":154,"style":110},[44,347],{"x":236,"y":151,"width":237,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,349,171],{"x":240,"y":154,"style":110},[44,351],{"x":243,"y":151,"width":74,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,353,105],{"x":246,"y":154,"style":110},[44,355],{"x":250,"y":151,"width":251,"height":82,"rx":47,"fill":50,"stroke":58,"style":59},[65,357,201],{"x":254,"y":154,"style":110},[65,359,363],{"x":360,"y":361,"style":362},"148.9","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","before",[65,365,367],{"x":366,"y":361,"style":362},"558.0","after",[369,370],"line",{"x1":371,"y1":372,"x2":373,"y2":372,"stroke":374,"style":375},"254.4","122.0","395.3","var(--brand,#5b5cf0)","stroke-width:2px",[377,378],"polygon",{"points":379,"fill":380},"399.3,122.0 390.3,127.0 390.3,117.0","#5b5cf0",[44,382],{"x":383,"y":384,"width":385,"height":386,"rx":387,"fill":50,"stroke":58,"style":388},"276.9","89.0","100.0","22.0","11","stroke-width:1.2px",[65,390,16],{"x":391,"y":392,"style":393},"326.8","104.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",[395,396,398],"h2",{"id":397},"prerequisites","Prerequisites",[400,401,406],"pre",{"className":402,"code":403,"language":404,"meta":405,"style":405},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,407,408],{"__ignoreMap":405},[409,410,412,416,420,423],"span",{"class":369,"line":411},1,[409,413,415],{"class":414},"sMTad","pip",[409,417,419],{"class":418},"srMev"," install",[409,421,422],{"class":418}," pandas",[409,424,425],{"class":418}," openpyxl\n",[400,427,431],{"className":428,"code":429,"language":430,"meta":405,"style":405},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\norders = pd.DataFrame({\n    \"Channel\": [\"Web\", \"Web\", \"Store\", \"Phone\", \"Web\", \"Store\", \"Store\", \"Web\"],\n    \"Status\": [\"Paid\", \"Refunded\", \"Paid\", \"Paid\", \"Paid\", \"Paid\", \"Refunded\", \"Paid\"],\n    \"Amount\": [50, 20, 300, 80, 65, 120, 40, 95],\n    \"Rating\": [5, 2, 4, None, 4, 5, 1, 3],\n})\norders.to_excel(\"orders.xlsx\", index=False)\ndf = pd.read_excel(\"orders.xlsx\")\n","python",[14,432,433,449,456,468,514,556,605,646,652,675],{"__ignoreMap":405},[409,434,435,439,443,446],{"class":369,"line":411},[409,436,438],{"class":437},"s-kum","import",[409,440,442],{"class":441},"skGVy"," pandas ",[409,444,445],{"class":437},"as",[409,447,448],{"class":441}," pd\n",[409,450,452],{"class":369,"line":451},2,[409,453,455],{"emptyLinePlaceholder":454},true,"\n",[409,457,459,462,465],{"class":369,"line":458},3,[409,460,461],{"class":441},"orders ",[409,463,464],{"class":437},"=",[409,466,467],{"class":441}," pd.DataFrame({\n",[409,469,471,474,477,480,483,485,487,490,492,495,497,499,501,503,505,507,509,511],{"class":369,"line":470},4,[409,472,473],{"class":418},"    \"Channel\"",[409,475,476],{"class":441},": [",[409,478,479],{"class":418},"\"Web\"",[409,481,482],{"class":441},", ",[409,484,479],{"class":418},[409,486,482],{"class":441},[409,488,489],{"class":418},"\"Store\"",[409,491,482],{"class":441},[409,493,494],{"class":418},"\"Phone\"",[409,496,482],{"class":441},[409,498,479],{"class":418},[409,500,482],{"class":441},[409,502,489],{"class":418},[409,504,482],{"class":441},[409,506,489],{"class":418},[409,508,482],{"class":441},[409,510,479],{"class":418},[409,512,513],{"class":441},"],\n",[409,515,517,520,522,525,527,530,532,534,536,538,540,542,544,546,548,550,552,554],{"class":369,"line":516},5,[409,518,519],{"class":418},"    \"Status\"",[409,521,476],{"class":441},[409,523,524],{"class":418},"\"Paid\"",[409,526,482],{"class":441},[409,528,529],{"class":418},"\"Refunded\"",[409,531,482],{"class":441},[409,533,524],{"class":418},[409,535,482],{"class":441},[409,537,524],{"class":418},[409,539,482],{"class":441},[409,541,524],{"class":418},[409,543,482],{"class":441},[409,545,524],{"class":418},[409,547,482],{"class":441},[409,549,529],{"class":418},[409,551,482],{"class":441},[409,553,524],{"class":418},[409,555,513],{"class":441},[409,557,559,562,564,568,570,573,575,578,580,583,585,588,590,593,595,598,600,603],{"class":369,"line":558},6,[409,560,561],{"class":418},"    \"Amount\"",[409,563,476],{"class":441},[409,565,567],{"class":566},"sP0c6","50",[409,569,482],{"class":441},[409,571,572],{"class":566},"20",[409,574,482],{"class":441},[409,576,577],{"class":566},"300",[409,579,482],{"class":441},[409,581,582],{"class":566},"80",[409,584,482],{"class":441},[409,586,587],{"class":566},"65",[409,589,482],{"class":441},[409,591,592],{"class":566},"120",[409,594,482],{"class":441},[409,596,597],{"class":566},"40",[409,599,482],{"class":441},[409,601,602],{"class":566},"95",[409,604,513],{"class":441},[409,606,608,611,613,615,617,619,621,623,625,628,630,632,634,636,638,640,642,644],{"class":369,"line":607},7,[409,609,610],{"class":418},"    \"Rating\"",[409,612,476],{"class":441},[409,614,155],{"class":566},[409,616,482],{"class":441},[409,618,105],{"class":566},[409,620,482],{"class":441},[409,622,139],{"class":566},[409,624,482],{"class":441},[409,626,627],{"class":566},"None",[409,629,482],{"class":441},[409,631,139],{"class":566},[409,633,482],{"class":441},[409,635,155],{"class":566},[409,637,482],{"class":441},[409,639,87],{"class":566},[409,641,482],{"class":441},[409,643,123],{"class":566},[409,645,513],{"class":441},[409,647,649],{"class":369,"line":648},8,[409,650,651],{"class":441},"})\n",[409,653,655,658,661,663,667,669,672],{"class":369,"line":654},9,[409,656,657],{"class":441},"orders.to_excel(",[409,659,660],{"class":418},"\"orders.xlsx\"",[409,662,482],{"class":441},[409,664,666],{"class":665},"sa561","index",[409,668,464],{"class":437},[409,670,671],{"class":566},"False",[409,673,674],{"class":441},")\n",[409,676,678,681,683,686,688],{"class":369,"line":677},10,[409,679,680],{"class":441},"df ",[409,682,464],{"class":437},[409,684,685],{"class":441}," pd.read_excel(",[409,687,660],{"class":418},[409,689,674],{"class":441},[395,691,693],{"id":692},"step-1-counts-with-totals","Step 1 — Counts with totals",[10,695,696,697,700],{},"Pass the column for the rows and the column for the columns. ",[14,698,699],{},"margins=True"," adds totals on both axes:",[400,702,704],{"className":428,"code":703,"language":430,"meta":405,"style":405},"counts = pd.crosstab(df[\"Channel\"], df[\"Status\"], margins=True, margins_name=\"Total\")\nprint(counts)\n",[14,705,706,748],{"__ignoreMap":405},[409,707,708,711,713,716,719,722,725,728,731,733,736,738,741,743,746],{"class":369,"line":411},[409,709,710],{"class":441},"counts ",[409,712,464],{"class":437},[409,714,715],{"class":441}," pd.crosstab(df[",[409,717,718],{"class":418},"\"Channel\"",[409,720,721],{"class":441},"], df[",[409,723,724],{"class":418},"\"Status\"",[409,726,727],{"class":441},"], ",[409,729,730],{"class":665},"margins",[409,732,464],{"class":437},[409,734,735],{"class":566},"True",[409,737,482],{"class":441},[409,739,740],{"class":665},"margins_name",[409,742,464],{"class":437},[409,744,745],{"class":418},"\"Total\"",[409,747,674],{"class":441},[409,749,750,753],{"class":369,"line":451},[409,751,752],{"class":566},"print",[409,754,755],{"class":441},"(counts)\n",[10,757,758,759,762],{},"Every cell is the number of rows with that combination, and the combinations that never occur — Phone orders that were refunded — show 0 rather than being left out. That is the main practical difference from ",[14,760,761],{},"groupby([\"Channel\", \"Status\"]).size()",", which lists only the combinations that exist.",[395,764,766],{"id":765},"step-2-percentages-with-normalize","Step 2 — Percentages with normalize",[10,768,769,770,773],{},"Counts answer \"how many\"; most crosstab questions are really \"what share\". The ",[14,771,772],{},"normalize"," argument divides by the right total:",[400,775,777],{"className":428,"code":776,"language":430,"meta":405,"style":405},"by_row = pd.crosstab(df[\"Channel\"], df[\"Status\"], normalize=\"index\")\nprint(by_row.round(3))\n# Status    Paid  Refunded\n# Phone    1.000     0.000\n# Store    0.667     0.333\n# Web      0.750     0.250\n",[14,778,779,805,817,823,828,833],{"__ignoreMap":405},[409,780,781,784,786,788,790,792,794,796,798,800,803],{"class":369,"line":411},[409,782,783],{"class":441},"by_row ",[409,785,464],{"class":437},[409,787,715],{"class":441},[409,789,718],{"class":418},[409,791,721],{"class":441},[409,793,724],{"class":418},[409,795,727],{"class":441},[409,797,772],{"class":665},[409,799,464],{"class":437},[409,801,802],{"class":418},"\"index\"",[409,804,674],{"class":441},[409,806,807,809,812,814],{"class":369,"line":451},[409,808,752],{"class":566},[409,810,811],{"class":441},"(by_row.round(",[409,813,123],{"class":566},[409,815,816],{"class":441},"))\n",[409,818,819],{"class":369,"line":458},[409,820,822],{"class":821},"s-wDw","# Status    Paid  Refunded\n",[409,824,825],{"class":369,"line":470},[409,826,827],{"class":821},"# Phone    1.000     0.000\n",[409,829,830],{"class":369,"line":516},[409,831,832],{"class":821},"# Store    0.667     0.333\n",[409,834,835],{"class":369,"line":558},[409,836,837],{"class":821},"# Web      0.750     0.250\n",[10,839,840,843,844,847,848,851],{},[14,841,842],{},"normalize=\"index\""," makes each row add up to 1, answering \"what share of Web orders were refunded?\" (25%). ",[14,845,846],{},"normalize=\"columns\""," makes each column add up to 1, answering \"what share of refunds came from Web?\" (50%). ",[14,849,850],{},"normalize=\"all\""," divides by the grand total. Picking the wrong one produces numbers that look plausible and answer a different question, so name the direction in the sheet title: \"Refund rate by channel\" for rows, \"Refunds by channel\" for columns.",[25,853,34,859,34,862,34,865,34,869,34,874,34,879,34,885,34,888,34,892,34,896,34,900,34,902,34,907,34,909,34,912,34,915,34,918,34,920,34,923,34,925,34,928,34,931,34,934,34,936,34,940,34,942],{"viewBox":854,"role":28,"ariaLabelledBy":855,"xmlns":32,"style":858},"4 0 752 174",[856,857],"ct-normalize-t","ct-normalize-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[36,860,861],{"id":856},"Which total each normalize option divides by",[40,863,864],{"id":857},"normalize index divides each cell by its row total and answers what share of a channel's orders had each status. normalize columns divides by the column total and answers what share of each status came from each channel. normalize all divides by the grand total.",[44,866],{"x":139,"y":47,"width":867,"height":868,"fill":50},"752","174",[44,870],{"x":871,"y":872,"width":873,"height":54,"rx":47,"fill":50,"stroke":58,"style":59},"20.0","16.0","170.0",[44,875],{"x":876,"y":872,"width":877,"height":54,"rx":47,"fill":878,"stroke":374,"style":388},"190.0","275.0","#f0f4ff",[65,880,884],{"x":881,"y":882,"style":883},"327.5","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Divides by",[44,886],{"x":887,"y":872,"width":877,"height":54,"rx":47,"fill":878,"stroke":374,"style":388},"465.0",[65,889,891],{"x":890,"y":882,"style":883},"602.5","Web, Refunded",[44,893],{"x":871,"y":894,"width":873,"height":895,"rx":47,"fill":57,"stroke":58,"style":59},"56.0","34.0",[65,897,842],{"x":55,"y":898,"style":899},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[44,901],{"x":876,"y":894,"width":877,"height":895,"rx":47,"fill":50,"stroke":58,"style":59},[65,903,906],{"x":881,"y":904,"style":905},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","row total",[44,908],{"x":887,"y":894,"width":877,"height":895,"rx":47,"fill":50,"stroke":58,"style":59},[65,910,911],{"x":890,"y":904,"style":905},"1 of 4 = 25%",[44,913],{"x":871,"y":914,"width":873,"height":895,"rx":47,"fill":57,"stroke":58,"style":59},"90.0",[65,916,846],{"x":55,"y":917,"style":899},"112.0",[44,919],{"x":876,"y":914,"width":877,"height":895,"rx":47,"fill":50,"stroke":58,"style":59},[65,921,922],{"x":881,"y":104,"style":905},"column total",[44,924],{"x":887,"y":914,"width":877,"height":895,"rx":47,"fill":50,"stroke":58,"style":59},[65,926,927],{"x":890,"y":104,"style":905},"1 of 2 = 50%",[44,929],{"x":871,"y":930,"width":873,"height":895,"rx":47,"fill":57,"stroke":58,"style":59},"124.0",[65,932,850],{"x":55,"y":933,"style":899},"146.0",[44,935],{"x":876,"y":930,"width":877,"height":895,"rx":47,"fill":50,"stroke":58,"style":59},[65,937,939],{"x":881,"y":938,"style":905},"145.0","grand total",[44,941],{"x":887,"y":930,"width":877,"height":895,"rx":47,"fill":50,"stroke":58,"style":59},[65,943,944],{"x":890,"y":938,"style":905},"1 of 8 = 12.5%",[395,946,948],{"id":947},"step-3-sums-and-averages-instead-of-counts","Step 3 — Sums and averages instead of counts",[10,950,951,952,955,956,959,960,963],{},"To put a measure in the cells rather than a count, pass ",[14,953,954],{},"values"," and ",[14,957,958],{},"aggfunc",". Empty combinations then come back as ",[14,961,962],{},"NaN",", because there is nothing to sum — fill them explicitly when zero is the right answer:",[400,965,967],{"className":428,"code":966,"language":430,"meta":405,"style":405},"revenue = pd.crosstab(df[\"Channel\"], df[\"Status\"], values=df[\"Amount\"], aggfunc=\"sum\").fillna(0)\nprint(revenue)\n# Status    Paid  Refunded\n# Phone     80.0       0.0\n# Store    420.0      40.0\n# Web      210.0      20.0\n",[14,968,969,1012,1019,1023,1028,1033],{"__ignoreMap":405},[409,970,971,974,976,978,980,982,984,986,988,990,993,996,998,1000,1002,1005,1008,1010],{"class":369,"line":411},[409,972,973],{"class":441},"revenue ",[409,975,464],{"class":437},[409,977,715],{"class":441},[409,979,718],{"class":418},[409,981,721],{"class":441},[409,983,724],{"class":418},[409,985,727],{"class":441},[409,987,954],{"class":665},[409,989,464],{"class":437},[409,991,992],{"class":441},"df[",[409,994,995],{"class":418},"\"Amount\"",[409,997,727],{"class":441},[409,999,958],{"class":665},[409,1001,464],{"class":437},[409,1003,1004],{"class":418},"\"sum\"",[409,1006,1007],{"class":441},").fillna(",[409,1009,47],{"class":566},[409,1011,674],{"class":441},[409,1013,1014,1016],{"class":369,"line":451},[409,1015,752],{"class":566},[409,1017,1018],{"class":441},"(revenue)\n",[409,1020,1021],{"class":369,"line":458},[409,1022,822],{"class":821},[409,1024,1025],{"class":369,"line":470},[409,1026,1027],{"class":821},"# Phone     80.0       0.0\n",[409,1029,1030],{"class":369,"line":516},[409,1031,1032],{"class":821},"# Store    420.0      40.0\n",[409,1034,1035],{"class":369,"line":558},[409,1036,1037],{"class":821},"# Web      210.0      20.0\n",[10,1039,1040,1041,1043,1044,955,1047,1050,1051,1055,1056,1058],{},"For an average, leave the gaps as ",[14,1042,962],{},": an average of no orders is not zero. At this point ",[14,1045,1046],{},"crosstab",[14,1048,1049],{},"pivot_table"," are interchangeable; ",[19,1052,1054],{"href":1053},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcreate-pivot-table-from-excel-with-pandas\u002F","Create a Pivot Table from Excel with pandas"," covers the ",[14,1057,1049],{}," form.",[395,1060,1062],{"id":1061},"step-4-band-numbers-before-counting","Step 4 — Band numbers before counting",[10,1064,1065,1066,1069],{},"A crosstab of raw amounts would have one column per distinct value. Group numbers into bands with ",[14,1067,1068],{},"pd.cut"," first:",[400,1071,1073],{"className":428,"code":1072,"language":430,"meta":405,"style":405},"bands = pd.cut(df[\"Amount\"], bins=[0, 50, 100, 500], labels=[\"up to 50\", \"51-100\", \"over 100\"])\nprint(pd.crosstab(bands, df[\"Status\"]))\n# Status    Paid  Refunded\n# Amount\n# up to 50     1         2\n# 51-100       3         0\n# over 100     2         0\n",[14,1074,1075,1138,1150,1154,1159,1164,1169],{"__ignoreMap":405},[409,1076,1077,1080,1082,1085,1087,1089,1092,1094,1097,1099,1101,1103,1105,1108,1110,1113,1115,1118,1120,1122,1125,1127,1130,1132,1135],{"class":369,"line":411},[409,1078,1079],{"class":441},"bands ",[409,1081,464],{"class":437},[409,1083,1084],{"class":441}," pd.cut(df[",[409,1086,995],{"class":418},[409,1088,727],{"class":441},[409,1090,1091],{"class":665},"bins",[409,1093,464],{"class":437},[409,1095,1096],{"class":441},"[",[409,1098,47],{"class":566},[409,1100,482],{"class":441},[409,1102,567],{"class":566},[409,1104,482],{"class":441},[409,1106,1107],{"class":566},"100",[409,1109,482],{"class":441},[409,1111,1112],{"class":566},"500",[409,1114,727],{"class":441},[409,1116,1117],{"class":665},"labels",[409,1119,464],{"class":437},[409,1121,1096],{"class":441},[409,1123,1124],{"class":418},"\"up to 50\"",[409,1126,482],{"class":441},[409,1128,1129],{"class":418},"\"51-100\"",[409,1131,482],{"class":441},[409,1133,1134],{"class":418},"\"over 100\"",[409,1136,1137],{"class":441},"])\n",[409,1139,1140,1142,1145,1147],{"class":369,"line":451},[409,1141,752],{"class":566},[409,1143,1144],{"class":441},"(pd.crosstab(bands, df[",[409,1146,724],{"class":418},[409,1148,1149],{"class":441},"]))\n",[409,1151,1152],{"class":369,"line":458},[409,1153,822],{"class":821},[409,1155,1156],{"class":369,"line":470},[409,1157,1158],{"class":821},"# Amount\n",[409,1160,1161],{"class":369,"line":516},[409,1162,1163],{"class":821},"# up to 50     1         2\n",[409,1165,1166],{"class":369,"line":558},[409,1167,1168],{"class":821},"# 51-100       3         0\n",[409,1170,1171],{"class":369,"line":607},[409,1172,1173],{"class":821},"# over 100     2         0\n",[10,1175,1176,1177,1179],{},"The bins are right-inclusive by default, so 50 falls in \"up to 50\" and 100 in \"51-100\". State the edges in the labels so readers do not have to guess, and make sure the outer edges cover every value — anything outside the bins becomes ",[14,1178,962],{}," and, as the next step shows, drops out of the count.",[395,1181,1183],{"id":1182},"step-5-do-not-lose-rows-to-missing-values","Step 5 — Do not lose rows to missing values",[10,1185,1186,1188],{},[14,1187,1046],{}," silently drops any row where one of the grouping values is missing. The order with no rating disappears from a channel-by-rating table — and with it, the only Phone order:",[400,1190,1192],{"className":428,"code":1191,"language":430,"meta":405,"style":405},"print(pd.crosstab(df[\"Channel\"], df[\"Rating\"]).index.tolist())         # ['Store', 'Web']\nrating = df[\"Rating\"].astype(\"Int64\").astype(\"string\").fillna(\"No rating\")\nfull = pd.crosstab(df[\"Channel\"], rating, margins=True)\nprint(full.loc[\"All\", \"All\"], len(df))                                 # 8 8\n",[14,1193,1194,1214,1245,1267],{"__ignoreMap":405},[409,1195,1196,1198,1201,1203,1205,1208,1211],{"class":369,"line":411},[409,1197,752],{"class":566},[409,1199,1200],{"class":441},"(pd.crosstab(df[",[409,1202,718],{"class":418},[409,1204,721],{"class":441},[409,1206,1207],{"class":418},"\"Rating\"",[409,1209,1210],{"class":441},"]).index.tolist())         ",[409,1212,1213],{"class":821},"# ['Store', 'Web']\n",[409,1215,1216,1219,1221,1224,1226,1229,1232,1235,1238,1240,1243],{"class":369,"line":451},[409,1217,1218],{"class":441},"rating ",[409,1220,464],{"class":437},[409,1222,1223],{"class":441}," df[",[409,1225,1207],{"class":418},[409,1227,1228],{"class":441},"].astype(",[409,1230,1231],{"class":418},"\"Int64\"",[409,1233,1234],{"class":441},").astype(",[409,1236,1237],{"class":418},"\"string\"",[409,1239,1007],{"class":441},[409,1241,1242],{"class":418},"\"No rating\"",[409,1244,674],{"class":441},[409,1246,1247,1250,1252,1254,1256,1259,1261,1263,1265],{"class":369,"line":458},[409,1248,1249],{"class":441},"full ",[409,1251,464],{"class":437},[409,1253,715],{"class":441},[409,1255,718],{"class":418},[409,1257,1258],{"class":441},"], rating, ",[409,1260,730],{"class":665},[409,1262,464],{"class":437},[409,1264,735],{"class":566},[409,1266,674],{"class":441},[409,1268,1269,1271,1274,1277,1279,1281,1283,1286,1289],{"class":369,"line":470},[409,1270,752],{"class":566},[409,1272,1273],{"class":441},"(full.loc[",[409,1275,1276],{"class":418},"\"All\"",[409,1278,482],{"class":441},[409,1280,1276],{"class":418},[409,1282,727],{"class":441},[409,1284,1285],{"class":566},"len",[409,1287,1288],{"class":441},"(df))                                 ",[409,1290,1291],{"class":821},"# 8 8\n",[10,1293,1294,1295,1297],{},"Turning the missing values into an explicit label keeps every record in the table, and comparing the grand total with the row count is a one-line check that nothing went missing. Make that check part of every crosstab you publish. The same trap applies to the amount bands from Step 4: a negative amount or a refund above 500 falls outside the bins, becomes ",[14,1296,962],{}," and vanishes from the table without any warning.",[395,1299,1301],{"id":1300},"step-6-show-categories-with-no-records","Step 6 — Show categories with no records",[10,1303,1304,1305,1308],{},"A table that should always list Paid, Refunded and Cancelled loses the Cancelled column in a month with no cancellations. Declare the categories once and pass ",[14,1306,1307],{},"dropna=False",":",[400,1310,1312],{"className":428,"code":1311,"language":430,"meta":405,"style":405},"status = pd.Categorical(df[\"Status\"], categories=[\"Paid\", \"Refunded\", \"Cancelled\"])\nfixed = pd.crosstab(df[\"Channel\"], status, dropna=False)\nprint(fixed.columns.tolist())      # ['Paid', 'Refunded', 'Cancelled']\n",[14,1313,1314,1348,1371],{"__ignoreMap":405},[409,1315,1316,1319,1321,1324,1326,1328,1331,1333,1335,1337,1339,1341,1343,1346],{"class":369,"line":411},[409,1317,1318],{"class":441},"status ",[409,1320,464],{"class":437},[409,1322,1323],{"class":441}," pd.Categorical(df[",[409,1325,724],{"class":418},[409,1327,727],{"class":441},[409,1329,1330],{"class":665},"categories",[409,1332,464],{"class":437},[409,1334,1096],{"class":441},[409,1336,524],{"class":418},[409,1338,482],{"class":441},[409,1340,529],{"class":418},[409,1342,482],{"class":441},[409,1344,1345],{"class":418},"\"Cancelled\"",[409,1347,1137],{"class":441},[409,1349,1350,1353,1355,1357,1359,1362,1365,1367,1369],{"class":369,"line":451},[409,1351,1352],{"class":441},"fixed ",[409,1354,464],{"class":437},[409,1356,715],{"class":441},[409,1358,718],{"class":418},[409,1360,1361],{"class":441},"], status, ",[409,1363,1364],{"class":665},"dropna",[409,1366,464],{"class":437},[409,1368,671],{"class":566},[409,1370,674],{"class":441},[409,1372,1373,1375,1378],{"class":369,"line":458},[409,1374,752],{"class":566},[409,1376,1377],{"class":441},"(fixed.columns.tolist())      ",[409,1379,1380],{"class":821},"# ['Paid', 'Refunded', 'Cancelled']\n",[10,1382,1383],{},"Fixed categories keep the layout identical from month to month, so formulas and charts that point at the sheet do not break, and the column order follows the business order rather than the alphabet.",[25,1385,34,1390,34,1393,34,1396,34,1399,34,1406,34,1417,34,1422,34,1427,34,1432,34,1440,34,1444,34,1449,34,1453,34,1460,34,1464,34,1469,34,1473,34,1480,34,1485,34,1489,34,1493,34,1498,34,1501,34,1506,34,1509,34,1514,34,1517],{"viewBox":1386,"role":28,"ariaLabelledBy":1387,"xmlns":32,"style":858},"4 0 752 233",[1388,1389],"ct-traps-t","ct-traps-d",[36,1391,1392],{"id":1388},"Two ways a crosstab quietly changes shape",[40,1394,1395],{"id":1389},"A missing value in a grouping column removes the whole record from the table, so totals fall short of the row count. A category with no records this month disappears from the columns. Labelling missing values and declaring categories keeps both totals and layout stable.",[44,1397],{"x":139,"y":47,"width":867,"height":1398,"fill":50},"233",[44,1400],{"x":871,"y":872,"width":1401,"height":1402,"rx":1403,"fill":1404,"stroke":1405,"style":375},"351.0","201.0","14","#fdefd8","var(--gold,#b4740a)",[1407,1408,1410],"g",{"transform":1409},"translate(182.5,29.0) scale(1.083)",[1411,1412],"path",{"d":1413,"fill":1414,"stroke":1415,"style":1416},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","none","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[65,1418,1421],{"x":1419,"y":898,"style":1420},"195.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Missing grouping values",[369,1423],{"x1":1424,"y1":1425,"x2":1426,"y2":1425,"stroke":1405,"style":59},"36.0","91.0","355.0",[65,1428,1431],{"x":1429,"y":104,"style":1430},"60.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","record dropped silently",[1407,1433,1435],{"transform":1434},"translate(38.5,99.5) scale(0.625)",[1411,1436],{"d":1437,"fill":1414,"stroke":1438,"style":1439},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[65,1441,1443],{"x":1429,"y":1442,"style":1430},"137.0","total below row count",[1407,1445,1447],{"transform":1446},"translate(38.5,125.5) scale(0.625)",[1411,1448],{"d":1437,"fill":1414,"stroke":1438,"style":1439},[65,1450,1452],{"x":1429,"y":1451,"style":1430},"163.0","fix: fill with Unknown",[1407,1454,1456],{"transform":1455},"translate(38.5,151.5) scale(0.625)",[1411,1457],{"d":1458,"fill":1414,"stroke":1459,"style":1439},"M5 12.5l4.5 4.5L19 7","var(--teal,#0f9488)",[65,1461,1463],{"x":1429,"y":1462,"style":1430},"189.0","check: All equals len(df)",[1407,1465,1467],{"transform":1466},"translate(38.5,177.5) scale(0.625)",[1411,1468],{"d":1458,"fill":1414,"stroke":1459,"style":1439},[44,1470],{"x":1471,"y":872,"width":1401,"height":1402,"rx":1403,"fill":1472,"stroke":1459,"style":375},"389.0","#d9f4f1",[1407,1474,1476],{"transform":1475},"translate(551.5,29.0) scale(1.083)",[1411,1477],{"d":1478,"fill":1414,"stroke":1479,"style":1416},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","var(--teal-ink,#0b6157)",[65,1481,1484],{"x":1482,"y":898,"style":1483},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Categories with no rows",[369,1486],{"x1":1487,"y1":1425,"x2":1488,"y2":1425,"stroke":1459,"style":59},"405.0","724.0",[65,1490,1492],{"x":1491,"y":104,"style":1430},"429.0","column disappears",[1407,1494,1496],{"transform":1495},"translate(407.5,99.5) scale(0.625)",[1411,1497],{"d":1437,"fill":1414,"stroke":1438,"style":1439},[65,1499,1500],{"x":1491,"y":1442,"style":1430},"charts and formulas break",[1407,1502,1504],{"transform":1503},"translate(407.5,125.5) scale(0.625)",[1411,1505],{"d":1437,"fill":1414,"stroke":1438,"style":1439},[65,1507,1508],{"x":1491,"y":1451,"style":1430},"fix: pd.Categorical",[1407,1510,1512],{"transform":1511},"translate(407.5,151.5) scale(0.625)",[1411,1513],{"d":1458,"fill":1414,"stroke":1459,"style":1439},[65,1515,1516],{"x":1491,"y":1462,"style":1430},"pass dropna=False",[1407,1518,1520],{"transform":1519},"translate(407.5,177.5) scale(0.625)",[1411,1521],{"d":1458,"fill":1414,"stroke":1459,"style":1439},[395,1523,1525],{"id":1524},"step-7-two-variables-down-the-side","Step 7 — Two variables down the side",[10,1527,1528],{},"Pass a list of columns to either axis to nest them — channel and amount band down the side, for example — which answers \"is the refund pattern different for small web orders?\" in one table:",[400,1530,1532],{"className":428,"code":1531,"language":430,"meta":405,"style":405},"nested = pd.crosstab([df[\"Channel\"], bands.rename(\"Band\")], df[\"Status\"])\nprint(nested)\nflat = nested.reset_index()\nflat.columns.name = None\nprint(flat.columns.tolist())      # ['Channel', 'Band', 'Paid', 'Refunded']\nprint(len(flat))                  # 5 — only the combinations that occur\n",[14,1533,1534,1559,1566,1576,1586,1596],{"__ignoreMap":405},[409,1535,1536,1539,1541,1544,1546,1549,1552,1555,1557],{"class":369,"line":411},[409,1537,1538],{"class":441},"nested ",[409,1540,464],{"class":437},[409,1542,1543],{"class":441}," pd.crosstab([df[",[409,1545,718],{"class":418},[409,1547,1548],{"class":441},"], bands.rename(",[409,1550,1551],{"class":418},"\"Band\"",[409,1553,1554],{"class":441},")], df[",[409,1556,724],{"class":418},[409,1558,1137],{"class":441},[409,1560,1561,1563],{"class":369,"line":451},[409,1562,752],{"class":566},[409,1564,1565],{"class":441},"(nested)\n",[409,1567,1568,1571,1573],{"class":369,"line":458},[409,1569,1570],{"class":441},"flat ",[409,1572,464],{"class":437},[409,1574,1575],{"class":441}," nested.reset_index()\n",[409,1577,1578,1581,1583],{"class":369,"line":470},[409,1579,1580],{"class":441},"flat.columns.name ",[409,1582,464],{"class":437},[409,1584,1585],{"class":566}," None\n",[409,1587,1588,1590,1593],{"class":369,"line":516},[409,1589,752],{"class":566},[409,1591,1592],{"class":441},"(flat.columns.tolist())      ",[409,1594,1595],{"class":821},"# ['Channel', 'Band', 'Paid', 'Refunded']\n",[409,1597,1598,1600,1603,1605,1608],{"class":369,"line":558},[409,1599,752],{"class":566},[409,1601,1602],{"class":441},"(",[409,1604,1285],{"class":566},[409,1606,1607],{"class":441},"(flat))                  ",[409,1609,1610],{"class":821},"# 5 — only the combinations that occur\n",[10,1612,1613],{},"Nested crosstabs list only the row combinations that actually occur, so a channel with no large orders simply has no \"over 100\" row. That keeps the table short, but it also means two months can have different rows; if the layout must stay fixed, make both columns categorical, as in Step 6. Resetting the index turns the nested labels into ordinary columns, which is the easiest shape to filter in Excel and to join with other tables. Keep nesting to two levels in a report; beyond that, a long table with a filter is easier to read than a crosstab.",[395,1615,1617],{"id":1616},"step-8-write-counts-and-percentages-to-excel","Step 8 — Write counts and percentages to Excel",[10,1619,1620],{},"Write the counts and the row percentages side by side, and let Excel format the percentages instead of multiplying by 100 in pandas:",[400,1622,1624],{"className":428,"code":1623,"language":430,"meta":405,"style":405},"from openpyxl import load_workbook\n\nshares = pd.crosstab(df[\"Channel\"], df[\"Status\"], normalize=\"index\")\nwith pd.ExcelWriter(\"crosstab.xlsx\") as writer:\n    counts.to_excel(writer, sheet_name=\"Orders by status\", startrow=1)\n    shares.to_excel(writer, sheet_name=\"Orders by status\", startrow=1, startcol=5)\n\nwb = load_workbook(\"crosstab.xlsx\")\nws = wb[\"Orders by status\"]\nws[\"A1\"], ws[\"F1\"] = \"Order count\", \"Share of channel's orders\"\nfor row in ws.iter_rows(min_row=3, min_col=7, max_col=8):\n    for cell in row:\n        cell.number_format = \"0.0%\"\nwb.save(\"crosstab.xlsx\")\nprint(ws[\"G5\"].value, ws[\"G5\"].number_format)     # 0.75 0.0%\n",[14,1625,1626,1639,1643,1668,1687,1711,1741,1745,1759,1774,1801,1844,1858,1869,1879],{"__ignoreMap":405},[409,1627,1628,1631,1634,1636],{"class":369,"line":411},[409,1629,1630],{"class":437},"from",[409,1632,1633],{"class":441}," openpyxl ",[409,1635,438],{"class":437},[409,1637,1638],{"class":441}," load_workbook\n",[409,1640,1641],{"class":369,"line":451},[409,1642,455],{"emptyLinePlaceholder":454},[409,1644,1645,1648,1650,1652,1654,1656,1658,1660,1662,1664,1666],{"class":369,"line":458},[409,1646,1647],{"class":441},"shares ",[409,1649,464],{"class":437},[409,1651,715],{"class":441},[409,1653,718],{"class":418},[409,1655,721],{"class":441},[409,1657,724],{"class":418},[409,1659,727],{"class":441},[409,1661,772],{"class":665},[409,1663,464],{"class":437},[409,1665,802],{"class":418},[409,1667,674],{"class":441},[409,1669,1670,1673,1676,1679,1682,1684],{"class":369,"line":470},[409,1671,1672],{"class":437},"with",[409,1674,1675],{"class":441}," pd.ExcelWriter(",[409,1677,1678],{"class":418},"\"crosstab.xlsx\"",[409,1680,1681],{"class":441},") ",[409,1683,445],{"class":437},[409,1685,1686],{"class":441}," writer:\n",[409,1688,1689,1692,1695,1697,1700,1702,1705,1707,1709],{"class":369,"line":516},[409,1690,1691],{"class":441},"    counts.to_excel(writer, ",[409,1693,1694],{"class":665},"sheet_name",[409,1696,464],{"class":437},[409,1698,1699],{"class":418},"\"Orders by status\"",[409,1701,482],{"class":441},[409,1703,1704],{"class":665},"startrow",[409,1706,464],{"class":437},[409,1708,87],{"class":566},[409,1710,674],{"class":441},[409,1712,1713,1716,1718,1720,1722,1724,1726,1728,1730,1732,1735,1737,1739],{"class":369,"line":558},[409,1714,1715],{"class":441},"    shares.to_excel(writer, ",[409,1717,1694],{"class":665},[409,1719,464],{"class":437},[409,1721,1699],{"class":418},[409,1723,482],{"class":441},[409,1725,1704],{"class":665},[409,1727,464],{"class":437},[409,1729,87],{"class":566},[409,1731,482],{"class":441},[409,1733,1734],{"class":665},"startcol",[409,1736,464],{"class":437},[409,1738,155],{"class":566},[409,1740,674],{"class":441},[409,1742,1743],{"class":369,"line":607},[409,1744,455],{"emptyLinePlaceholder":454},[409,1746,1747,1750,1752,1755,1757],{"class":369,"line":648},[409,1748,1749],{"class":441},"wb ",[409,1751,464],{"class":437},[409,1753,1754],{"class":441}," load_workbook(",[409,1756,1678],{"class":418},[409,1758,674],{"class":441},[409,1760,1761,1764,1766,1769,1771],{"class":369,"line":654},[409,1762,1763],{"class":441},"ws ",[409,1765,464],{"class":437},[409,1767,1768],{"class":441}," wb[",[409,1770,1699],{"class":418},[409,1772,1773],{"class":441},"]\n",[409,1775,1776,1779,1782,1785,1788,1791,1793,1796,1798],{"class":369,"line":677},[409,1777,1778],{"class":441},"ws[",[409,1780,1781],{"class":418},"\"A1\"",[409,1783,1784],{"class":441},"], ws[",[409,1786,1787],{"class":418},"\"F1\"",[409,1789,1790],{"class":441},"] ",[409,1792,464],{"class":437},[409,1794,1795],{"class":418}," \"Order count\"",[409,1797,482],{"class":441},[409,1799,1800],{"class":418},"\"Share of channel's orders\"\n",[409,1802,1804,1807,1810,1813,1816,1819,1821,1823,1825,1828,1830,1832,1834,1837,1839,1841],{"class":369,"line":1803},11,[409,1805,1806],{"class":437},"for",[409,1808,1809],{"class":441}," row ",[409,1811,1812],{"class":437},"in",[409,1814,1815],{"class":441}," ws.iter_rows(",[409,1817,1818],{"class":665},"min_row",[409,1820,464],{"class":437},[409,1822,123],{"class":566},[409,1824,482],{"class":441},[409,1826,1827],{"class":665},"min_col",[409,1829,464],{"class":437},[409,1831,186],{"class":566},[409,1833,482],{"class":441},[409,1835,1836],{"class":665},"max_col",[409,1838,464],{"class":437},[409,1840,201],{"class":566},[409,1842,1843],{"class":441},"):\n",[409,1845,1847,1850,1853,1855],{"class":369,"line":1846},12,[409,1848,1849],{"class":437},"    for",[409,1851,1852],{"class":441}," cell ",[409,1854,1812],{"class":437},[409,1856,1857],{"class":441}," row:\n",[409,1859,1861,1864,1866],{"class":369,"line":1860},13,[409,1862,1863],{"class":441},"        cell.number_format ",[409,1865,464],{"class":437},[409,1867,1868],{"class":418}," \"0.0%\"\n",[409,1870,1872,1875,1877],{"class":369,"line":1871},14,[409,1873,1874],{"class":441},"wb.save(",[409,1876,1678],{"class":418},[409,1878,674],{"class":441},[409,1880,1882,1884,1887,1890,1893,1895,1898],{"class":369,"line":1881},15,[409,1883,752],{"class":566},[409,1885,1886],{"class":441},"(ws[",[409,1888,1889],{"class":418},"\"G5\"",[409,1891,1892],{"class":441},"].value, ws[",[409,1894,1889],{"class":418},[409,1896,1897],{"class":441},"].number_format)     ",[409,1899,1900],{"class":821},"# 0.75 0.0%\n",[10,1902,1903,1904,955,1906,1908],{},"Keeping the stored values as fractions means anyone who builds on the sheet can use them in formulas directly, while readers see 75.0%. The ",[14,1905,1704],{},[14,1907,1734],{}," offsets leave room for a title above each table.",[395,1910,1912],{"id":1911},"common-pitfalls","Common pitfalls",[1914,1915,1916,1933,1944,1954],"ul",{},[1917,1918,1919,1923,1924,955,1926,1928,1929,1932],"li",{},[1920,1921,1922],"strong",{},"Normalising a table that has margins."," With ",[14,1925,699],{},[14,1927,772],{},", the ",[14,1930,1931],{},"All"," row and column are proportions too; label them clearly or add totals after normalising.",[1917,1934,1935,1938,1939,1943],{},[1920,1936,1937],{},"Counting text that differs only in spelling."," \"web\", \"Web \" and \"WEB\" become three rows. Normalise the text first — see ",[19,1940,1942],{"href":1941},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstrip-whitespace-and-normalise-text-columns-with-pandas\u002F","Strip Whitespace and Normalise Text Columns with pandas",".",[1917,1945,1946,1949,1950,1953],{},[1920,1947,1948],{},"Comparing crosstabs from different months by position."," Row and column order follow the data unless the categories are fixed; align tables by label with ",[14,1951,1952],{},"reindex"," before subtracting one from another.",[1917,1955,1956,1959],{},[1920,1957,1958],{},"Averaging percentages across rows."," A mean of row shares ignores how many orders each row has. Recompute from counts.",[395,1961,1963],{"id":1962},"conclusion","Conclusion",[10,1965,1966,1968,1969,1971,1972,1974,1975,955,1977,1979,1980,1982],{},[14,1967,16],{}," builds frequency tables straight from Excel columns, fills impossible combinations with zero and adds totals with ",[14,1970,699],{},". Choose ",[14,1973,772],{}," by the question you are answering, fill gaps explicitly when using ",[14,1976,954],{},[14,1978,958],{},", band numbers with ",[14,1981,1068],{},", label missing values so no record is dropped, and declare categories so the layout stays stable. Write fractions and format them as percentages in Excel.",[395,1984,1986],{"id":1985},"frequently-asked-questions","Frequently asked questions",[10,1988,1989,1992],{},[1920,1990,1991],{},"What is the difference between crosstab and pivot_table?","\ncrosstab starts from separate columns and counts combinations by default, which suits frequency tables. pivot_table starts from a DataFrame and aggregates a value column. With values and aggfunc, crosstab can aggregate too, so the choice is mostly about which reads better.",[10,1994,1995,1998],{},[1920,1996,1997],{},"How do I show percentages instead of counts?","\nPass normalize=\"index\" for percentages within each row, normalize=\"columns\" for each column, or normalize=\"all\" for shares of the whole table. Multiply by 100 or apply an Excel percentage format when writing.",[10,2000,2001,2004],{},[1920,2002,2003],{},"Why did some rows disappear from my crosstab?","\ncrosstab drops any record with a missing value in one of the grouping columns. Fill missing values with a label such as \"Unknown\" before building the table so every record is counted.",[10,2006,2007,2010],{},[1920,2008,2009],{},"How do I include categories that have no records?","\nConvert the column to a pandas Categorical with the full list of categories and pass dropna=False. Categories with no matching rows then appear with a count of zero.",[395,2012,2014],{"id":2013},"related","Related",[1914,2016,2017,2023,2030,2037],{},[1917,2018,2019,2020,2022],{},"Up one level: ",[19,2021,22],{"href":21}," — pivots from Excel sheets.",[1917,2024,2025,2029],{},[19,2026,2028],{"href":2027},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table\u002F","Add Subtotals and Grand Totals to a pandas Pivot Table"," — group totals for multi-level tables.",[1917,2031,2032,2036],{},[19,2033,2035],{"href":2034},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fcountif-and-countifs-equivalent-in-pandas\u002F","COUNTIF and COUNTIFS Equivalent in pandas"," — single counts with conditions.",[1917,2038,2039,2043],{},[19,2040,2042],{"href":2041},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Funpivot-a-wide-excel-sheet-with-pandas-melt\u002F","Unpivot a Wide Excel Sheet with pandas melt"," — turning a crosstab-shaped sheet back into rows.",[2045,2046,2047],"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 .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 .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":405,"searchDepth":451,"depth":451,"links":2049},[2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062],{"id":397,"depth":451,"text":398},{"id":692,"depth":451,"text":693},{"id":765,"depth":451,"text":766},{"id":947,"depth":451,"text":948},{"id":1061,"depth":451,"text":1062},{"id":1182,"depth":451,"text":1183},{"id":1300,"depth":451,"text":1301},{"id":1524,"depth":451,"text":1525},{"id":1616,"depth":451,"text":1617},{"id":1911,"depth":451,"text":1912},{"id":1962,"depth":451,"text":1963},{"id":1985,"depth":451,"text":1986},{"id":2013,"depth":451,"text":2014},"2026-09-23","Count how often categories occur together in an Excel sheet with pandas.crosstab — frequency tables, row and column percentages, totals, sums instead of counts, banded numbers, categories with zero counts and missing values that silently drop rows — then write the result to Excel with percentage formats.","md",[2067,2069,2071,2073],{"q":1991,"a":2068},"crosstab starts from separate columns and counts combinations by default, which suits frequency tables. pivot_table starts from a DataFrame and aggregates a value column. With values and aggfunc, crosstab can aggregate too, so the choice is mostly about which reads better.",{"q":1997,"a":2070},"Pass normalize=\"index\" for percentages within each row, normalize=\"columns\" for each column, or normalize=\"all\" for shares of the whole table. Multiply by 100 or apply an Excel percentage format when writing.",{"q":2003,"a":2072},"crosstab drops any record with a missing value in one of the grouping columns. Fill missing values with a label such as \"Unknown\" before building the table so every record is counted.",{"q":2009,"a":2074},"Convert the column to a pandas Categorical with the full list of categories and pass dropna=False. Categories with no matching rows then appear with a count of zero.","build a crosstab from excel data with pandas",null,{"breadcrumb":2078},[2079,2082,2085],{"name":2080,"item":2081},"Home","\u002F",{"name":2083,"item":2084},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":2086,"item":21},"Creating Pivot Tables from Excel Data with Pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-crosstab-from-excel-data-with-pandas",{"title":2089,"description":2090},"Build a Crosstab from Excel Data with pandas (pd.crosstab)","Create frequency tables from Excel data with pandas.crosstab — counts, row and column percentages with normalize, margins, sums with values and aggfunc, pd.cut bands, zero-count categories, missing values and Excel export.","build-a-crosstab-from-excel-data-with-pandas","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-crosstab-from-excel-data-with-pandas\u002Findex",[2094,2095],"pivots-and-summaries","pandas","how-to","7Ejlt7yw8t86qRowEkPMYOwCj4aqqLOmhrSO4EGIDxg",[2099,2103],{"title":2100,"path":2101,"stem":2102,"children":-1},"Build a Cohort Retention Table from Excel Data","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-cohort-retention-table-from-excel-data","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-cohort-retention-table-from-excel-data\u002Findex",{"title":2104,"path":2105,"stem":2106,"children":-1},"Build a Funnel Conversion Table from Event Data","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-funnel-conversion-table-from-event-data","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-funnel-conversion-table-from-event-data\u002Findex",[2108,2110],{"path":2109,"title":2086},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data",{"path":2111,"title":2083},"\u002Fadvanced-data-transformation-and-cleaning",[],[2114,2117,2120,2123,2125,2128],{"path":2115,"title":2116,"kind":2096},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-customer-churn-report-from-subscription-data","Build a Customer Churn Report from Subscription Data",{"path":2118,"title":2119,"kind":2096},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-xlwings-basics\u002Frefresh-pivot-tables-and-queries-in-excel-with-python","Refresh Excel Pivot Tables and Queries with Python",{"path":2121,"title":2122,"kind":2096},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python","Add a Native Excel Pivot Table with Python",{"path":2124,"title":2028,"kind":2096},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table",{"path":2126,"title":2127,"kind":2096},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut","Bin Values into Ranges Like Excel FREQUENCY with pd.cut",{"path":2101,"title":2100,"kind":2096},1790972086218]