Repository: pandas-dev/pandas

Issue:
Unable to read Stata value_labels from .dta-file created by pandas (to_stata())
#### Code Sample, a copy-pastable example if possible

```python
import pandas as pd

d = {'A':['B','E','C','A','E']} 
df = pd.DataFrame(data=d)
df['A'] = df['A'].astype('category') # Setting as categorical, similar to value_label in Stata 

df.to_stata('test.dta') # Writing dataframe to Stata-file

dfs_fromstata = pd.read_stata('test.dta', iterator=True) # Creating StataReader-object

print(dfs_fromstata.value_labels()) # Printing value_labels
```
```python
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    print(dfs_fromstata.value_labels()) # Printing value_labels
  File "C:\Anaconda3\lib\site-packages\pandas\io\stata.py", line 1725, in value_labels
    self._read_value_labels()
  File "C:\Anaconda3\lib\site-packages\pandas\io\stata.py", line 1329, in _read_value_labels
    offset = self.nobs * self._dtype.itemsize
AttributeError: 'NoneType' object has no attribute 'itemsize'
```
#### Problem description
It seems as if read_stata() is not able to read value_labels properly from a .dta-file created by to_stata(). If the file is created by Stata itself, the value_labels are read correctly. Also, if the .dta-file created by to_stata() is opened and saved by Stata, the value_labels are read correctly.


#### Expected Output
{'A': {0: 'A', 1: 'B', 2: 'C', 3: 'E'}}

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 3.6.0.final.0
python-bits: 64
OS: Windows
OS-release: 10
machine: AMD64
processor: Intel64 Family 6 Model 78 Stepping 3, GenuineIntel
byteorder: little
LC_ALL: None
LANG: None
LOCALE: None.None

pandas: 0.20.2
pytest: 3.0.5
pip: 9.0.1
setuptools: 27.2.0
Cython: 0.25.2
numpy: 1.12.1
scipy: 0.19.1
xarray: None
IPython: 5.1.0
sphinx: 1.5.1
patsy: 0.4.1
dateutil: 2.6.0
pytz: 2016.10
blosc: None
bottleneck: 1.2.1
tables: 3.2.2
numexpr: 2.6.2
feather: None
matplotlib: 2.0.2
openpyxl: 2.4.1
xlrd: 1.0.0
xlwt: 1.2.0
xlsxwriter: 0.9.6
lxml: 3.7.2
bs4: 4.5.3
html5lib: None
sqlalchemy: 1.1.5
pymysql: None
psycopg2: None
jinja2: 2.9.4
s3fs: None
pandas_gbq: None
pandas_datareader: None
</details>



Discussion:
cc @bashtage 

I don't think this is supported.
It works fine.  You need to read once in order to initialize some values.  This appears to be an omission in the iterator implementation. Should probably raise with a usable error in the scenario.

```
import pandas as pd
d = {'A':['B','E','C','A','E']}
df = pd.DataFrame(data=d)
df['A'] = df['A'].astype('category') # Setting as categorical, similar to value_label in Stata

df.to_stata('test.dta') # Writing dataframe to Stata-file

dfs_fromstata = pd.read_stata('test.dta', iterator=True) # Creating StataReader-object
dfs_fromstata.read()
print(dfs_fromstata.value_labels())

```
pull-requests welcome!
Should pull this block

https://github.com/pandas-dev/pandas/blob/master/pandas/io/stata.py#L1480

out to a stand alone function and then call it once the header has been read. 

