Repository: optuna/optuna
Pull request: Remove dependency on pandas
Close #509

Problem statement:
Error on importing optuna in Python 3.5.2
When I tried to use optuna in Python 3.5.2, I was not able to import optuna due to the error related to `pandas`. Currently, `pandas=0.25.0` is installed with `pip install optuna`, but it supports `Python>=3.5.3`. 

Currently, `pandas` is only required by `Study.trials_dataframe()`, and I think `pandas` can be optional because we can optimize objective functions without it.

**Workaround**
I can use optuna with `pandas==0.24.2` in Python 3.5.2. So, please downgrade pandas as a workaround.

```console
$ pip uninstall pandas
$ pip install pandas==0.24.2
```

**Conditions**
- Optuna version: 0.15.0
- Python version: 3.5.2
- OS: macOS High Sierra
- Machine Learning library to be optimized: N/A

**Code to reproduce**

```console
$ python
>>> import optuna
```

**Error messages, stack traces, or logs**

```console
>>> import optuna
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/yanase/.pyenv/versions/optuna352/lib/python3.5/site-packages/optuna/__init__.py", line 2, in <module>
    from optuna import dashboard  # NOQA
  File "/Users/yanase/.pyenv/versions/optuna352/lib/python3.5/site-packages/optuna/dashboard.py", line 22, in <module>
    import optuna.study
  File "/Users/yanase/.pyenv/versions/optuna352/lib/python3.5/site-packages/optuna/study.py", line 7, in <module>
    import pandas as pd
  File "/Users/yanase/.pyenv/versions/optuna352/lib/python3.5/site-packages/pandas/__init__.py", line 55, in <module>
    from pandas.core.api import (
  File "/Users/yanase/.pyenv/versions/optuna352/lib/python3.5/site-packages/pandas/core/api.py", line 5, in <module>
...
  File "/Users/yanase/.pyenv/versions/3.5.2/lib/python3.5/abc.py", line 225, in __subclasscheck__
    for scls in cls.__subclasses__():
TypeError: descriptor '__subclasses__' of 'type' object needs an argument
```

Hints from issue discussion:

Commit message: Merge remote-tracking branch 'upstream/master' into remove_dependency_pandas

Patch to review:
diff --git a/optuna/study.py b/optuna/study.py
index b007e18222c..5afc8bf0e6a 100644
--- a/optuna/study.py
+++ b/optuna/study.py
@@ -4,7 +4,15 @@
 import math
 import multiprocessing
 import multiprocessing.pool
-import pandas as pd
+
+try:
+    import pandas as pd  # NOQA
+    _available = True
+except ImportError as e:
+    _import_error = e
+    # trials_dataframe is disabled because pandas is not available.
+    _available = False
+
 from six.moves import queue
 import threading
 import time
@@ -322,6 +330,7 @@ def trials_dataframe(self, include_internal_fields=False):
         .. _DataFrame: http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html
         .. _MultiIndex: https://pandas.pydata.org/pandas-docs/stable/advanced.html
         """
+        _check_pandas_availability()
 
         # column_agg is an aggregator of column names.
         # Keys of column agg are attributes of FrozenTrial such as 'trial_id' and 'params'.
@@ -665,3 +674,14 @@ def get_all_study_summaries(storage):
 
     storage = storages.get_storage(storage)
     return storage.get_all_study_summaries()
+
+
+def _check_pandas_availability():
+    # type: () -> None
+
+    if not _available:
+        raise ImportError(
+            'pandas is not available. Please install pandas to use this feature. '
+            'pandas can be installed by executing `$ pip install pandas`. '
+            'For further information, please refer to the installation guide of pandas. '
+            '(The actual import error is as follows: ' + str(_import_error) + ')')
diff --git a/setup.py b/setup.py
index eb5198abf0c..2fea868ac7e 100644
--- a/setup.py
+++ b/setup.py
@@ -40,7 +40,7 @@ def get_install_requires():
     # type: () -> List[str]
 
     install_requires = [
-        'alembic', 'cliff', 'colorlog', 'numpy', 'pandas', 'scipy', 'six',
+        'alembic', 'cliff', 'colorlog', 'numpy', 'scipy', 'six',
         'sqlalchemy>=1.1.0', 'typing',
     ]
     if sys.version_info[0] == 2:
@@ -55,8 +55,8 @@ def get_extras_require():
         'checking': ['autopep8', 'hacking'],
         'testing': [
             'bokeh', 'chainer>=5.0.0', 'cma', 'keras', 'lightgbm', 'mock',
-            'mpi4py', 'mxnet', 'plotly>=4.0.0', 'pytest', 'scikit-optimize',
-            'tensorflow', 'xgboost', 'scikit-learn>=0.19.0',
+            'mpi4py', 'mxnet', 'pandas', 'plotly>=4.0.0', 'pytest',
+            'scikit-optimize', 'tensorflow', 'xgboost', 'scikit-learn>=0.19.0',
         ],
         'example': [
             'chainer', 'keras', 'catboost', 'lightgbm', 'scikit-learn',


Code review comments:
diff_hunk: @@ -4,7 +4,15 @@
 import math
 import multiprocessing
 import multiprocessing.pool
-import pandas as pd
+
+try:
+    import pandas as pd  # NOQA
+    _available = True
path: optuna/study.py
text: @user1:
I prefer the more specific variable name like `_pandas_available` to avoid conflicts in the module namespace. And I think following code is enough for this purposes:

```python
try:
    import pandas
except ImportError as e:
    pandas = None


def _check_pandas_availability():
    # type: () -> None

     if pandas is None:
         raise ImportError(...)
```


@author:
thanks!