Repository: MDAnalysis/panedr (language: python)

Issue:
panedrlite should allow `import panedr`
The current dev version of panedrlite is used as
```python
import panedrlite as panedr
```
but both @jbarnoud (in PR #42) and @IAlibay (in PR #47 ) stated that the following should actually work **for panedrlite**
```python
import panedr
```



Discussion / hints:
So I've spent a large amount of time testing things out since yesterday, unfortunately we're hitting a lot of limitations when it comes to what setuptools & the PEPs of packaging allow.

As far as I can tell these are the feasible options we can have:

#### 1. Keep things as they are:
  - This means that we have to have different import patterns for both packages, in my opinion that's problematic if we want both packages to be interchangeable.
  - Having panedrlite be the base package isn't wrong though, we just have to re-evaluate things under the context that it's the base package. It's kind of overkill though.

#### 2. Only have one package and make pandas an extra_requires
  - This is the best option in my opinion. I know @jbarnoud doesn't agree, but this means the one package can be put in an `install_requires`, so if users have the package already they don't re-install because it wasn't the right flavour.
  - We can split things up on conda-forge and make two packages here without problems.

#### 3. Have two setup.py in different directories copying files across
  - This is messy, but we could create a panedrlite directory with it's own setup.py/setup.cfg/pyproject.toml that copies over the source files on deployment. It _works_, but it's going to need extra attention.

#### 4. two setup.py in one package directory
  - Technically you can do this right now (if you delete the contents of setup.cfg), but it's dangerous and you can't upgrade to pyproject.toml.
Option 2 is the one that is fully supported & will make downstream life easier imho. I know it breaks user expectations, but it doesn't affect the science just the need to install pandas 

Option 3 can work but it'll be messy on paper, 4 is a terrible idea, 1 _works_ but seems overkill for such a small package, if we do this we'll need to make things _very_ clear to users and it'll be sure to confuse users.

Option 5 is to make panedr optional for MDA. Then we don't care about the pandas dependency in most cases.
I gave it a try and it indeed looks more complex than I thought it was. This seems to be one of the rare case where conda is simpler than pip.

I do not like option 2 because it means that one downstream use case breaks the experience for all the others.

One solution I discarded earlier is to have 2 distinct packages with distinct code: one with a name like `pyedr` is the equivalent of what is now `panedr-lite`, and `panedr`. `pyedr` has all the code except for `edr_to_df`. `panedr` only has something like:

```python
import pyedr
import pandas as pd

def edr_to_df(...):
    ...
    all_energies, all_names, times = read_edr(path, verbose=verbose)
    df = pd.DataFrame(all_energies, columns=all_names, index=times)
    return df
```

I discarded this option because it means having code in two different places (even though these places are in the same repo), compared to the metapackaged it *looked* more cumbersome. The metapackage not being trivial as I thought it was, the drawback of having code in two places loses importance.
It makes for a package that's all of ~ 5 lines of code, which is a bit excessive, but sure that way you could have panedr completely decoupled from the base package, panedr would keep its current version number and never really change and pyedr would be the thing that gets actively developed?

It does mean that bug fixes & changes because more opaque to users of panedr though.
Do we expect much fixes and changes, though? EDR is a surprisingly stable format and the feature set of panedr is limited on purpose.

It is also possible to bump the version number of panedr if big changes happened in pyedr, just to force the update of pyedr. The visibility of the changes, at the end of the day, is mostly a matter of publicity.
> Do we expect much fixes and changes, though? EDR is a surprisingly stable format and the feature set of panedr is limited on purpose.
> 
> It is also possible to bump the version number of panedr if big changes happened in pyedr, just to force the update of pyedr. The visibility of the changes, at the end of the day, is mostly a matter of publicity.

I expect at ~ two more versions of this code before a stable 1.0.

Anyways we should quickly come up with an agreement on the way forward. In the nicest way possible, as long as I don't have to take responsibility for working out the versioning / changelog-ing the two packages, I'm happy to go ahead and implement the option to split it into `pyedr` and `panedr`.

@BFedder @orbeckst @hmacdope any views here? We probably need to get this moving for MDA stuff to move on.
I like the dichotomy of panedr and pyedr — it's pretty clear from the name what they do. It also means minimal disruption for anyone using panedr already. 

If there a clear notes for panedr users to check the pyedr CHANGELOG and the release procedure for pyedr contains "bump panedr version" then it should be ok. 

Do you want to maintain them in the same repo or make two separate ones? (If separate ones then I'd actually suggest to rename the current one to pyedr to keep the issue history together with the code and create a fresh panedr one.)
my first instinct would be to suggest keeping both packages in the same repo (similar to the work @BFedder has already done)

Fix patch:
diff --git a/.coveragerc b/.coveragerc
index 48a8796..81b01a7 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,7 +1,6 @@
 [run]
 omit =
-    panedr/*
-    tests/data/*
+    pyedr/pyedr/tests/data/*
 
 [report]
 exclude_lines =
diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
new file mode 100644
index 0000000..678fda4
--- /dev/null
+++ b/.github/workflows/deploy.yaml
@@ -0,0 +1,69 @@
+name: Build and upload to PyPI
+
+on:
+  pull_request:
+    branches:
+      - "package-*"
+  push:
+    branches:
+      - "package-*"
+    tags:
+      - "package-*"
+  release:
+    types:
+      - published
+
+
+concurrency:
+  group: "${{ github.ref }}-${{ github.head_ref }}-${{ github.workflow }}"
+  cancel-in-progress: true
+
+
+defaults:
+  run:
+    shell: bash -l {0}
+
+
+jobs:
+  build_wheels:
+    environment: deploy
+    if: "github.repository == 'MDAnalysis/panedr'"
+    name: Build pure Python wheel
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
+
+      - name: setup_miniconda
+        uses: conda-incubator/setup-miniconda@v2
+        with:
+          python-version: 3.8
+          auto-update-conda: true
+          add-pip-as-python-dependency: true
+          architecture: x64
+
+      - name: install_deps
+        run: |
+          cd pyedr && python -m pip install build
+          cd panedr && python -m pip install build
+
+      - name: build
+        run: |
+          cd pyedr && python -m build --sdist --wheel --outdir ../dist/
+          cd panedr && python -m build --sdist --wheel --outdir ../dist
+
+      - name: publish_testpypi
+        # Upload to testpypi on every tag
+        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
+        uses: pypa/gh-action-pypi-publish@master
+        with:
+          password: ${{ secrets.TEST_PYPI_API_TOKEN }}
+          repository_url: https://test.pypi.org/legacy/
+
+      - name: publish_pypi
+        if: github.event_name == 'release' && github.event.action == 'published'
+        uses: pypa/gh-action-pypi-publish@master
+        with:
+          password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.github/workflows/gh-ci.yaml b/.github/workflows/gh-ci.yaml
index bc2dbb1..3ea80ae 100644
--- a/.github/workflows/gh-ci.yaml
+++ b/.github/workflows/gh-ci.yaml
@@ -45,7 +45,7 @@ jobs:
 
     - name: install package deps
       run: |
-        mamba install pandas pytest pytest-xdist pytest-cov codecov pbr
+        mamba install pytest pytest-xdist pytest-cov codecov pbr numpy
 
     - name: check install
       run: |
@@ -54,28 +54,32 @@ jobs:
         conda info
         conda list
 
-    - name: install panedrlite package
+    - name: install pyedr package
       run: |
         # installing via pip leads to codecov not being able to report coverage
-        cd panedrlite && python setup.py develop
+        cd pyedr && python setup.py develop
+
+    - name: run pyedr tests
+      run: |
+        pytest -n 2 -v --cov=pyedr/pyedr --cov-report=xml --color=yes pyedr/pyedr/tests
 
     - name: install panedr package
       run: |
+        mamba install pandas
         cd panedr && python setup.py develop
 
+    - name: run panedr tests
+      run: |
+        pytest -n2 -v --cov=panedr/panedr --cov-report=xml --color=yes --cov-append panedr/panedr/tests
+
     - name: test imports
       # Exit the git repo in order for pbr to stop auto-picking up version info
       # from the local git data
       working-directory: ../
       run: |
-        python -Ic "from panedrlite import edr_to_dict"
+        python -Ic "from pyedr import edr_to_dict"
         python -Ic "from panedr import edr_to_df"
 
-
-    - name: run unit tests
-      run: |
-        pytest -n 2 -v --cov=panedrlite/panedrlite --cov-report=xml --color=yes ./tests
-
     - name: codecov
       uses: codecov/codecov-action@v3
       with:
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index a7bd9e7..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,3 +0,0 @@
-include README.rst LICENSE.txt
-include setup.py setup.cfg
-include panedr/VERSION
diff --git a/README.rst b/README.rst
index 69c6275..f891b55 100644
--- a/README.rst
+++ b/README.rst
@@ -1,25 +1,61 @@
-Panedr
-======
+Pyedr and Panedr
+================
 
 |Build Status| |cov|
 
-Panedr reads a `Gromacs EDR`_ binary energy XDR file and returns its content
-as a pandas_ dataframe. The library exposes three functions:
+This repository hosts the source files for both the Pyedr and Panedr packages.
 
-- the ``edr_to_df`` function, which gets the path to an EDR file and returns a
-  pandas DataFrame,
+``pyedr`` and ``panedr`` are compatible with Python 3.6 and greater.
+
+Pyedr
+-----
+
+Pyedr provides a means of reading a `Gromacs EDR`_ binary XDR file and
+return its contents as a dictionary of `numpy`_ arrays. Pyedr exposes
+the following functions:
 
-- the ``edr_to_dict`` function, which returns a dictionary of NumPy arrays instead
-  of a pandas DataFrame
+- ``edr_to_dict``: returns a dictionary of NumPy arrays keyed by the energy
+  type from a given path to an EDR file.
+- ``read_edr``: parses an EDR file and returns the energy terms
+  in a nested list
 
-- and the ``read_edr`` function, which is called by the other two functions to
-  do the actual reading of EDR files. It returns a tuple of lists.
 
-``panedr`` is compatible with Python 3.6 and greater.
+Panedr
+------
+
+Panedr uses the Pyedr library to read a `Gromacs EDR`_ binary energy XDR file
+and returns its contents as a pandas_ dataframe. Panedr exposes the
+following function:
+
+- ``edr_to_df``: which gets the path to an EDR file and returns a
+  pandas DataFrame,
+
 
 Example
 -------
 
+Using ``pyedr``:
+
+
+.. code:: python
+
+    import pyedr
+
+    # Read the EDR file
+    path = 'ener.edr'
+    dic = pyedr.edr_to_dict(path)
+
+    # The `verbose` optional parameter can be set to True to display the
+    # progress on stderr
+    dic = pyedr.edr_to_dict(path, verbose=True)
+
+    # Get the average pressure after the first 10 ns
+    pressure_avg = dic['Pressure'][dic['Time'] > 10000].mean()
+
+
+Using ``panedr``:
+
+
 .. code:: python
 
     import panedr
@@ -27,87 +63,69 @@ Example
     # Read the EDR file
     path = 'ener.edr'
     df = panedr.edr_to_df(path)
-    dic = panedr.edr_to_dict(path)
 
     # The `verbose` optional parameter can be set to True to display the
     # progress on stderr
     df = panedr.edr_to_df(path, verbose=True)
-    dic = panedr.edr_to_dict(path, verbose=True)
 
     # Get the average pressure after the first 10 ns
     pressure_avg = df['Pressure'][df['Time'] > 10000].mean()
-    pressure_avg = dic['Pressure'][dic['Time'] > 10000].mean()
 
 
 Install
 -------
-Install panedr ``pip``:
+
+You can install ``pyedr`` and ``panedr`` using ``pip``:
 
 .. code:: bash
 
+    pip install pyedr 
+
+    # installing panedr automatically installs pyedr
     pip install panedr
 
 
-If you are using `conda`_ and `conda-forge`_, you can install with
+If you are using `conda`_ and `conda-forge`_, you can install with:
 
 .. code:: bash
 
+    conda install -c conda-forge pyedr
+
+    # install panedr automatically installs pyedr
     conda install -c conda-forge panedr
 
+
 Tests
 -----
 
-The ``panedr`` repository contains a series of tests. If you downloaded or
-cloned the code from the repository, you can run the tests. To do so,
-install `pytest`_, and, in the directory of the
+The ``pyedr`` and ``panedr`` repositories contains a series of tests.
+If you downloaded or cloned the code from the repository, you can run
+the tests. To do so, install `pytest`_, and, in the directory of the
 panedr source code, run:
 
+For ``pyedr``:
+
+
 .. code:: bash
 
-    pytest -v tests
+    pytest -v pyedr/pyedr/tests
 
 
-panedrlite
-----------
-Under the hood, panedr is just a metapackage that installs panedrlite and
-the requirements for all functions, notably including pandas. To avoid requiring
-pandas in downstream applications, panedrlite is available for installation as
-well. It provides all functionality, except that pandas is not automatically
-installed as a dependency, and therefore :func:`edr_to_df` will not work
-out-of-the-box unless it is installed manually. `panedrlite` also uses the panedr
-namespace, so `import panedr` works. 
+For ``panedr``:
 
 
 .. code:: bash
 
-    pip install panedrlite
+    pytest -v panedr/panedr/tests
 
 
 License
 -------
 
-Panedr translate in python part of the source code of Gromacs.
+Pyedr and Panedr translate part of the source code of Gromacs into Python.
 Therefore, Panedr is distributed under the same GNU Lesser General
-Public License version 2.1 as Gromacs.
-
-    Panedr — a library to manipulate Gromacs EDR file in python
-
-    Copyright (C) 2016 Jonathan Barnoud
-
-    This library is free software; you can redistribute it and/or modify
-    it under the terms of the GNU Lesser General Public License as
-    published by the Free Software Foundation; either version 2.1 of the
-    License, or (at your option) any later version.
-
-    This library is distributed in the hope that it will be useful, but
-    WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-    Lesser General Public License for more details.
+Public License version 2.1 as Gromacs. See the `license`_ for more details.
 
-    You should have received a copy of the GNU Lesser General Public
-    License along with this library; if not, write to the Free Software
-    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-    02110-1301 USA
 
 .. |Build Status| image:: https://github.com/MDAnalysis/panedr/actions/workflows/gh-ci.yaml/badge.svg
    :alt: Github Actions Build Status
@@ -118,7 +136,9 @@ Public License version 2.1 as Gromacs.
    :target: https://codecov.io/gh/MDAnalysis/panedr
 
 .. _`Gromacs EDR`: https://manual.gromacs.org/documentation/current/reference-manual/file-formats.html#edr
+.. _numpy: https://numpy.org/
 .. _pandas: https://pandas.pydata.org/
 .. _conda: https://docs.conda.io
 .. _`conda-forge`: https://conda-forge.org/
 .. _pytest: https://docs.pytest.org/
+.. _license: https://github.com/MDAnalysis/panedr/blob/master/LICENSE.txt
diff --git a/panedr/MANIFEST.in b/panedr/MANIFEST.in
new file mode 100644
index 0000000..a07afba
--- /dev/null
+++ b/panedr/MANIFEST.in
@@ -0,0 +1,2 @@
+include ../README.rst ../LICENSE.txt
+include setup.py setup.cfg
diff --git a/panedr/panedr/__init__.py b/panedr/panedr/__init__.py
index 3f82318..bfe9caf 100644
--- a/panedr/panedr/__init__.py
+++ b/panedr/panedr/__init__.py
@@ -4,4 +4,4 @@
 __version__ = pbr.version.VersionInfo('panedr').release_string()
 del pbr
 
-from panedrlite import edr_to_dict, edr_to_df, read_edr
+from .panedr import edr_to_df
diff --git a/panedr/panedr/panedr.py b/panedr/panedr/panedr.py
new file mode 100644
index 0000000..e544bc6
--- /dev/null
+++ b/panedr/panedr/panedr.py
@@ -0,0 +1,65 @@
+#-*- coding:utf-8 -*-
+# Panedr -- a library to manipulate Gromacs EDR file in python
+# Copyright (C) 2016  Jonathan Barnoud
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA  02110-1301  USA
+
+# Most of this file is a python rewrite of part of
+# `src/gromacs/fileio/enxio.c` from Gromacs 5.1.
+# See gromacs.org.
+
+"""
+Panedr -- Read Gromacs energy file (EDR) to ``pandas.Dataframe`` in python
+==========================================================================
+
+The ``panedr`` library allows users to read and manipulate the content of
+Gromacs energy file (.edr files) in python using pandas Dataframes.
+
+This uses the ``pyedr`` library which tries to be in par with Gromacs 5.1.1
+when it comes to reading EDR files.
+
+So far, only one function is exposed by the library : the :fun:`edr_to_df`
+function that returns a pandas ``DataFrame`` from an EDR file.
+
+.. autofunction:: edr_to_df
+"""
+
+from pyedr import edr_to_dict, read_edr
+import pandas as pd
+
+
+__all__ = ['edr_to_df', ]
+
+
+def edr_to_df(path: str, verbose: bool = False) -> pd.DataFrame:
+    """Calls :func:`read_edr` from ``pyedr`` and packs its return values into
+    a ``pandas.DataFrame``.
+
+    Parameters
+    ----------
+    path : str
+        path to EDR file to be read
+    verbose : bool
+        Optionally show verbose output while reading the file
+    Returns
+    -------
+    df: pandas.DataFrame
+        :class:`pandas.DataFrame()` object that holds all energy terms found in
+        the EDR file.
+    """
+    all_energies, all_names, times = read_edr(path, verbose=verbose)
+    df = pd.DataFrame(all_energies, columns=all_names, index=times)
+    return df
diff --git a/panedr/requirements.txt b/panedr/requirements.txt
index 0882827..098d244 100644
--- a/panedr/requirements.txt
+++ b/panedr/requirements.txt
@@ -1,3 +1,3 @@
-panedrlite[pandas]
-numpy>=1.19.0
+pandas
+pyedr
 pbr
diff --git a/panedr/setup.cfg b/panedr/setup.cfg
index cb5d958..d80645b 100644
--- a/panedr/setup.cfg
+++ b/panedr/setup.cfg
@@ -2,13 +2,13 @@
 name = panedr
 author = Jonathan Barnoud
 author_email = jonathan@barnoud.net
-summary = Read and manipulate Gromacs energy files
-license = LGPL
+summary = Read and manipulate Gromacs energy files to pandas Dataframes
+license = LGPLv2+
+license_file = ../LICENSE.txt
 description_file =
     ../README.rst
 long_description_content_type = text/x-rst
-home_page = https://github.com/MDAnalysis/panedr
-python_requires = >=3.6
+url = https://github.com/MDAnalysis/panedr
 classifier =
     Development Status :: 4 - Beta
     Intended Audience :: Developers
@@ -23,9 +23,13 @@ classifier =
     Programming Language :: Python :: 3.10
     Operating System :: OS Independent
 
-[extras]
+[options]
+python_requires = >= 3.6
+install_requires =
+    pyedr
+    pandas
+    pbr
+
+[options.extras_require]
 test =
-    six
     pytest
-pandas =
-    pandas
diff --git a/panedr/setup.py b/panedr/setup.py
index aa10fa0..6c1c6cc 100755
--- a/panedr/setup.py
+++ b/panedr/setup.py
@@ -1,6 +1,7 @@
 from setuptools import setup
 
-setup(
-    setup_requires=['pbr'],
-    pbr=True,
+setup(name="panedr",
+      setup_requires=['pbr'],
+      pbr=True,
+      include_package_data=True,
 )
diff --git a/panedrlite/panedrlite/__init__.py b/panedrlite/panedrlite/__init__.py
deleted file mode 100644
index 44419c4..0000000
--- a/panedrlite/panedrlite/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pbr.version
-__version__ = pbr.version.VersionInfo('panedrlite').release_string()
-del pbr
-
-from .panedr import edr_to_df, edr_to_dict, read_edr
diff --git a/panedrlite/setup.py b/panedrlite/setup.py
deleted file mode 100755
index f963b8c..0000000
--- a/panedrlite/setup.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from setuptools import setup
-
-setup(name="panedr",
-    setup_requires=['pbr'],
-    pbr=True,
-)
diff --git a/pyedr/MANIFEST.in b/pyedr/MANIFEST.in
new file mode 100644
index 0000000..a07afba
--- /dev/null
+++ b/pyedr/MANIFEST.in
@@ -0,0 +1,2 @@
+include ../README.rst ../LICENSE.txt
+include setup.py setup.cfg
diff --git a/pyedr/pyedr/__init__.py b/pyedr/pyedr/__init__.py
new file mode 100644
index 0000000..2dd0829
--- /dev/null
+++ b/pyedr/pyedr/__init__.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+import pbr.version
+__version__ = pbr.version.VersionInfo('pyedr').release_string()
+del pbr
+
+from .pyedr import edr_to_dict, read_edr
diff --git a/panedrlite/panedrlite/panedr.py b/pyedr/pyedr/pyedr.py
similarity index 90%
rename from panedrlite/panedrlite/panedr.py
rename to pyedr/pyedr/pyedr.py
index 2063253..29effc9 100644
--- a/panedrlite/panedrlite/panedr.py
+++ b/pyedr/pyedr/pyedr.py
@@ -1,6 +1,6 @@
 #-*- coding:utf-8 -*-
-# Panedr -- a library to manipulate Gromacs EDR file in python
-# Copyright (C) 2016  Jonathan Barnoud
+# PyEDR -- a library to manipulate Gromacs EDR file in python
+# Copyright (C) 2022  Jonathan Barnoud
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -22,13 +22,13 @@
 # See gromacs.org.
 
 """
-Panedr -- Read Gromacs energy file (EDR) in python
+PyEDR -- Read Gromacs energy file (EDR) in python
 ==================================================
 
-The ``panedr`` library allows to read and manipulate the content of Gromacs
+The ``pyedr`` library allows to read and manipulate the content of Gromacs
 energy file (.edr files) in python.
 
-The current version of ``panedr`` tries to be in par with Gromacs 5.1.1 and
+The current version of ``pyedr`` tries to be in par with Gromacs 5.1.1 and
 newer when it comes to reading EDR files.
 
 The library exposes the following functions:
@@ -36,26 +36,21 @@
 - the :func:`read_edr` function parses an EDR file and returns the energy terms
   in a nested list
 
-- the :func:`edr_to_df` function that turns the nested list created by
-  :func:`read_edr` into a pandas ``DataFrame``
-
 - the :func:`edr_to_dict` function that turns the nested list created by
   :func:`read_edr` into a dictionary that maps term names to numpy arrays
 
-.. autofunction:: edr_to_df
+.. autofunction:: edr_to_dict
 """
-
-from __future__ import print_function
-
 import xdrlib
 import collections
 import warnings
 import sys
 import itertools
 import time
-import numpy as np
 from typing import List, Tuple, Dict
 
+import numpy as np
+
 
 #Index for the IDs of additional blocks in the energy file.
 #Blocks can be added without sacrificing backward and forward
@@ -85,7 +80,7 @@
 Enxnm = collections.namedtuple('Enxnm', 'name unit')
 ENX_VERSION = 5
 
-__all__ = ['edr_to_df', 'edr_to_dict', 'read_edr']
+__all__ = ['edr_to_dict', 'read_edr']
 
 class EDRFile(object):
     def __init__(self, path):
@@ -469,38 +464,6 @@ def read_edr(path: str, verbose: bool = False) -> read_edr_return_type:
     return all_energies, all_names, times
 
 
-def edr_to_df(path: str, verbose: bool = False):
-    """Calls :func:`read_edr` and packs its return values into a DataFrame
-
-    This function has a pandas dependency. Installing panedrlite instead of
-    panedr will not automatically install pandas. If you want to use this
-    function, please install pandas or consider installing panedr instead.
-
-    Parameters
-    ----------
-    path : str
-        path to EDR file to be read
-    verbose : bool
-        Optionally show verbose output while reading the file
-
-    Returns
-    -------
-    df: pandas.DataFrame
-        :class:`pandas.DataFrame()` object that holds all energy terms found in
-        the EDR file.
-        """
-    try:
-        import pandas as pd
-    except ImportError:
-        raise ImportError("""ERROR --- pandas was not found!
-                          pandas is required to use the `.edr_to_df()`
-                          functionality. Try installing it using pip, e.g.:
-                          python -m pip install pandas""")
-    all_energies, all_names, times = read_edr(path, verbose=verbose)
-    df = pd.DataFrame(all_energies, columns=all_names, index=times)
-    return df
-
-
 def edr_to_dict(path: str, verbose: bool = False) -> Dict[str, np.ndarray]:
     """Calls :func:`read_edr` and packs its return values into a dictionary
 
diff --git a/panedrlite/requirements.txt b/pyedr/requirements.txt
similarity index 100%
rename from panedrlite/requirements.txt
rename to pyedr/requirements.txt
diff --git a/panedrlite/setup.cfg b/pyedr/setup.cfg
similarity index 79%
rename from panedrlite/setup.cfg
rename to pyedr/setup.cfg
index 8c6ecdb..c197f86 100644
--- a/panedrlite/setup.cfg
+++ b/pyedr/setup.cfg
@@ -1,14 +1,14 @@
 [metadata]
-name = panedrlite
+name = pyedr
 author = Jonathan Barnoud
 author_email = jonathan@barnoud.net
 summary = Read and manipulate Gromacs energy files
-license = LGPL
+license = LGPLv2+
+license_file = ../LICENSE.txt
 description_file =
     ../README.rst
 long_description_content_type = text/x-rst
-home_page = https://github.com/MDAnalysis/panedr
-python_requires = >=3.6
+url = https://github.com/MDAnalysis/panedr
 classifier =
     Development Status :: 4 - Beta
     Intended Audience :: Developers
@@ -23,9 +23,12 @@ classifier =
     Programming Language :: Python :: 3.10
     Operating System :: OS Independent
 
-[extras]
+[options]
+python_requires = >= 3.6
+install_requires =
+    numpy
+    pbr
+
+[options.extras_require]
 test =
-    six
     pytest
-pandas =
-    pandas
diff --git a/pyedr/setup.py b/pyedr/setup.py
new file mode 100755
index 0000000..b43ea73
--- /dev/null
+++ b/pyedr/setup.py
@@ -0,0 +1,7 @@
+from setuptools import setup
+
+setup(name="pyedr",
+      setup_requires=['pbr'],
+      pbr=True,
+      include_package_data=True,
+)


Test patch:
diff --git a/tests/__init__.py b/panedr/panedr/tests/__init__.py
similarity index 100%
rename from tests/__init__.py
rename to panedr/panedr/tests/__init__.py
diff --git a/panedr/panedr/tests/test_edr.py b/panedr/panedr/tests/test_edr.py
new file mode 100644
index 0000000..3f81776
--- /dev/null
+++ b/panedr/panedr/tests/test_edr.py
@@ -0,0 +1,151 @@
+#-*- coding: utf-8 -*-
+
+"""
+Tests for panedr
+"""
+
+import sys
+import unittest
+import pytest
+import contextlib
+import re
+from io import StringIO
+from collections import namedtuple
+from pathlib import Path
+
+import numpy as np
+from numpy.testing import assert_allclose
+import pandas
+import pyedr
+from pyedr.tests.test_edr import read_xvg, redirect_stderr
+from pyedr.tests.datafiles import (
+        EDR, EDR_XVG, EDR_IRREGULAR, EDR_IRREGULAR_XVG,
+        EDR_DOUBLE, EDR_DOUBLE_XVG, EDR_BLOCKS, EDR_BLOCKS_XVG
+)
+
+import panedr
+
+
+# Constants for XVG parsing
+COMMENT_PATTERN = re.compile(r'\s*[@#%&/]')
+LEGEND_PATTERN = re.compile(r'@\s+s\d+\s+legend\s+"(.*)"')
+NDEC_PATTERN = re.compile(r'[\.eE]')
+
+# Data constants
+EDR_Data = namedtuple('EDR_Data', ['df', 'edr_dict', 'xvgdata', 'xvgtime',
+                                   'xvgnames', 'xvgcols', 'xvgprec', 'edrfile',
+                                   'xvgfile'])
+
+
+@pytest.fixture(scope='module',
+                params=[(EDR, EDR_XVG),
+                        (EDR_IRREGULAR, EDR_IRREGULAR_XVG),
+                        (EDR_DOUBLE, EDR_DOUBLE_XVG),
+                        (EDR_BLOCKS, EDR_BLOCKS_XVG),
+                        (Path(EDR), EDR_XVG),])
+def edr(request):
+    edrfile, xvgfile = request.param
+    df = panedr.edr_to_df(edrfile)
+    edr_dict = pyedr.edr_to_dict(edrfile)
+    xvgdata, xvgnames, xvgprec = read_xvg(xvgfile)
+    xvgtime = xvgdata[:, 0]
+    xvgdata = xvgdata[:, 1:]
+    xvgcols = np.insert(xvgnames, 0, u'Time')
+    return EDR_Data(df, edr_dict, xvgdata, xvgtime, xvgnames,
+                    xvgcols, xvgprec, edrfile, xvgfile)
+
+
+class TestEdrToDf(object):
+    """
+    Tests for :fun:`panedr.edr_to_df`.
+    """
+    def test_output_type(self, edr):
+        """
+        Test that the function returns a pandas DataFrame.
+        """
+        assert isinstance(edr.df, pandas.DataFrame)
+
+    def test_columns(self, edr):
+        """
+        Test that the column names and order match.
+        """
+        columns = edr.df.columns.values
+        if columns.shape[0] == edr.xvgcols.shape[0]:
+            print('These columns differ from the reference (displayed as read):')
+            print(columns[edr.xvgcols != columns])
+            print('The corresponding names displayed as reference:')
+            print(edr.xvgcols[edr.xvgcols != columns])
+        assert edr.xvgcols.shape == columns.shape, \
+               'The number of columns read is unexpected.'
+        assert np.all(edr.xvgcols == columns), \
+               'At least one column name was misread.'
+
+    def test_times(self, edr):
+        """
+        Test that the time is read correctly when dt is regular.
+        """
+        time = edr.df[u'Time'].values
+        assert_allclose(edr.xvgtime, time, atol=5e-7)
+
+    def test_content(self, edr):
+        """
+        Test that the content of the DataFrame is the expected one.
+        """
+        content = edr.df.iloc[:, 1:].values
+        print(edr.xvgdata - content)
+        assert_allclose(edr.xvgdata, content, atol=edr.xvgprec/2)
+
+    def test_verbosity(self):
+        """
+        Make sure the verbose mode does not alter the results.
+        """
+        with redirect_stderr(sys.stdout):
+            df = panedr.edr_to_df(EDR, verbose=True)
+        ref_content, _, prec = read_xvg(EDR_XVG)
+        content = df.values
+        print(ref_content - content)
+        assert_allclose(ref_content, content, atol=prec/2)
+
+    def test_progress(self):
+        """
+        Test the progress meter displays what is expected.
+        """
+        output = StringIO()
+        with redirect_stderr(output):
+            df = panedr.edr_to_df(EDR, verbose=True)
+        progress = output.getvalue().split('\n')[0].split('\r')
+        print(progress)
+        dt = 2000.0
+        # We can already iterate on `progress`, but I want to keep the cursor
+        # position from one for loop to the other.
+        progress_iter = iter(progress)
+        assert '' == next(progress_iter)
+        self._assert_progress_range(progress_iter, dt, 0, 21, 1)
+        self._assert_progress_range(progress_iter, dt, 30, 201, 10)
+        self._assert_progress_range(progress_iter, dt, 300, 2001, 100)
+        self._assert_progress_range(progress_iter, dt, 3000, 14101, 1000)
+        # Check the last line
+        print(df.iloc[-1, 0])
+        ref_line = 'Last Frame read : 14099, time : 28198000.0 ps'
+        last_line = next(progress_iter)
+        assert ref_line == last_line
+        # Did we leave stderr clean with a nice new line at the end?
+        assert output.getvalue().endswith('\n'), \
+               'New line missing at the end of output.'
+
+    def _assert_progress_range(self, progress, dt, start, stop, step):
+        for frame_idx in range(start, stop, step):
+            ref_line = 'Read frame : {},  time : {} ps'.format(frame_idx,
+                                                               dt * frame_idx)
+            progress_line = next(progress)
+            print(frame_idx, progress_line)
+            assert ref_line == progress_line
+
+    def test_edr_dict_to_df_match(self, edr):
+        array_df = pandas.DataFrame.from_dict(edr.edr_dict).set_index(
+                "Time", drop=False)
+        assert array_df.equals(edr.df)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/pyedr/pyedr/tests/__init__.py b/pyedr/pyedr/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/data/blocks.edr b/pyedr/pyedr/tests/data/blocks.edr
similarity index 100%
rename from tests/data/blocks.edr
rename to pyedr/pyedr/tests/data/blocks.edr
diff --git a/tests/data/blocks.xvg b/pyedr/pyedr/tests/data/blocks.xvg
similarity index 100%
rename from tests/data/blocks.xvg
rename to pyedr/pyedr/tests/data/blocks.xvg
diff --git a/tests/data/cat.edr b/pyedr/pyedr/tests/data/cat.edr
similarity index 100%
rename from tests/data/cat.edr
rename to pyedr/pyedr/tests/data/cat.edr
diff --git a/tests/data/cat.xvg b/pyedr/pyedr/tests/data/cat.xvg
similarity index 100%
rename from tests/data/cat.xvg
rename to pyedr/pyedr/tests/data/cat.xvg
diff --git a/tests/data/double.edr b/pyedr/pyedr/tests/data/double.edr
similarity index 100%
rename from tests/data/double.edr
rename to pyedr/pyedr/tests/data/double.edr
diff --git a/tests/data/double.xvg b/pyedr/pyedr/tests/data/double.xvg
similarity index 100%
rename from tests/data/double.xvg
rename to pyedr/pyedr/tests/data/double.xvg
diff --git a/tests/data/irregular.edr b/pyedr/pyedr/tests/data/irregular.edr
similarity index 100%
rename from tests/data/irregular.edr
rename to pyedr/pyedr/tests/data/irregular.edr
diff --git a/tests/data/irregular.xvg b/pyedr/pyedr/tests/data/irregular.xvg
similarity index 100%
rename from tests/data/irregular.xvg
rename to pyedr/pyedr/tests/data/irregular.xvg
diff --git a/pyedr/pyedr/tests/datafiles.py b/pyedr/pyedr/tests/datafiles.py
new file mode 100644
index 0000000..c51cae2
--- /dev/null
+++ b/pyedr/pyedr/tests/datafiles.py
@@ -0,0 +1,41 @@
+#-*- coding:utf-8 -*-
+# PyEDR -- a library to manipulate Gromacs EDR file in python
+# Copyright (C) 2022  Jonathan Barnoud
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA  02110-1301  USA
+
+__all__ = [
+    "EDR", "EDR_XVG",  # all EDR fields read with ``gmx energy``
+    "EDR_IRREGULAR", "EDR_IRREGULAR_XVG",
+    "EDR_DOUBLE", "EDR_DOUBLE_XVG",
+    "EDR_BLOCKS", "EDR_BLOCKS_XVG",
+]
+
+# Move to impportlib.resources when py>=3.7
+from pkg_resources import resource_filename
+
+
+EDR = resource_filename(__name__, 'data/cat.edr')
+EDR_XVG = resource_filename(__name__, 'data/cat.xvg')
+
+EDR_IRREGULAR = resource_filename(__name__, 'data/irregular.edr')
+EDR_IRREGULAR_XVG = resource_filename(__name__, 'data/irregular.xvg')
+
+EDR_DOUBLE = resource_filename(__name__, 'data/double.edr')
+EDR_DOUBLE_XVG = resource_filename(__name__, 'data/double.xvg')
+
+EDR_BLOCKS = resource_filename(__name__, 'data/blocks.edr')
+EDR_BLOCKS_XVG = resource_filename(__name__, 'data/blocks.xvg')
diff --git a/tests/test_edr.py b/pyedr/pyedr/tests/test_edr.py
similarity index 61%
rename from tests/test_edr.py
rename to pyedr/pyedr/tests/test_edr.py
index c17cdcf..befb729 100644
--- a/tests/test_edr.py
+++ b/pyedr/pyedr/tests/test_edr.py
@@ -1,24 +1,26 @@
 #-*- coding: utf-8 -*-
 
 """
-Tests for panedr
+Tests for pyedr
 """
-
-from __future__ import print_function, division
-
-import six
-import os
-import sys
-import unittest
-import pytest
+from collections import namedtuple
 import contextlib
-import numpy
-import pandas
-import panedrlite as panedr
-import re
 from io import StringIO
-from collections import namedtuple
 from pathlib import Path
+import re
+import sys
+import unittest
+
+import pytest
+import numpy as np
+from numpy.testing import assert_allclose
+
+import pyedr
+from pyedr.tests.datafiles import (
+        EDR, EDR_XVG, EDR_IRREGULAR, EDR_IRREGULAR_XVG,
+        EDR_DOUBLE, EDR_DOUBLE_XVG, EDR_BLOCKS, EDR_BLOCKS_XVG
+)
+
 
 # Constants for XVG parsing
 COMMENT_PATTERN = re.compile(r'\s*[@#%&/]')
@@ -26,29 +28,9 @@
 NDEC_PATTERN = re.compile(r'[\.eE]')
 
 # Data constants
-DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
-EDR = os.path.join(DATA_DIR, 'cat.edr')
-EDR_XVG = os.path.join(DATA_DIR, 'cat.xvg')  # All EDR fields read with
-                                             # ``gmx energy``
-EDR_IRREGULAR = os.path.join(DATA_DIR, 'irregular.edr')
-EDR_IRREGULAR_XVG = os.path.join(DATA_DIR, 'irregular.xvg')
-
-EDR_DOUBLE = os.path.join(DATA_DIR, 'double.edr')
-EDR_DOUBLE_XVG = os.path.join(DATA_DIR, 'double.xvg')
-
-EDR_BLOCKS = os.path.join(DATA_DIR, 'blocks.edr')
-EDR_BLOCKS_XVG = os.path.join(DATA_DIR, 'blocks.xvg')
-
-EDR_Data = namedtuple('EDR_Data', ['df', 'xvgdata', 'xvgtime', 'xvgnames',
-                                   'xvgprec', 'edrfile', 'xvgfile'])
-
-
-def test_failed_import(monkeypatch):
-    # Putting this test first to avoid datafiles already being loaded
-    errmsg = "ERROR --- pandas was not found!"
-    monkeypatch.setitem(sys.modules, 'pandas', None)
-    with pytest.raises(ImportError, match=errmsg):
-        panedr.edr_to_df(EDR)
+EDR_Data = namedtuple('EDR_Data', 
+                      ['edr_dict', 'xvgdata', 'xvgtime', 'xvgnames',
+                       'xvgcols', 'xvgprec', 'edrfile', 'xvgfile'])
 
 
 @pytest.fixture(scope='module',
@@ -56,68 +38,67 @@ def test_failed_import(monkeypatch):
                         (EDR_IRREGULAR, EDR_IRREGULAR_XVG),
                         (EDR_DOUBLE, EDR_DOUBLE_XVG),
                         (EDR_BLOCKS, EDR_BLOCKS_XVG),
-                        (Path(EDR), EDR_XVG),
-                       ])
+                        (Path(EDR), EDR_XVG),])
 def edr(request):
     edrfile, xvgfile = request.param
-    df = panedr.edr_to_df(edrfile)
+    edr_dict = pyedr.edr_to_dict(edrfile)
     xvgdata, xvgnames, xvgprec = read_xvg(xvgfile)
     xvgtime = xvgdata[:, 0]
     xvgdata = xvgdata[:, 1:]
-    return EDR_Data(df, xvgdata, xvgtime, xvgnames, xvgprec, edrfile, xvgfile)
+    xvgcols = np.insert(xvgnames, 0, u'Time')
+    return EDR_Data(edr_dict, xvgdata, xvgtime, xvgnames,
+                    xvgcols, xvgprec, edrfile, xvgfile)
 
 
-class TestEdrToDf(object):
+class TestEdrToDict(object):
     """
-    Tests for :fun:`panedr.edr_to_df`.
+    Tests for :fun:`pyedr.edr_to_dict`.
     """
     def test_output_type(self, edr):
         """
-        Test that the function returns a pandas DataFrame.
+        Test that the function returns a dictionary of ndarrays
         """
-        assert isinstance(edr.df, pandas.DataFrame)
+        assert isinstance(edr.edr_dict, dict)
+        assert isinstance(edr.edr_dict['Time'], np.ndarray)
 
     def test_columns(self, edr):
         """
-        Test that the column names and order match.
+        Test that the dictionary names match
         """
-        ref_columns = numpy.insert(edr.xvgnames, 0, u'Time')
-        columns = edr.df.columns.values
-        if columns.shape[0] == ref_columns.shape[0]:
-            print('These columns differ from the reference (displayed as read):')
-            print(columns[ref_columns != columns])
-            print('The corresponding names displayed as reference:')
-            print(ref_columns[ref_columns != columns])
-        assert ref_columns.shape == columns.shape, \
-               'The number of columns read is unexpected.'
-        assert numpy.all(ref_columns == columns), \
-               'At least one column name was misread.'
+        errmsg = "mistmatch in number of colums read"
+        assert edr.xvgcols.shape[0] == len(edr.edr_dict), errmsg
+
+        for ref, val in zip(edr.xvgcols, edr.edr_dict.keys()):
+            assert ref == val, "mismatching column entries"
 
     def test_times(self, edr):
         """
         Test that the time is read correctly when dt is regular.
         """
-        time = edr.df[u'Time'].values
-        assert numpy.allclose(edr.xvgtime, time, atol=5e-7)
+        time = edr.edr_dict['Time']
+        assert_allclose(edr.xvgtime, time)
 
     def test_content(self, edr):
         """
         Test that the content of the DataFrame is the expected one.
         """
-        content = edr.df.iloc[:, 1:].values
-        print(edr.xvgdata - content)
-        assert numpy.allclose(edr.xvgdata, content, atol=edr.xvgprec/2)
+        for i, key in enumerate(edr.xvgcols):
+            # already tested under `test_times`
+            if key != "Time":
+                assert_allclose(edr.xvgdata[:, i-1], edr.edr_dict[key],
+                                atol=edr.xvgprec/2)
 
     def test_verbosity(self):
         """
         Make sure the verbose mode does not alter the results.
         """
         with redirect_stderr(sys.stdout):
-            df = panedr.edr_to_df(EDR, verbose=True)
+            edr_dict = pyedr.edr_to_dict(EDR, verbose=True)
         ref_content, _, prec = read_xvg(EDR_XVG)
-        content = df.values
-        print(ref_content - content)
-        assert numpy.allclose(ref_content, content, atol=prec/2)
+
+        for i, key in enumerate(edr_dict.keys()):
+            assert_allclose(ref_content[:, i], edr_dict[key],
+                            atol=prec/2)
 
     def test_progress(self):
         """
@@ -125,7 +106,7 @@ def test_progress(self):
         """
         output = StringIO()
         with redirect_stderr(output):
-            df = panedr.edr_to_df(EDR, verbose=True)
+            edr_dict = pyedr.edr_to_dict(EDR, verbose=True)
         progress = output.getvalue().split('\n')[0].split('\r')
         print(progress)
         dt = 2000.0
@@ -138,7 +119,6 @@ def test_progress(self):
         self._assert_progress_range(progress_iter, dt, 300, 2001, 100)
         self._assert_progress_range(progress_iter, dt, 3000, 14101, 1000)
         # Check the last line
-        print(df.iloc[-1, 0])
         ref_line = 'Last Frame read : 14099, time : 28198000.0 ps'
         last_line = next(progress_iter)
         assert ref_line == last_line
@@ -155,14 +135,6 @@ def _assert_progress_range(self, progress, dt, start, stop, step):
             assert ref_line == progress_line
 
 
-def test_edr_to_dict_matches_edr_to_df():
-    array_dict = panedr.edr_to_dict(EDR)
-    ref_df = panedr.edr_to_df(EDR)
-    array_df = pandas.DataFrame.from_dict(array_dict).set_index(
-        "Time", drop=False)
-    assert array_df.equals(ref_df)
-
-
 def read_xvg(path):
     """
     Reads XVG file, returning the data, names, and precision.
@@ -192,14 +164,14 @@ def read_xvg(path):
                 continue
             match = re.match(LEGEND_PATTERN, line)
             if match:
-                names.append(six.text_type(match.groups()[0]))
+                names.append(str(match.groups()[0]))
     if prec <= 0:
         prec = 1.
     else:
         prec = 10**(-prec)
 
-    return (numpy.array(data, dtype=float),
-            numpy.array(names, dtype=object),
+    return (np.array(data, dtype=float),
+            np.array(names, dtype=object),
             prec)