diff --git a/CITATION.cff b/CITATION.cff
new file mode 100644
index 0000000..181d745
--- /dev/null
+++ b/CITATION.cff
@@ -0,0 +1,18 @@
+cff-version: 1.2.0
+message: "If you use this software, please cite it as below."
+authors:
+  - family-names: "Ramos-Carreño"
+    given-names: "Carlos"
+    orcid: "https://orcid.org/0000-0003-2566-7058"
+    affiliation: "Universidad Autónoma de Madrid"
+    email: vnmabus@gmail.com
+title: "rdata: Read R datasets from Python"
+date-released: 2022-03-24
+url: "https://github.com/vnmabus/rdata"
+license: MIT
+keywords:
+  - rdata
+  - Python
+  - R
+  - parser
+  - conversion
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
index 56e0267..4e06f8e 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,5 @@
 include MANIFEST.in
-include VERSION
+include rdata/VERSION
 include LICENSE
 include rdata/py.typed
 include *.txt
\ No newline at end of file
diff --git a/README.rst b/README.rst
index 98a4a44..784aceb 100644
--- a/README.rst
+++ b/README.rst
@@ -103,9 +103,9 @@ Pandas `Categorical` objects:
 >>> converted = rdata.conversion.convert(parsed, new_dict)
 >>> converted
 {'test_dataframe':   class  value
-    0     b'a'      1
-    1     b'b'      2
-    2     b'b'      3}
+    1     b'a'      1
+    2     b'b'      2
+    3     b'b'      3}
 
 
 .. |build-status| image:: https://github.com/vnmabus/rdata/actions/workflows/main.yml/badge.svg?branch=master
diff --git a/VERSION b/VERSION
deleted file mode 100644
index ea2303b..0000000
--- a/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-0.5
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
index ae780ab..270f1f0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-rdata (0.7-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Mon, 16 May 2022 12:55:43 -0000
+
 python-rdata (0.5-2) unstable; urgency=medium
 
   * Source package upload.
diff --git a/docs/simpleusage.rst b/docs/simpleusage.rst
index 4ecf266..968cc25 100644
--- a/docs/simpleusage.rst
+++ b/docs/simpleusage.rst
@@ -70,6 +70,6 @@ Pandas :class:`~pandas.Categorical` objects:
 >>> converted = rdata.conversion.convert(parsed, new_dict)
 >>> converted
 {'test_dataframe':   class  value
-    0     b'a'      1
-    1     b'b'      2
-    2     b'b'      3}
+    1     b'a'      1
+    2     b'b'      2
+    3     b'b'      3}
diff --git a/rdata/VERSION b/rdata/VERSION
new file mode 100644
index 0000000..0e2c939
--- /dev/null
+++ b/rdata/VERSION
@@ -0,0 +1 @@
+0.7
\ No newline at end of file
diff --git a/rdata/__init__.py b/rdata/__init__.py
index 90d9fa6..c83f931 100644
--- a/rdata/__init__.py
+++ b/rdata/__init__.py
@@ -1,3 +1,5 @@
+"""rdata: Read R datasets from Python."""
+import errno as _errno
 import os as _os
 import pathlib as _pathlib
 
@@ -13,3 +15,15 @@ TESTDATA_PATH = _get_test_data_path()
 Path of the test data.
 
 """
+
+try:
+    with open(
+        _pathlib.Path(_os.path.dirname(__file__)) / 'VERSION',
+        'r',
+    ) as version_file:
+        __version__ = version_file.read().strip()
+except IOError as e:
+    if e.errno != _errno.ENOENT:
+        raise
+
+    __version__ = "0.0"
diff --git a/rdata/conversion/__init__.py b/rdata/conversion/__init__.py
index 9d9e1cb..b0391e6 100644
--- a/rdata/conversion/__init__.py
+++ b/rdata/conversion/__init__.py
@@ -1,8 +1,17 @@
-from ._conversion import (RExpression, RLanguage,
-                          convert_list, convert_attrs, convert_vector,
-                          convert_char, convert_symbol, convert_array,
-                          Converter, SimpleConverter,
-                          dataframe_constructor,
-                          factor_constructor,
-                          ts_constructor,
-                          DEFAULT_CLASS_MAP, convert)
+from ._conversion import (
+    DEFAULT_CLASS_MAP,
+    Converter,
+    RExpression,
+    RLanguage,
+    SimpleConverter,
+    convert,
+    convert_array,
+    convert_attrs,
+    convert_char,
+    convert_list,
+    convert_symbol,
+    convert_vector,
+    dataframe_constructor,
+    factor_constructor,
+    ts_constructor,
+)
diff --git a/rdata/conversion/_conversion.py b/rdata/conversion/_conversion.py
index ed86853..8c9c217 100644
--- a/rdata/conversion/_conversion.py
+++ b/rdata/conversion/_conversion.py
@@ -6,7 +6,6 @@ from typing import (
     Any,
     Callable,
     ChainMap,
-    Hashable,
     List,
     Mapping,
     MutableMapping,
@@ -23,27 +22,26 @@ import xarray
 from .. import parser
 from ..parser import RObject
 
+ConversionFunction = Callable[[Union[parser.RData, parser.RObject]], Any]
+StrMap = Mapping[Union[str, bytes], Any]
+
 
 class RLanguage(NamedTuple):
-    """
-    R language construct.
-    """
+    """R language construct."""
+
     elements: List[Any]
 
 
 class RExpression(NamedTuple):
-    """
-    R expression.
-    """
+    """R expression."""
+
     elements: List[RLanguage]
 
 
 def convert_list(
     r_list: parser.RObject,
-    conversion_function: Callable[
-        [Union[parser.RData, parser.RObject]
-         ], Any]=lambda x: x
-) -> Union[Mapping[Union[str, bytes], Any], List[Any]]:
+    conversion_function: ConversionFunction,
+) -> Union[StrMap, List[Any]]:
     """
     Expand a tagged R pairlist to a Python dictionary.
 
