Repository: pandas-dev/pandas

Issue:
crosstabs doesn't work with margin and normalize together
#### Code Sample, a copy-pastable example if possible
```python
import pandas as pd
df = pd.DataFrame({"A": ["foo", "foo", "foo", "foo", "foo",
          "bar", "bar", "bar", "bar"],
                    "B": ["one", "one", "one", "two", "two",
                          "one", "one", "two", "two"],
                    "C": ["small", "large", "large", "small",
                          "small", "large", "small", "small",
                          "large"],
                    "D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
                    "E": [2, 4, 5, 5, 6, 6, 8, 9, 9]})

pd.crosstab([df.A,df.B],df.C,margins=True,margins_name='Sub-Total',normalize=0)
```
#### Problem description

pandas.Crosstab:
As per the Documentation, For parameter normalize If margins is True, will also normalize margin values. However when I give normalize as True and margins as True with margin_names as a string then it throws following exception:

KeyError: "['Sub-Total'] not found in axis"

where Sub-Total is the margins_name String.

#### Expected Output
Normalize value of the Margin rows and columns

#### Output of ``pd.show_versions()``


INSTALLED VERSIONS
------------------
commit: None
python: 3.6.3.final.0
python-bits: 64
OS: Windows
OS-release: 10
machine: AMD64
processor: Intel64 Family 6 Model 142 Stepping 9, GenuineIntel
byteorder: little
LC_ALL: None
LANG: None
LOCALE: None.None

pandas: 0.24.2
pytest: 3.2.1
pip: 18.0
setuptools: 39.1.0
Cython: 0.26.1
numpy: 1.15.4
scipy: 1.0.0
pyarrow: 0.8.0
xarray: 0.10.7
IPython: 6.1.0
sphinx: 1.6.3
patsy: 0.4.1
dateutil: 2.6.1
pytz: 2017.2
blosc: None
bottleneck: 1.2.1
tables: 3.4.2
numexpr: 2.6.2
feather: None
matplotlib: 2.1.0
openpyxl: 2.4.8
xlrd: 1.1.0
xlwt: 1.3.0
xlsxwriter: 1.0.2
lxml.etree: 4.1.0
bs4: 4.6.0
html5lib: 0.9999999
sqlalchemy: 1.1.13
pymysql: None
psycopg2: 2.7.5 (dt dec pq3 ext lo64)
jinja2: 2.9.6
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
gcsfs: None
None




Discussion:

Resolving patch:
diff --git a/pandas/core/reshape/pivot.py b/pandas/core/reshape/pivot.py
--- a/pandas/core/reshape/pivot.py
+++ b/pandas/core/reshape/pivot.py
@@ -611,13 +611,21 @@ def _normalize(table, normalize, margins, margins_name="All"):
         table = table.fillna(0)
 
     elif margins is True:
-
-        column_margin = table.loc[:, margins_name].drop(margins_name)
-        index_margin = table.loc[margins_name, :].drop(margins_name)
-        table = table.drop(margins_name, axis=1).drop(margins_name)
-        # to keep index and columns names
-        table_index_names = table.index.names
-        table_columns_names = table.columns.names
+        # keep index and column of pivoted table
+        table_index = table.index
+        table_columns = table.columns
+
+        # check if margin name is in (for MI cases) or equal to last
+        # index/column and save the column and index margin
+        if (margins_name not in table.iloc[-1, :].name) | (
+            margins_name != table.iloc[:, -1].name
+        ):
+            raise ValueError("{} not in pivoted DataFrame".format(margins_name))
+        column_margin = table.iloc[:-1, -1]
+        index_margin = table.iloc[-1, :-1]
+
+        # keep the core table
+        table = table.iloc[:-1, :-1]
 
         # Normalize core
         table = _normalize(table, normalize=normalize, margins=False)
@@ -627,11 +635,13 @@ def _normalize(table, normalize, margins, margins_name="All"):
             column_margin = column_margin / column_margin.sum()
             table = concat([table, column_margin], axis=1)
             table = table.fillna(0)
+            table.columns = table_columns
 
         elif normalize == "index":
             index_margin = index_margin / index_margin.sum()
             table = table.append(index_margin)
             table = table.fillna(0)