Resolving patch:
diff --git a/pandas/io/stata.py b/pandas/io/stata.py
--- a/pandas/io/stata.py
+++ b/pandas/io/stata.py
@@ -997,6 +997,7 @@ def __init__(self, path_or_buf, convert_dates=True,
             self.path_or_buf = BytesIO(contents)
 
         self._read_header()
+        self._setup_dtype()
 
     def __enter__(self):
         """ enter context manager """
@@ -1299,6 +1300,23 @@ def _read_old_header(self, first_char):
         # necessary data to continue parsing
         self.data_location = self.path_or_buf.tell()
 
+    def _setup_dtype(self):
+        """Map between numpy and state dtypes"""
+        if self._dtype is not None:
+            return self._dtype
+
+        dtype = []  # Convert struct data types to numpy data type
+        for i, typ in enumerate(self.typlist):
+            if typ in self.NUMPY_TYPE_MAP:
+                dtype.append(('s' + str(i), self.byteorder +
+                              self.NUMPY_TYPE_MAP[typ]))
+            else:
+                dtype.append(('s' + str(i), 'S' + str(typ)))
+        dtype = np.dtype(dtype)
+        self._dtype = dtype
+
+        return self._dtype
+
     def _calcsize(self, fmt):
         return (type(fmt) is int and fmt or
                 struct.calcsize(self.byteorder + fmt))
@@ -1472,22 +1490,10 @@ def read(self, nrows=None, convert_dates=None,
         if nrows is None:
             nrows = self.nobs
 
-        if (self.format_version >= 117) and (self._dtype is None):
+        if (self.format_version >= 117) and (not self._value_labels_read):
             self._can_read_value_labels = True
             self._read_strls()
 
-        # Setup the dtype.
-        if self._dtype is None:
-            dtype = []  # Convert struct data types to numpy data type
-            for i, typ in enumerate(self.typlist):
-                if typ in self.NUMPY_TYPE_MAP:
-                    dtype.append(('s' + str(i), self.byteorder +
-                                  self.NUMPY_TYPE_MAP[typ]))
-                else:
-                    dtype.append(('s' + str(i), 'S' + str(typ)))
-            dtype = np.dtype(dtype)
-            self._dtype = dtype
-
         # Read data
         dtype = self._dtype
         max_read_len = (self.nobs - self._lines_read) * dtype.itemsize
@@ -1958,7 +1964,6 @@ def _prepare_categoricals(self, data):
             return data
 
         get_base_missing_value = StataMissingValue.get_base_missing_value
-        index = data.index
         data_formatted = []
         for col, col_is_cat in zip(data, is_cat):
             if col_is_cat:
@@ -1981,8 +1986,7 @@ def _prepare_categoricals(self, data):
 
                 # Replace missing values with Stata missing value for type
                 values[values == -1] = get_base_missing_value(dtype)
-                data_formatted.append((col, values, index))
-
+                data_formatted.append((col, values))
             else:
                 data_formatted.append((col, data[col]))
         return DataFrame.from_items(data_formatted)


Test patch:
diff --git a/pandas/tests/io/test_stata.py b/pandas/tests/io/test_stata.py
--- a/pandas/tests/io/test_stata.py
+++ b/pandas/tests/io/test_stata.py
@@ -9,18 +9,18 @@
 from datetime import datetime
 from distutils.version import LooseVersion
 
-import pytest
 import numpy as np
 import pandas as pd
 import pandas.util.testing as tm
+import pytest
 from pandas import compat
+from pandas._libs.tslib import NaT
 from pandas.compat import iterkeys
+from pandas.core.dtypes.common import is_categorical_dtype
 from pandas.core.frame import DataFrame, Series
 from pandas.io.parsers import read_csv
 from pandas.io.stata import (read_stata, StataReader, InvalidColumnName,
                              PossiblePrecisionLoss, StataMissingValue)
-from pandas._libs.tslib import NaT
-from pandas.core.dtypes.common import is_categorical_dtype
 
 
 class TestStata(object):
@@ -1297,3 +1297,15 @@ def test_pickle_path_localpath(self):
         reader = lambda x: read_stata(x).set_index('index')
         result = tm.round_trip_localpath(df.to_stata, reader)
         tm.assert_frame_equal(df, result)
+
+    @pytest.mark.parametrize('write_index', [True, False])
+    def test_value_labels_iterator(self, write_index):
+        # GH 16923
+        d = {'A': ['B', 'E', 'C', 'A', 'E']}
+        df = pd.DataFrame(data=d)
+        df['A'] = df['A'].astype('category')
+        with tm.ensure_clean() as path:
+            df.to_stata(path, write_index=write_index)
+            dta_iter = pd.read_stata(path, iterator=True)
+            value_labels = dta_iter.value_labels()
+        assert value_labels == {'A': {0: 'A', 1: 'B', 2: 'C', 3: 'E'}}