@@ -68,8 +66,10 @@ def convert_list(
     """
     if r_list.info.type is parser.RObjectType.NILVALUE:
         return {}
-    elif r_list.info.type not in [parser.RObjectType.LIST,
-                                  parser.RObjectType.LANG]:
+    elif r_list.info.type not in {
+        parser.RObjectType.LIST,
+        parser.RObjectType.LANG,
+    }:
         raise TypeError("Must receive a LIST, LANG or NILVALUE object")
 
     if r_list.tag is None:
@@ -84,20 +84,18 @@ def convert_list(
             cdr = {}
 
         return {tag: conversion_function(r_list.value[0]), **cdr}
-    else:
-        if cdr is None:
-            cdr = []
 
-        return [conversion_function(r_list.value[0]), *cdr]
+    if cdr is None:
+        cdr = []
+
+    return [conversion_function(r_list.value[0]), *cdr]
 
 
 def convert_env(
     r_env: parser.RObject,
-    conversion_function: Callable[
-        [Union[parser.RData, parser.RObject]
-         ], Any]=lambda x: x
+    conversion_function: ConversionFunction,
 ) -> ChainMap[Union[str, bytes], Any]:
-
+    """Convert environment objects."""
     if r_env.info.type is not parser.RObjectType.ENV:
         raise TypeError("Must receive a ENV object")
 
@@ -115,10 +113,8 @@ def convert_env(
 
 def convert_attrs(
     r_obj: parser.RObject,
-        conversion_function: Callable[
-            [Union[parser.RData, parser.RObject]
-             ], Any]=lambda x: x
-) -> Mapping[Union[str, bytes], Any]:
+        conversion_function: ConversionFunction,
+) -> StrMap:
     """
     Return the attributes of an object as a Python dictionary.
 
@@ -143,7 +139,7 @@ def convert_attrs(
     """
     if r_obj.attributes:
         attrs = cast(
-            Mapping[Union[str, bytes], Any],
+            StrMap,
             conversion_function(r_obj.attributes),
         )
     else:
@@ -153,10 +149,9 @@ def convert_attrs(
 
 def convert_vector(
     r_vec: parser.RObject,
-    conversion_function: Callable[
-        [Union[parser.RData, parser.RObject]], Any]=lambda x: x,
-    attrs: Optional[Mapping[Union[str, bytes], Any]] = None,
-) -> Union[List[Any], Mapping[Union[str, bytes], Any]]:
+    conversion_function: ConversionFunction,
+    attrs: Optional[StrMap] = None,
+) -> Union[List[Any], StrMap]:
     """
     Convert a R vector to a Python list or dictionary.
 
@@ -186,11 +181,13 @@ def convert_vector(
     if attrs is None:
         attrs = {}
 
-    if r_vec.info.type not in [parser.RObjectType.VEC,
-                               parser.RObjectType.EXPR]:
+    if r_vec.info.type not in {
+        parser.RObjectType.VEC,
+        parser.RObjectType.EXPR,
+    }:
         raise TypeError("Must receive a VEC or EXPR object")
 
-    value: Union[List[Any], Mapping[Union[str, bytes], Any]] = [
+    value: Union[List[Any], StrMap] = [
         conversion_function(o) for o in r_vec.value
     ]
 
@@ -203,9 +200,7 @@ def convert_vector(
 
 
 def safe_decode(byte_str: bytes, encoding: str) -> Union[str, bytes]:
-    """
-    Decode a (possibly malformed) string.
-    """
+    """Decode a (possibly malformed) string."""
     try:
         return byte_str.decode(encoding)
     except UnicodeDecodeError as e:
@@ -250,29 +245,37 @@ def convert_char(
 
     assert isinstance(r_char.value, bytes)
 
+    encoding = None
+
     if not force_default_encoding:
         if r_char.info.gp & parser.CharFlags.UTF8:
-            return safe_decode(r_char.value, "utf_8")
+            encoding = "utf_8"
         elif r_char.info.gp & parser.CharFlags.LATIN1:
-            return safe_decode(r_char.value, "latin_1")
+            encoding = "latin_1"
         elif r_char.info.gp & parser.CharFlags.ASCII:
-            return safe_decode(r_char.value, "ascii")
+            encoding = "ascii"
         elif r_char.info.gp & parser.CharFlags.BYTES:
-            return r_char.value
+            encoding = "bytes"
 
-    if default_encoding:
-        return safe_decode(r_char.value, default_encoding)
-    else:
-        # Assume ASCII if no encoding is marked
-        warnings.warn(f"Unknown encoding. Assumed ASCII.")
-        return safe_decode(r_char.value, "ascii")
+    if encoding is None:
+        if default_encoding:
+            encoding = default_encoding
+        else:
+            # Assume ASCII if no encoding is marked
+            warnings.warn("Unknown encoding. Assumed ASCII.")
+            encoding = "ascii"
+
+    return (
+        r_char.value
+        if encoding == "bytes"
+        else safe_decode(r_char.value, encoding)
+    )
 
 
-def convert_symbol(r_symbol: parser.RObject,
-                   conversion_function: Callable[
-                       [Union[parser.RData, parser.RObject]],
-                       Any]=lambda x: x
-                   ) -> Union[str, bytes]:
+def convert_symbol(
+    r_symbol: parser.RObject,
+    conversion_function: ConversionFunction,
+) -> Union[str, bytes]:
     """
     Decode a R symbol to a Python string or bytes.
 
@@ -298,16 +301,14 @@ def convert_symbol(r_symbol: parser.RObject,
         symbol = conversion_function(r_symbol.value)
         assert isinstance(symbol, (str, bytes))
         return symbol
-    else:
-        raise TypeError("Must receive a SYM object")
+
+    raise TypeError("Must receive a SYM object")
 
 
 def convert_array(
     r_array: RObject,
-    conversion_function: Callable[
-        [Union[parser.RData, parser.RObject]
-         ], Any]=lambda x: x,
-    attrs: Optional[Mapping[Union[str, bytes], Any]] = None,
+    conversion_function: ConversionFunction,
+    attrs: Optional[StrMap] = None,
 ) -> Union[np.ndarray, xarray.DataArray]:
     """
     Convert a R array to a Numpy ndarray or a Xarray DataArray.
@@ -336,10 +337,12 @@ def convert_array(
     if attrs is None:
         attrs = {}
 
-    if r_array.info.type not in {parser.RObjectType.LGL,
-                                 parser.RObjectType.INT,
-                                 parser.RObjectType.REAL,
-                                 parser.RObjectType.CPLX}:
+    if r_array.info.type not in {
+        parser.RObjectType.LGL,
+        parser.RObjectType.INT,
+        parser.RObjectType.REAL,
+        parser.RObjectType.CPLX,
+    }:
         raise TypeError("Must receive an array object")
 
     value = r_array.value
@@ -351,10 +354,16 @@ def convert_array(
 
     dimnames = attrs.get('dimnames')
     if dimnames:
-        dimension_names = ["dim_" + str(i) for i, _ in enumerate(dimnames)]
-        coords: Mapping[Hashable, Any] = {
-            dimension_names[i]: d
-            for i, d in enumerate(dimnames) if d is not None}
+        if isinstance(dimnames, Mapping):
+            dimension_names = list(dimnames.keys())
+            coords = dimnames
+        else:
+            dimension_names = [f"dim_{i}" for i, _ in enumerate(dimnames)]
+            coords = {
+                dimension_names[i]: d
+                for i, d in enumerate(dimnames)
+                if d is not None
+            }
 
         value = xarray.DataArray(value, dims=dimension_names, coords=coords)
 
@@ -363,14 +372,25 @@ def convert_array(
 
 def dataframe_constructor(
     obj: Any,
-    attrs: Mapping[Union[str, bytes], Any],
+    attrs: StrMap,
 ) -> pandas.DataFrame:
-    return pandas.DataFrame(obj, columns=obj)
+
+    row_names = attrs["row.names"]
+
+    # Default row names are stored as [INT_MIN, -len]
+    INT_MIN = -2**31  # noqa: WPS432
+    index = (
+        pandas.RangeIndex(1, abs(row_names[1]) + 1)
+        if len(row_names) == 2 and row_names[0] == INT_MIN
+        else tuple(row_names)
+    )
+
+    return pandas.DataFrame(obj, columns=obj, index=index)
 
 
 def _factor_constructor_internal(
     obj: Any,
-    attrs: Mapping[Union[str, bytes], Any],
+    attrs: StrMap,
     ordered: bool,
 ) -> pandas.Categorical:
     values = [attrs['levels'][i - 1] if i >= 0 else None for i in obj]
@@ -380,23 +400,25 @@ def _factor_constructor_internal(
 
 def factor_constructor(
     obj: Any,
-    attrs: Mapping[Union[str, bytes], Any],
+    attrs: StrMap,
 ) -> pandas.Categorical:
+    """Construct a factor objects."""
     return _factor_constructor_internal(obj, attrs, ordered=False)
 
 
 def ordered_constructor(
     obj: Any,
-    attrs: Mapping[Union[str, bytes], Any],
+    attrs: StrMap,
 ) -> pandas.Categorical:
+    """Contruct an ordered factor."""
     return _factor_constructor_internal(obj, attrs, ordered=True)
 
 
 def ts_constructor(
     obj: Any,
-    attrs: Mapping[Union[str, bytes], Any],
+    attrs: StrMap,
 ) -> pandas.Series:
-
+    """Construct a time series object."""
     start, end, frequency = attrs['tsp']
 
     frequency = int(frequency)
@@ -404,8 +426,11 @@ def ts_constructor(
     real_start = Fraction(int(round(start * frequency)), frequency)
     real_end = Fraction(int(round(end * frequency)), frequency)
 
-    index = np.arange(real_start, real_end + Fraction(1, frequency),
-                      Fraction(1, frequency))
+    index = np.arange(
+        real_start,
+        real_end + Fraction(1, frequency),
+        Fraction(1, frequency),
+    )
 
     if frequency == 1:
         index = index.astype(int)
@@ -414,6 +439,10 @@ def ts_constructor(
 
 
 Constructor = Callable[[Any, Mapping], Any]
+ConstructorDict = Mapping[
+    Union[str, bytes],
+    Constructor,
+]
 
 default_class_map_dict: Mapping[Union[str, bytes], Constructor] = {
     "data.frame": dataframe_constructor,
@@ -440,15 +469,11 @@ It has support for converting several commonly used R classes:
 
 
 class Converter(abc.ABC):
-    """
-    Interface of a class converting R objects in Python objects.
-    """
+    """Interface of a class converting R objects in Python objects."""
 
     @abc.abstractmethod
     def convert(self, data: Union[parser.RData, parser.RObject]) -> Any:
-        """
-        Convert a R object to a Python one.
-        """
+        """Convert a R object to a Python one."""
         pass
 
 
@@ -480,13 +505,10 @@ class SimpleConverter(Converter):
 
     def __init__(
         self,
-        constructor_dict: Mapping[
-            Union[str, bytes],
-            Constructor,
-        ] = DEFAULT_CLASS_MAP,
+        constructor_dict: ConstructorDict = DEFAULT_CLASS_MAP,
         default_encoding: Optional[str] = None,
         force_default_encoding: bool = False,
-        global_environment: Optional[Mapping[Union[str, bytes], Any]] = None,
+        global_environment: Optional[StrMap] = None,
     ) -> None:
 
         self.constructor_dict = constructor_dict
@@ -494,9 +516,9 @@ class SimpleConverter(Converter):
         self.force_default_encoding = force_default_encoding
         self.global_environment = ChainMap(
             {} if global_environment is None
-            else global_environment
+            else global_environment,
         )
-        self.empty_environment: Mapping[Union[str, bytes], Any] = ChainMap({})
+        self.empty_environment: StrMap = ChainMap({})
 
         self._reset()
 
@@ -504,15 +526,15 @@ class SimpleConverter(Converter):
         self.references: MutableMapping[int, Any] = {}
         self.default_encoding_used = self.default_encoding
 
-    def convert(self, data: Union[parser.RData, parser.RObject]) -> Any:
+    def convert(  # noqa: D102
+        self,
+        data: Union[parser.RData, parser.RObject],
+    ) -> Any:
         self._reset()
         return self._convert_next(data)
 
     def _convert_next(self, data: Union[parser.RData, parser.RObject]) -> Any:
-        """
-        Convert a R object to a Python one.
-        """
-
+        """Convert a R object to a Python one."""
         obj: RObject
         if isinstance(data, parser.RData):
             obj = data.object
@@ -563,10 +585,12 @@ class SimpleConverter(Converter):
                 force_default_encoding=self.force_default_encoding,
             )
 
-        elif obj.info.type in {parser.RObjectType.LGL,
-                               parser.RObjectType.INT,
-                               parser.RObjectType.REAL,
-                               parser.RObjectType.CPLX}:
+        elif obj.info.type in {
+            parser.RObjectType.LGL,
+            parser.RObjectType.INT,
+            parser.RObjectType.REAL,
+            parser.RObjectType.CPLX,
+        }:
 
             # Return the internal array
             value = convert_array(obj, self._convert_next, attrs=attrs)
@@ -583,7 +607,10 @@ class SimpleConverter(Converter):
 
         elif obj.info.type == parser.RObjectType.EXPR:
             rexpression_list = convert_vector(
-                obj, self._convert_next, attrs=attrs)
+                obj,
+                self._convert_next,
+                attrs=attrs,
+            )
             assert isinstance(rexpression_list, list)
 
             # Convert the internal objects returning a special object
@@ -602,7 +629,6 @@ class SimpleConverter(Converter):
 
             # Return the referenced value
             value = self.references.get(id(obj.referenced_object))
-            # value = self.references[id(obj.referenced_object)]
             if value is None:
                 reference_id = id(obj.referenced_object)
                 assert obj.referenced_object is not None
@@ -627,20 +653,26 @@ class SimpleConverter(Converter):
                     new_value = NotImplemented
 
                 if new_value is NotImplemented:
-                    missing_msg = (f"Missing constructor for R class "
-                                   f"\"{c}\". ")
+                    missing_msg = (
+                        f"Missing constructor for R class \"{c}\". "
+                    )
 
                     if len(classname) > (i + 1):
-                        solution_msg = (f"The constructor for class "
-                                        f"\"{classname[i+1]}\" will be "
-                                        f"used instead."
-                                        )
+                        solution_msg = (
+                            f"The constructor for class "
+                            f"\"{classname[i+1]}\" will be "
+                            f"used instead."
+                        )
                     else:
-                        solution_msg = ("The underlying R object is "
-                                        "returned instead.")
-
-                    warnings.warn(missing_msg + solution_msg,
-                                  stacklevel=1)
+                        solution_msg = (
+                            "The underlying R object is "
+                            "returned instead."
+                        )
+
+                    warnings.warn(
+                        missing_msg + solution_msg,
+                        stacklevel=1,
+                    )
                 else:
                     value = new_value
                     break
@@ -656,10 +688,9 @@ def convert(
     **kwargs: Any,
 ) -> Any:
     """
-    Uses the default converter (:func:`SimpleConverter`) to convert the data.
+    Use the default converter (:func:`SimpleConverter`) to convert the data.
 
     Examples:
-
         Parse one of the included examples, containing a vector
 
         >>> import rdata
@@ -679,9 +710,9 @@ def convert(
         >>> converted = rdata.conversion.convert(parsed)
         >>> converted
         {'test_dataframe':   class  value
-        0     a      1
-        1     b      2
-        2     b      3}
+        1     a      1
+        2     b      2
+        3     b      3}
 
     """
     return SimpleConverter(*args, **kwargs).convert(data)
diff --git a/rdata/parser/__init__.py b/rdata/parser/__init__.py
index 720979e..1810e4b 100644
--- a/rdata/parser/__init__.py
+++ b/rdata/parser/__init__.py
@@ -1,3 +1,5 @@
+"""Utilities for parsing a rdata file."""
+
 from ._parser import (
     DEFAULT_ALTREP_MAP,
     CharFlags,
diff --git a/rdata/parser/_parser.py b/rdata/parser/_parser.py
index df2009c..9ba9054 100644
--- a/rdata/parser/_parser.py
+++ b/rdata/parser/_parser.py
@@ -28,9 +28,8 @@ import numpy as np
 
 
 class FileTypes(enum.Enum):
-    """
-    Type of file containing a R file.
-    """
+    """Type of file containing a R file."""
+
     bzip2 = "bz2"
     gzip = "gzip"
     xz = "xz"
@@ -43,15 +42,12 @@ magic_dict = {
     FileTypes.gzip: b"\x1f\x8b",
     FileTypes.xz: b"\xFD7zXZ\x00",
     FileTypes.rdata_binary_v2: b"RDX2\n",
-    FileTypes.rdata_binary_v3: b"RDX3\n"
+    FileTypes.rdata_binary_v3: b"RDX3\n",
 }
 
 
 def file_type(data: memoryview) -> Optional[FileTypes]:
-    """
-    Returns the type of the file.
-    """
-
+    """Return the type of the file."""
     for filetype, magic in magic_dict.items():
         if data[:len(magic)] == magic:
             return filetype
@@ -59,9 +55,8 @@ def file_type(data: memoryview) -> Optional[FileTypes]:
 
 
 class RdataFormats(enum.Enum):
-    """
-    Format of a R file.
-    """
+    """Format of a R file."""
+
     XDR = "XDR"
     ASCII = "ASCII"
     binary = "binary"
@@ -75,10 +70,7 @@ format_dict = {
 
 
 def rdata_format(data: memoryview) -> Optional[RdataFormats]:
-    """
-    Returns the format of the data.
-    """
-
+    """Return the format of the data."""
     for format_type, magic in format_dict.items():
         if data[:len(magic)] == magic:
             return format_type
@@ -86,9 +78,8 @@ def rdata_format(data: memoryview) -> Optional[RdataFormats]:
 
 
 class RObjectType(enum.Enum):
-    """
-    Type of a R object.
-    """
+    """Type of a R object."""
+
     NIL = 0  # NULL
     SYM = 1  # symbols
     LIST = 2  # pairlists
@@ -121,6 +112,8 @@ class RObjectType(enum.Enum):
 
 
 class CharFlags(enum.IntFlag):
+    """Flags for R objects of type char."""
+
     HAS_HASH = 1
     BYTES = 1 << 1
     LATIN1 = 1 << 2
@@ -131,10 +124,9 @@ class CharFlags(enum.IntFlag):
 
 @dataclass
 class RVersions():
-    """
-    R versions.
-    """
-    format: int
+    """R versions."""
+
+    format: int  # noqa: E701
     serialized: int
     minimum: int
 
@@ -145,15 +137,16 @@ class RExtraInfo():
     Extra information.
 
     Contains the default encoding (only in version 3).
+
     """
+
     encoding: Optional[str] = None
 
 
 @dataclass
 class RObjectInfo():
-    """
-    Internal attributes of a R object.
-    """
+    """Internal attributes of a R object."""
+
     type: RObjectType
     object: bool
     attributes: bool
@@ -164,9 +157,8 @@ class RObjectInfo():
 
 @dataclass
 class RObject():
-    """
-    Representation of a R object.
-    """
+    """Representation of a R object."""
+
     info: RObjectInfo
     value: Any
     attributes: Optional[RObject]
@@ -176,54 +168,73 @@ class RObject():
     def _str_internal(
         self,
         indent: int = 0,
-        used_references: Optional[Set[int]] = None
+        used_references: Optional[Set[int]] = None,
     ) -> str:
 
         if used_references is None:
             used_references = set()
 
+        small_indent = indent + 2
+        big_indent = indent + 4
+
+        indent_spaces = ' ' * indent
+        small_indent_spaces = ' ' * small_indent
+        big_indent_spaces = ' ' * big_indent
+
         string = ""
 
-        string += f"{' ' * indent}{self.info.type}\n"
+        string += f"{indent_spaces}{self.info.type}\n"
 
         if self.tag:
-            tag_string = self.tag._str_internal(indent + 4,
-                                                used_references.copy())
-            string += f"{' ' * (indent + 2)}tag:\n{tag_string}\n"
+            tag_string = self.tag._str_internal(
+                big_indent,
+                used_references.copy(),
+            )
+            string += f"{small_indent_spaces}tag:\n{tag_string}\n"
 
         if self.info.reference:
             assert self.referenced_object
-            reference_string = (f"{' ' * (indent + 4)}..."
-                                if self.info.reference in used_references
-                                else self.referenced_object._str_internal(
-                                    indent + 4, used_references.copy()))
-            string += (f"{' ' * (indent + 2)}reference: "
-                       f"{self.info.reference}\n{reference_string}\n")
+            reference_string = (
+                f"{big_indent_spaces}..."
+                if self.info.reference in used_references
+                else self.referenced_object._str_internal(
+                    indent + 4, used_references.copy())
+            )
+            string += (
+                f"{small_indent_spaces}reference: "
+                f"{self.info.reference}\n{reference_string}\n"
+            )
 
-        string += f"{' ' * (indent + 2)}value:\n"
+        string += f"{small_indent_spaces}value:\n"
 
         if isinstance(self.value, RObject):
-            string += self.value._str_internal(indent + 4,
-                                               used_references.copy())
-        elif isinstance(self.value, tuple) or isinstance(self.value, list):
+            string += self.value._str_internal(
+                big_indent,
+                used_references.copy(),
+            )
+        elif isinstance(self.value, (tuple, list)):
             for elem in self.value:
-                string += elem._str_internal(indent + 4,
-                                             used_references.copy())
+                string += elem._str_internal(
+                    big_indent,
+                    used_references.copy(),
+                )
         elif isinstance(self.value, np.ndarray):
-            string += " " * (indent + 4)
+            string += big_indent_spaces
             if len(self.value) > 4:
-                string += (f"[{self.value[0]}, {self.value[1]} ... "
-                           f"{self.value[-2]}, {self.value[-1]}]\n")
+                string += (
+                    f"[{self.value[0]}, {self.value[1]} ... "
+                    f"{self.value[-2]}, {self.value[-1]}]\n"
+                )
             else:
                 string += f"{self.value}\n"
         else:
-            string += f"{' ' * (indent + 4)}{self.value}\n"
+            string += f"{big_indent_spaces}{self.value}\n"
 
-        if(self.attributes):
+        if self.attributes:
             attr_string = self.attributes._str_internal(
-                indent + 4,
+                big_indent,
                 used_references.copy())
-            string += f"{' ' * (indent + 2)}attributes:\n{attr_string}\n"
+            string += f"{small_indent_spaces}attributes:\n{attr_string}\n"
 
         return string
 
@@ -233,9 +244,8 @@ class RObject():
 
 @dataclass
 class RData():
-    """
-    Data contained in a R file.
-    """
+    """Data contained in a R file."""
+
     versions: RVersions
     extra: RExtraInfo
     object: RObject
@@ -243,9 +253,8 @@ class RData():
 
 @dataclass
 class EnvironmentValue():
-    """
-    Value of an environment.
-    """
+    """Value of an environment."""
+
     locked: bool
     enclosure: RObject
     frame: RObject
@@ -260,11 +269,12 @@ AltRepConstructorMap = Mapping[bytes, AltRepConstructor]
 
 
 def format_float_with_scipen(number: float, scipen: int) -> bytes:
+    """Format a floating point value as in R."""
     fixed = np.format_float_positional(number, trim="-")
     scientific = np.format_float_scientific(number, trim="-")
 
-    assert(isinstance(fixed, str))
-    assert(isinstance(scientific, str))
+    assert isinstance(fixed, str)
+    assert isinstance(scientific, str)
 
     return (
         scientific if len(fixed) - len(scientific) > scipen
@@ -275,7 +285,7 @@ def format_float_with_scipen(number: float, scipen: int) -> bytes:
 def deferred_string_constructor(
     state: RObject,
 ) -> Tuple[RObjectInfo, Any]:
-
+    """Expand a deferred string ALTREP."""
     new_info = RObjectInfo(
         type=RObjectType.STR,
         object=False,
@@ -312,9 +322,9 @@ def deferred_string_constructor(
 def compact_seq_constructor(
     state: RObject,
     *,
-    is_int: bool = False
+    is_int: bool = False,
 ) -> Tuple[RObjectInfo, Any]:
-
+    """Expand a compact_seq ALTREP."""
     new_info = RObjectInfo(
         type=RObjectType.INT if is_int else RObjectType.REAL,
         object=False,
@@ -341,19 +351,21 @@ def compact_seq_constructor(
 def compact_intseq_constructor(
     state: RObject,
 ) -> Tuple[RObjectInfo, Any]:
+    """Expand a compact_intseq ALTREP."""
     return compact_seq_constructor(state, is_int=True)
 
 
 def compact_realseq_constructor(
     state: RObject,
 ) -> Tuple[RObjectInfo, Any]:
+    """Expand a compact_realseq ALTREP."""
     return compact_seq_constructor(state, is_int=False)
 
 
 def wrap_constructor(
     state: RObject,
 ) -> Tuple[RObjectInfo, Any]:
-
+    """Expand any wrap_* ALTREP."""
     new_info = RObjectInfo(
         type=state.value[0].info.type,
         object=False,
@@ -384,9 +396,7 @@ DEFAULT_ALTREP_MAP = MappingProxyType(default_altrep_map_dict)
 
 
 class Parser(abc.ABC):
-    """
-    Parser interface for a R file.
-    """
+    """Parser interface for a R file."""
 
     def __init__(
         self,
@@ -398,43 +408,30 @@ class Parser(abc.ABC):
         self.altrep_constructor_dict = altrep_constructor_dict
 
     def parse_bool(self) -> bool:
-        """
-        Parse a boolean.
-        """
+        """Parse a boolean."""
         return bool(self.parse_int())
 
     @abc.abstractmethod
     def parse_int(self) -> int:
-        """
-        Parse an integer.
-        """
+        """Parse an integer."""
         pass
 
     @abc.abstractmethod
     def parse_double(self) -> float:
-        """
-        Parse a double.
-        """
+        """Parse a double."""
         pass
 
     def parse_complex(self) -> complex:
-        """
-        Parse a complex number.
-        """
+        """Parse a complex number."""
         return complex(self.parse_double(), self.parse_double())
 
     @abc.abstractmethod
     def parse_string(self, length: int) -> bytes:
-        """
-        Parse a string.
-        """
+        """Parse a string."""
         pass
 
     def parse_all(self) -> RData:
-        """
-        Parse all the file.
-        """
-
+        """Parse all the file."""
         versions = self.parse_versions()
         extra_info = self.parse_extra_info(versions)
         obj = self.parse_R_object()
@@ -442,15 +439,12 @@ class Parser(abc.ABC):
         return RData(versions, extra_info, obj)
 
     def parse_versions(self) -> RVersions:
-        """
-        Parse the versions header.
-        """
-
+        """Parse the versions header."""
         format_version = self.parse_int()
         r_version = self.parse_int()
         minimum_r_version = self.parse_int()
 
-        if format_version not in [2, 3]:
+        if format_version not in {2, 3}:
             raise NotImplementedError(
                 f"Format version {format_version} unsupported",
             )
@@ -459,18 +453,18 @@ class Parser(abc.ABC):
 
     def parse_extra_info(self, versions: RVersions) -> RExtraInfo:
         """
-        Parse the versions header.
-        """
+        Parse the extra info.
+
+        Parses de encoding in version 3 format.
 
+        """
         encoding = None
 
         if versions.format >= 3:
             encoding_len = self.parse_int()
             encoding = self.parse_string(encoding_len).decode("ASCII")
 
-        extra_info = RExtraInfo(encoding)
-
-        return extra_info
+        return RExtraInfo(encoding)
 
     def expand_altrep_to_object(
         self,
@@ -478,7 +472,6 @@ class Parser(abc.ABC):
         state: RObject,
     ) -> Tuple[RObjectInfo, Any]:
         """Expand alternative representation to normal object."""
-
         assert info.info.type == RObjectType.LIST
 
         class_sym = info.value[0]
@@ -496,12 +489,9 @@ class Parser(abc.ABC):
 
     def parse_R_object(
         self,
-        reference_list: Optional[List[RObject]] = None
+        reference_list: Optional[List[RObject]] = None,
     ) -> RObject:
-        """
-        Parse a R object.
-        """
-
+        """Parse a R object."""
         if reference_list is None:
             # Index is 1-based, so we insert a dummy object
             reference_list = []
@@ -531,7 +521,7 @@ class Parser(abc.ABC):
             # Symbols can be referenced
             add_reference = True
 
-        elif info.type in [RObjectType.LIST, RObjectType.LANG]:
+        elif info.type in {RObjectType.LIST, RObjectType.LANG}:
             tag = None
             if info.attributes:
                 attributes = self.parse_R_object(reference_list)
@@ -579,7 +569,8 @@ class Parser(abc.ABC):
                 value = None
             else:
                 raise NotImplementedError(
-                    f"Length of CHAR cannot be {length}")
+                    f"Length of CHAR cannot be {length}",
+                )
 
         elif info.type == RObjectType.LGL:
             length = self.parse_int()
@@ -613,8 +604,11 @@ class Parser(abc.ABC):
             for i in range(length):
                 value[i] = self.parse_complex()
 
-        elif info.type in [RObjectType.STR,
-                           RObjectType.VEC, RObjectType.EXPR]:
+        elif info.type in {
+            RObjectType.STR,
+            RObjectType.VEC,
+            RObjectType.EXPR,
+        }:
             length = self.parse_int()
 
             value = [None] * length
@@ -657,8 +651,10 @@ class Parser(abc.ABC):
             raise NotImplementedError(f"Type {info.type} not implemented")
 
         if info.tag and not tag_read:
-            warnings.warn(f"Tag not implemented for type {info.type} "
-                          "and ignored")
+            warnings.warn(
+                f"Tag not implemented for type {info.type} "
+                "and ignored",
+            )
         if info.attributes and not attributes_read:
             attributes = self.parse_R_object(reference_list)
 
@@ -683,9 +679,7 @@ class Parser(abc.ABC):
 
 
 class ParserXDR(Parser):
-    """
-    Parser used when the integers and doubles are in XDR format.
-    """
+    """Parser used when the integers and doubles are in XDR format."""
 
     def __init__(
         self,
@@ -703,21 +697,21 @@ class ParserXDR(Parser):
         self.position = position
         self.xdr_parser = xdrlib.Unpacker(data)
 
-    def parse_int(self) -> int:
+    def parse_int(self) -> int:  # noqa: D102
         self.xdr_parser.set_position(self.position)
         result = self.xdr_parser.unpack_int()
         self.position = self.xdr_parser.get_position()
 
         return result
 
-    def parse_double(self) -> float:
+    def parse_double(self) -> float:  # noqa: D102
         self.xdr_parser.set_position(self.position)
         result = self.xdr_parser.unpack_double()
         self.position = self.xdr_parser.get_position()
 
         return result
 
-    def parse_string(self, length: int) -> bytes:
+    def parse_string(self, length: int) -> bytes:  # noqa: D102
         result = self.data[self.position:(self.position + length)]
         self.position += length
         return bytes(result)
@@ -746,7 +740,6 @@ def parse_file(
         :func:`parse_data`: Similar function that receives the data directly.
 
     Examples:
-
         Parse one of the included examples, containing a vector
 
         >>> import rdata
@@ -848,7 +841,6 @@ def parse_data(
         :func:`parse_file`: Similar function that parses a file directly.
 
     Examples:
-
         Parse one of the included examples, containing a vector
 
         >>> import rdata
@@ -946,9 +938,7 @@ def parse_rdata_binary(
     expand_altrep: bool = True,
     altrep_constructor_dict: AltRepConstructorMap = DEFAULT_ALTREP_MAP,
 ) -> RData:
-    """
-    Select the appropiate parser and parse all the info.
-    """
+    """Select the appropiate parser and parse all the info."""
     format_type = rdata_format(data)
 
     if format_type:
@@ -961,14 +951,12 @@ def parse_rdata_binary(
             altrep_constructor_dict=altrep_constructor_dict,
         )
         return parser.parse_all()
-    else:
-        raise NotImplementedError("Unknown file format")
+
+    raise NotImplementedError("Unknown file format")
 
 
 def bits(data: int, start: int, stop: int) -> int:
-    """
-    Read bits [start, stop) of an integer.
-    """
+    """Read bits [start, stop) of an integer."""
     count = stop - start
     mask = ((1 << count) - 1) << start
 
@@ -977,17 +965,15 @@ def bits(data: int, start: int, stop: int) -> int:
 
 
 def is_special_r_object_type(r_object_type: RObjectType) -> bool:
-    """
-    Check if a R type has a different serialization than the usual one.
-    """
-    return (r_object_type is RObjectType.NILVALUE
-            or r_object_type is RObjectType.REF)
+    """Check if a R type has a different serialization than the usual one."""
+    return (
+        r_object_type is RObjectType.NILVALUE
+        or r_object_type is RObjectType.REF
+    )
 
 
 def parse_r_object_info(info_int: int) -> RObjectInfo:
-    """
-    Parse the internal information of an object.
-    """
+    """Parse the internal information of an object."""
     type_exp = RObjectType(bits(info_int, 0, 8))
 
     reference = 0
@@ -1000,11 +986,11 @@ def parse_r_object_info(info_int: int) -> RObjectInfo:
     else:
         object_flag = bool(bits(info_int, 8, 9))
         attributes = bool(bits(info_int, 9, 10))
-        tag = bool(bits(info_int, 10, 11))
-        gp = bits(info_int, 12, 28)
+        tag = bool(bits(info_int, 10, 11))  # noqa: WPS432
+        gp = bits(info_int, 12, 28)  # noqa: WPS432
 
     if type_exp == RObjectType.REF:
-        reference = bits(info_int, 8, 32)
+        reference = bits(info_int, 8, 32)  # noqa: WPS432
 
     return RObjectInfo(
         type=type_exp,
@@ -1012,5 +998,5 @@ def parse_r_object_info(info_int: int) -> RObjectInfo:
         attributes=attributes,
         tag=tag,
         gp=gp,
-        reference=reference
+        reference=reference,
     )
diff --git a/rdata/tests/data/test_dataframe_rownames.rda b/rdata/tests/data/test_dataframe_rownames.rda
new file mode 100644
index 0000000..4c791e2
Binary files /dev/null and b/rdata/tests/data/test_dataframe_rownames.rda differ
diff --git a/rdata/tests/data/test_full_named_matrix.rda b/rdata/tests/data/test_full_named_matrix.rda
new file mode 100644
index 0000000..1b20735
Binary files /dev/null and b/rdata/tests/data/test_full_named_matrix.rda differ
diff --git a/rdata/tests/data/test_half_named_matrix.rda b/rdata/tests/data/test_half_named_matrix.rda
new file mode 100644
index 0000000..557a765
Binary files /dev/null and b/rdata/tests/data/test_half_named_matrix.rda differ
diff --git a/rdata/tests/data/test_named_matrix.rda b/rdata/tests/data/test_named_matrix.rda
new file mode 100644
index 0000000..401391e
Binary files /dev/null and b/rdata/tests/data/test_named_matrix.rda differ
diff --git a/rdata/tests/test_rdata.py b/rdata/tests/test_rdata.py
index 9c3333f..2b99cdf 100644
--- a/rdata/tests/test_rdata.py
+++ b/rdata/tests/test_rdata.py
@@ -1,3 +1,5 @@
+"""Tests of parsing and conversion."""
+
 import unittest
 from collections import ChainMap
 from fractions import Fraction
@@ -6,106 +8,190 @@ from typing import Any, Dict
 
 import numpy as np
 import pandas as pd
-
 import rdata
+import xarray
 
 TESTDATA_PATH = rdata.TESTDATA_PATH
 
 
 class SimpleTests(unittest.TestCase):
+    """Collection of simple test cases."""
 
     def test_opened_file(self) -> None:
-        parsed = rdata.parser.parse_file(open(TESTDATA_PATH /
-                                              "test_vector.rda"))
-        converted = rdata.conversion.convert(parsed)
+        """Test that an opened file can be passed to parse_file."""
+        with open(TESTDATA_PATH / "test_vector.rda") as f:
+            parsed = rdata.parser.parse_file(f)
+            converted = rdata.conversion.convert(parsed)
 
-        self.assertIsInstance(converted, dict)
+            self.assertIsInstance(converted, dict)
 
     def test_opened_string(self) -> None:
-        parsed = rdata.parser.parse_file(str(TESTDATA_PATH /
-                                             "test_vector.rda"))
+        """Test that a string can be passed to parse_file."""
+        parsed = rdata.parser.parse_file(
+            str(TESTDATA_PATH / "test_vector.rda"),
+        )
         converted = rdata.conversion.convert(parsed)
 
         self.assertIsInstance(converted, dict)
 
     def test_logical(self) -> None:
+        """Test parsing of logical vectors."""
         parsed = rdata.parser.parse_file(TESTDATA_PATH / "test_logical.rda")
         converted = rdata.conversion.convert(parsed)
 
         np.testing.assert_equal(converted, {
-            "test_logical": np.array([True, True, False, True, False])
+            "test_logical": np.array([True, True, False, True, False]),
         })
 
     def test_vector(self) -> None:
+        """Test parsing of numerical vectors."""
         parsed = rdata.parser.parse_file(TESTDATA_PATH / "test_vector.rda")
         converted = rdata.conversion.convert(parsed)
 
         np.testing.assert_equal(converted, {
-            "test_vector": np.array([1., 2., 3.])
+            "test_vector": np.array([1.0, 2.0, 3.0]),
         })
 
     def test_empty_string(self) -> None:
+        """Test that the empty string is parsed correctly."""
         parsed = rdata.parser.parse_file(TESTDATA_PATH / "test_empty_str.rda")
         converted = rdata.conversion.convert(parsed)
 
         np.testing.assert_equal(converted, {
-            "test_empty_str": [""]
+            "test_empty_str": [""],
         })
 
     def test_na_string(self) -> None:
+        """Test that the NA string is parsed correctly."""
         parsed = rdata.parser.parse_file(
-            TESTDATA_PATH / "test_na_string.rda")
+            TESTDATA_PATH / "test_na_string.rda",
+        )
         converted = rdata.conversion.convert(parsed)
 
         np.testing.assert_equal(converted, {
-            "test_na_string": [None]
+            "test_na_string": [None],
         })
 
     def test_complex(self) -> None:
+        """Test that complex numbers can be parsed."""
         parsed = rdata.parser.parse_file(TESTDATA_PATH / "test_complex.rda")
         converted = rdata.conversion.convert(parsed)
 
         np.testing.assert_equal(converted, {
-            "test_complex": np.array([1 + 2j, 2, 0, 1 + 3j, -1j])
+            "test_complex": np.array([1 + 2j, 2, 0, 1 + 3j, -1j]),
         })
 
     def test_matrix(self) -> None:
+        """Test that a matrix can be parsed."""
         parsed = rdata.parser.parse_file(TESTDATA_PATH / "test_matrix.rda")
         converted = rdata.conversion.convert(parsed)
 
         np.testing.assert_equal(converted, {
-            "test_matrix": np.array([[1., 2., 3.],
-                                     [4., 5., 6.]])
+            "test_matrix": np.array([
+                [1.0, 2.0, 3.0],
+                [4.0, 5.0, 6.0],
+            ]),
         })
 
+    def test_named_matrix(self) -> None:
+        """Test that a named matrix can be parsed."""
+        parsed = rdata.parser.parse_file(
+            TESTDATA_PATH / "test_named_matrix.rda",
+        )
+        converted = rdata.conversion.convert(parsed)
+        reference = xarray.DataArray(
+            [
+                [1.0, 2.0, 3.0],
+                [4.0, 5.0, 6.0],
+            ],
+            dims=["dim_0", "dim_1"],
+            coords={
+                "dim_0": ["dim0_0", "dim0_1"],
+                "dim_1": ["dim1_0", "dim1_1", "dim1_2"],
+            },
+        )
+
+        xarray.testing.assert_identical(
+            converted["test_named_matrix"],
+            reference,
+        )
+
+    def test_half_named_matrix(self) -> None:
+        """Test that a named matrix with no name for a dim can be parsed."""
+        parsed = rdata.parser.parse_file(
+            TESTDATA_PATH / "test_half_named_matrix.rda",
+        )
+        converted = rdata.conversion.convert(parsed)
+        reference = xarray.DataArray(
+            [
+                [1.0, 2.0, 3.0],
+                [4.0, 5.0, 6.0],
+            ],
+            dims=["dim_0", "dim_1"],
+            coords={
+                "dim_0": ["dim0_0", "dim0_1"],
+            },
+        )
+
+        xarray.testing.assert_identical(
+            converted["test_half_named_matrix"],
+            reference,
+        )
+
+    def test_full_named_matrix(self) -> None:
+        """Test that a named matrix with dim names can be parsed."""
+        parsed = rdata.parser.parse_file(
+            TESTDATA_PATH / "test_full_named_matrix.rda",
+        )
+        converted = rdata.conversion.convert(parsed)
+        reference = xarray.DataArray(
+            [
+                [1.0, 2.0, 3.0],
+                [4.0, 5.0, 6.0],
+            ],
+            dims=["my_dim_0", "my_dim_1"],
+            coords={
+                "my_dim_0": ["dim0_0", "dim0_1"],
+                "my_dim_1": ["dim1_0", "dim1_1", "dim1_2"],
+            },
+        )
+
+        xarray.testing.assert_identical(
+            converted["test_full_named_matrix"],
+            reference,
+        )
+
     def test_list(self) -> None:
+        """Test that list can be parsed."""
         parsed = rdata.parser.parse_file(TESTDATA_PATH / "test_list.rda")
         converted = rdata.conversion.convert(parsed)
 
         np.testing.assert_equal(converted, {
             "test_list":
                 [
-                    np.array([1.]),
+                    np.array([1.0]),
                     ['a', 'b', 'c'],
-                    np.array([2., 3.]),
-                    ['hi']
-                ]
+                    np.array([2.0, 3.0]),
+                    ['hi'],
+                ],
         })
 
     def test_expression(self) -> None:
+        """Test that expressions can be parsed."""
         parsed = rdata.parser.parse_file(TESTDATA_PATH / "test_expression.rda")
         converted = rdata.conversion.convert(parsed)
 
         np.testing.assert_equal(converted, {
             "test_expression": rdata.conversion.RExpression([
-                rdata.conversion.RLanguage(['^', 'base', 'exponent'])])
+                rdata.conversion.RLanguage(['^', 'base', 'exponent']),
+            ]),
         })
 
     def test_encodings(self) -> None:
-
+        """Test of differents encodings."""
         with self.assertWarns(
             UserWarning,
-            msg="Unknown encoding. Assumed ASCII."
+            msg="Unknown encoding. Assumed ASCII.",
         ):
             parsed = rdata.parser.parse_file(
                 TESTDATA_PATH / "test_encodings.rda",
@@ -120,7 +206,7 @@ class SimpleTests(unittest.TestCase):
             })
 
     def test_encodings_v3(self) -> None:
-
+        """Test encodings in version 3 format."""
         parsed = rdata.parser.parse_file(
             TESTDATA_PATH / "test_encodings_v3.rda",
         )
@@ -134,8 +220,8 @@ class SimpleTests(unittest.TestCase):
         })
 
     def test_dataframe(self) -> None:
-
-        for f in {"test_dataframe.rda", "test_dataframe_v3.rda"}:
+        """Test dataframe conversion."""
+        for f in ("test_dataframe.rda", "test_dataframe_v3.rda"):
             with self.subTest(file=f):
                 parsed = rdata.parser.parse_file(
                     TESTDATA_PATH / f,
@@ -144,25 +230,53 @@ class SimpleTests(unittest.TestCase):
 
                 pd.testing.assert_frame_equal(
                     converted["test_dataframe"],
-                    pd.DataFrame({
-                        "class": pd.Categorical(
-                            ["a", "b", "b"]),
-                        "value": [1, 2, 3],
-                    })
+                    pd.DataFrame(
+                        {
+                            "class": pd.Categorical(
+                                ["a", "b", "b"],
+                            ),
+                            "value": [1, 2, 3],
+                        },
+                        index=pd.RangeIndex(start=1, stop=4),
+                    ),
                 )
 
+    def test_dataframe_rownames(self) -> None:
+        """Test dataframe conversion."""
+        parsed = rdata.parser.parse_file(
+            TESTDATA_PATH / "test_dataframe_rownames.rda",
+        )
+        converted = rdata.conversion.convert(parsed)
+
+        pd.testing.assert_frame_equal(
+            converted["test_dataframe_rownames"],
+            pd.DataFrame(
+                {
+                    "class": pd.Categorical(
+                        ["a", "b", "b"],
+                    ),
+                    "value": [1, 2, 3],
+                },
+                index=('Madrid', 'Frankfurt', 'Herzberg am Harz'),
+            ),
+        )
+
     def test_ts(self) -> None:
+        """Test time series conversion."""
         parsed = rdata.parser.parse_file(TESTDATA_PATH / "test_ts.rda")
         converted = rdata.conversion.convert(parsed)
 
-        pd.testing.assert_series_equal(converted["test_ts"],
-                                       pd.Series({
-                                           2000 + Fraction(2, 12): 1.,
-                                           2000 + Fraction(3, 12): 2.,
-                                           2000 + Fraction(4, 12): 3.,
-                                       }))
+        pd.testing.assert_series_equal(
+            converted["test_ts"],
+            pd.Series({
+                2000 + Fraction(2, 12): 1.0,
+                2000 + Fraction(3, 12): 2.0,
+                2000 + Fraction(4, 12): 3.0,
+            }),
+        )
 
     def test_s4(self) -> None:
+        """Test parsing of S4 classes."""
         parsed = rdata.parser.parse_file(TESTDATA_PATH / "test_s4.rda")
         converted = rdata.conversion.convert(parsed)
 
@@ -170,20 +284,22 @@ class SimpleTests(unittest.TestCase):
             "test_s4": SimpleNamespace(
                 age=np.array(28),
                 name=["Carlos"],
-                **{'class': ["Person"]}
-            )
+                **{'class': ["Person"]},  # noqa: WPS517
+            ),
         })
 
     def test_environment(self) -> None:
+        """Test parsing of environments."""
         parsed = rdata.parser.parse_file(
-            TESTDATA_PATH / "test_environment.rda")
+            TESTDATA_PATH / "test_environment.rda",
+        )
         converted = rdata.conversion.convert(parsed)
 
         dict_env = {'string': ['test']}
         empty_global_env: Dict[str, Any] = {}
 
         np.testing.assert_equal(converted, {
-            "test_environment": ChainMap(dict_env, ChainMap(empty_global_env))
+            "test_environment": ChainMap(dict_env, ChainMap(empty_global_env)),
         })
 
         global_env = {"global": "test"}
@@ -194,24 +310,27 @@ class SimpleTests(unittest.TestCase):
         )
 
         np.testing.assert_equal(converted_global, {
-            "test_environment": ChainMap(dict_env, ChainMap(global_env))
+            "test_environment": ChainMap(dict_env, ChainMap(global_env)),
         })
 
     def test_emptyenv(self) -> None:
+        """Test parsing the empty environment."""
         parsed = rdata.parser.parse_file(
-            TESTDATA_PATH / "test_emptyenv.rda")
+            TESTDATA_PATH / "test_emptyenv.rda",
+        )
         converted = rdata.conversion.convert(parsed)
 
-        np.testing.assert_equal(converted, {
-            "test_emptyenv": ChainMap({})
+        self.assertEqual(converted, {
+            "test_emptyenv": ChainMap({}),
         })
 
     def test_list_attrs(self) -> None:
+        """Test that lists accept attributes."""
         parsed = rdata.parser.parse_file(TESTDATA_PATH / "test_list_attrs.rda")
         converted = rdata.conversion.convert(parsed)
 
         np.testing.assert_equal(converted, {
-            "test_list_attrs": [['list'], [5]]
+            "test_list_attrs": [['list'], [5]],
         })
 
     def test_altrep_compact_intseq(self) -> None:
@@ -244,7 +363,7 @@ class SimpleTests(unittest.TestCase):
         converted = rdata.conversion.convert(parsed)
 
         np.testing.assert_equal(converted, {
-            "test_altrep_deferred_string": [
+            "test_altrep_deferred_string": [  # noqa: WPS317
                 "1", "2.3", "10000",
                 "1e+05", "-10000", "-1e+05",
                 "0.001", "1e-04", "1e-05",
@@ -286,5 +405,4 @@ class SimpleTests(unittest.TestCase):
 
 
 if __name__ == "__main__":
-    # import sys;sys.argv = ['', 'Test.testName']
     unittest.main()
diff --git a/setup.cfg b/setup.cfg
index 46e0513..5794b78 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -11,6 +11,125 @@ include_trailing_comma = true
 use_parentheses = true
 combine_as_imports = 1
 
+[flake8]
+ignore =
+	# No docstring for magic methods
+	D105,
+	# No docstrings in __init__
+	D107,
+	# Ignore until https://github.com/terrencepreilly/darglint/issues/54 is closed
+	DAR202,
+	# Ignore until https://github.com/terrencepreilly/darglint/issues/144 is closed
+	DAR401,
+	# Non-explicit exceptions may be documented in raises
+	DAR402,
+	# Uppercase arguments like X are common in scikit-learn
+	N803,
+	# Uppercase variables like X are common in scikit-learn
+	N806,
+	# There are no bad quotes
+	Q000,
+	# Google Python style is not RST until after processed by Napoleon
+    # See https://github.com/peterjc/flake8-rst-docstrings/issues/17
+    RST201, RST203, RST301,
+    # assert is used by pytest tests
+    S101,
+	# Line break occurred before a binary operator (antipattern)
+	W503,
+	# Utils is used as a module name
+	WPS100,
+	# Short names like X or y are common in scikit-learn
+	WPS111,
+	# We do not like this underscored numbers convention
+	WPS114,
+	# Attributes in uppercase are used in enums
+	WPS115,
+	# Trailing underscores are a scikit-learn convention
+	WPS120,
+	# Cognitive complexity cannot be avoided at some modules
+	WPS232,
+	# The number of imported things may be large, especially for typing
+	WPS235,
+	# We like local imports, thanks
+	WPS300,
+	# Dotted imports are ok
+	WPS301,
+	# We love f-strings
+	WPS305,
+	# Implicit string concatenation is useful for exception messages
+	WPS306,
+	# No base class needed
+	WPS326,
+	# We allow multiline conditions
+	WPS337,
+	# We order methods differently
+	WPS338,
+	# We need multine loops
+	WPS352,
+	# Assign to a subcript slice is normal behaviour in numpy
+	WPS362,
+	# All keywords are beautiful
+	WPS420,
+	# We use nested imports sometimes, and it is not THAT bad
+	WPS433,
+	# We use list multiplication to allocate list with immutable values (None or numbers)
+	WPS435,
+	# Our private modules are fine to import
+	# (check https://github.com/wemake-services/wemake-python-styleguide/issues/1441)
+	WPS436,
+	# Our private objects are fine to import
+	WPS450,
+	# Numpy mixes bitwise and comparison operators
+	WPS465,
+	# Explicit len compare is better than implicit
+	WPS507,
+	# Comparison with not is not the same as with equality
+	WPS520,
+
+per-file-ignores =
+	__init__.py:
+		# Unused modules are allowed in `__init__.py`, to reduce imports
+		F401,
+		# Import multiple names is allowed in `__init__.py`
+		WPS235,
+		# Logic is allowed in `__init__.py`
+		WPS412
+
+	# Tests benefit from overused expressions, magic numbers and fixtures
+	test_*.py: WPS204, WPS432, WPS442
+
+rst-directives =
+    # These are sorted alphabetically - but that does not matter
+    autosummary,data,currentmodule,deprecated,
+    glossary,moduleauthor,plot,testcode,
+    versionadded,versionchanged,
+
+rst-roles =
+    attr,class,func,meth,mod,obj,ref,term,
+
+allowed-domain-names = data, info, obj, result, results, val, value, values, var
+
+# Needs to be tuned
+max-arguments = 10
+max-attributes = 10
+max-cognitive-score = 30
+max-expressions = 15
+max-imports = 20
+max-line-complexity = 30
+max-local-variables = 15
+max-methods = 30
+max-module-expressions = 15
+max-module-members = 15
+max-string-usages = 10
+
+ignore-decorators = (property)|(overload)
+
+strictness = long
+
+# Beautify output and make it more informative
+format = wemake
+show-source = true
+
 [mypy]
 strict = True
 strict_equality = True
diff --git a/setup.py b/setup.py
index ff0d6d5..2e8bfe4 100644
--- a/setup.py
+++ b/setup.py
@@ -7,6 +7,7 @@ This package parses .rda datasets used in R. It does not depend on the R
 language or its libraries, and thus it is released under a MIT license.
 """
 import os
+import pathlib
 import sys
 
 from setuptools import find_packages, setup
@@ -16,44 +17,51 @@ pytest_runner = ['pytest-runner'] if needs_pytest else []
 
 DOCLINES = (__doc__ or '').split("\n")
 
-with open(os.path.join(os.path.dirname(__file__),
-                       'VERSION'), 'r') as version_file:
+with open(
+    pathlib.Path(os.path.dirname(__file__)) / 'rdata' / 'VERSION',
+    'r',
+) as version_file:
     version = version_file.read().strip()
 
-setup(name='rdata',
-      version=version,
-      description=DOCLINES[1],
-      long_description="\n".join(DOCLINES[3:]),
-      url='https://github.com/vnmabus/rdata',
-      author='Carlos Ramos Carreño',
-      author_email='vnmabus@gmail.com',
-      include_package_data=True,
-      platforms=['any'],
-      license='MIT',
-      packages=find_packages(),
-      python_requires='>=3.7, <4',
-      classifiers=[
-          'Development Status :: 4 - Beta',
-          'Intended Audience :: Developers',
-          'Intended Audience :: Science/Research',
-          'License :: OSI Approved :: MIT License',
-          'Natural Language :: English',
-          'Operating System :: OS Independent',
-          'Programming Language :: Python :: 3',
-          'Programming Language :: Python :: 3.6',
-          'Programming Language :: Python :: 3.7',
-          'Programming Language :: Python :: 3.8',
-          'Topic :: Scientific/Engineering :: Mathematics',
-          'Topic :: Software Development :: Libraries :: Python Modules',
-          'Typing :: Typed',
-      ],
-      keywords=['rdata', 'r', 'dataset'],
-      install_requires=['numpy',
-                        'xarray',
-                        'pandas'],
-      setup_requires=pytest_runner,
-      tests_require=['pytest-cov',
-                     'numpy>=1.14'  # The printing format for numpy changes
-                     ],
-      test_suite='rdata.tests',
-      zip_safe=False)
+setup(
+    name='rdata',
+    version=version,
+    description=DOCLINES[1],
+    long_description="\n".join(DOCLINES[3:]),
+    url='https://github.com/vnmabus/rdata',
+    author='Carlos Ramos Carreño',
+    author_email='vnmabus@gmail.com',
+    include_package_data=True,
+    platforms=['any'],
+    license='MIT',
+    packages=find_packages(),
+    python_requires='>=3.7, <4',
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Intended Audience :: Developers',
+        'Intended Audience :: Science/Research',
+        'License :: OSI Approved :: MIT License',
+        'Natural Language :: English',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
+        'Topic :: Scientific/Engineering :: Mathematics',
+        'Topic :: Software Development :: Libraries :: Python Modules',
+        'Typing :: Typed',
+    ],
+    keywords=['rdata', 'r', 'dataset'],
+    install_requires=[
+        'numpy',
+        'xarray',
+        'pandas',
+    ],
+    setup_requires=pytest_runner,
+    tests_require=[
+        'pytest-cov',
+        'numpy>=1.14',  # The printing format for numpy changes
+    ],
+    test_suite='rdata.tests',
+    zip_safe=False,
+)