+            table.index = table_index
 
         elif normalize == "all" or normalize is True:
             column_margin = column_margin / column_margin.sum()
@@ -641,13 +651,12 @@ def _normalize(table, normalize, margins, margins_name="All"):
             table = table.append(index_margin)
 
             table = table.fillna(0)
+            table.index = table_index
+            table.columns = table_columns
 
         else:
             raise ValueError("Not a valid normalize argument")
 
-        table.index.names = table_index_names
-        table.columns.names = table_columns_names
-
     else:
         raise ValueError("Not a valid margins argument")
 


Test patch:
diff --git a/pandas/tests/reshape/test_pivot.py b/pandas/tests/reshape/test_pivot.py
--- a/pandas/tests/reshape/test_pivot.py
+++ b/pandas/tests/reshape/test_pivot.py
@@ -2447,3 +2447,84 @@ def test_crosstab_unsorted_order(self):
             [[1, 0, 0], [0, 1, 0], [0, 0, 1]], index=e_idx, columns=e_columns
         )
         tm.assert_frame_equal(result, expected)
+
+    def test_margin_normalize(self):
+        # GH 27500
+        df = pd.DataFrame(
+            {
+                "A": ["foo", "foo", "foo", "foo", "foo", "bar", "bar", "bar", "bar"],
+                "B": ["one", "one", "one", "two", "two", "one", "one", "two", "two"],
+                "C": [
+                    "small",
+                    "large",
+                    "large",
+                    "small",
+                    "small",
+                    "large",
+                    "small",
+                    "small",
+                    "large",
+                ],
+                "D": [1, 2, 2, 3, 3, 4, 5, 6, 7],
+                "E": [2, 4, 5, 5, 6, 6, 8, 9, 9],
+            }
+        )
+        # normalize on index
+        result = pd.crosstab(
+            [df.A, df.B], df.C, margins=True, margins_name="Sub-Total", normalize=0
+        )
+        expected = pd.DataFrame(
+            [[0.5, 0.5], [0.5, 0.5], [0.666667, 0.333333], [0, 1], [0.444444, 0.555556]]
+        )
+        expected.index = MultiIndex(
+            levels=[["Sub-Total", "bar", "foo"], ["", "one", "two"]],
+            codes=[[1, 1, 2, 2, 0], [1, 2, 1, 2, 0]],
+            names=["A", "B"],
+        )
+        expected.columns = Index(["large", "small"], dtype="object", name="C")
+        tm.assert_frame_equal(result, expected)
+
+        # normalize on columns
+        result = pd.crosstab(
+            [df.A, df.B], df.C, margins=True, margins_name="Sub-Total", normalize=1
+        )
+        expected = pd.DataFrame(
+            [
+                [0.25, 0.2, 0.222222],
+                [0.25, 0.2, 0.222222],
+                [0.5, 0.2, 0.333333],
+                [0, 0.4, 0.222222],
+            ]
+        )
+        expected.columns = Index(
+            ["large", "small", "Sub-Total"], dtype="object", name="C"
+        )
+        expected.index = MultiIndex(
+            levels=[["bar", "foo"], ["one", "two"]],
+            codes=[[0, 0, 1, 1], [0, 1, 0, 1]],
+            names=["A", "B"],
+        )
+        tm.assert_frame_equal(result, expected)
+
+        # normalize on both index and column
+        result = pd.crosstab(
+            [df.A, df.B], df.C, margins=True, margins_name="Sub-Total", normalize=True
+        )
+        expected = pd.DataFrame(
+            [
+                [0.111111, 0.111111, 0.222222],
+                [0.111111, 0.111111, 0.222222],
+                [0.222222, 0.111111, 0.333333],
+                [0.000000, 0.222222, 0.222222],
+                [0.444444, 0.555555, 1],
+            ]
+        )
+        expected.columns = Index(
+            ["large", "small", "Sub-Total"], dtype="object", name="C"
+        )
+        expected.index = MultiIndex(
+            levels=[["Sub-Total", "bar", "foo"], ["", "one", "two"]],
+            codes=[[1, 1, 2, 2, 0], [1, 2, 1, 2, 0]],
+            names=["A", "B"],
+        )
+        tm.assert_frame_equal(result, expected)