Repository: statsmodels/statsmodels

Issue:
plot_partregress_grid function has a bug when use gird
#### Describe the bug
when I use grid param as 
**sm.graphics.plot_partregress_grid(result_wine, grid=(5,4), fig=fig)**
error occurs as 
"local variable 'title_kwargs' referenced before assignment"

so I looked up the code plot_partregress_grid function
```python
if grid is not None:
        nrows, ncols = grid
    else:
        if len(exog_idx) > 2:
            nrows = int(np.ceil(len(exog_idx)/2.))
            ncols = 2
            title_kwargs = {"fontdict" : {"fontsize" : 'small'}}
        else:
            nrows = len(exog_idx)
            ncols = 1
            title_kwargs = {}
```
if I put grid, the "title_kwargs" does not initialized.
so at the bottom, when plot_partregress called, it occurs error


#### Code Sample, a copy-pastable example if possible

```python
if grid is not None:
        nrows, ncols = grid
        title_kwargs = {"fontdict" : {"fontsize" : 'small'}}
    else:
        if len(exog_idx) > 2:
            nrows = int(np.ceil(len(exog_idx)/2.))
            ncols = 2
            title_kwargs = {"fontdict" : {"fontsize" : 'small'}}
        else:
            nrows = len(exog_idx)
            ncols = 1
            title_kwargs = {}

```
**Note**: As you can see, there are many issues on our GitHub tracker, so it is very possible that your issue has been posted before. Please check first before submitting so that we do not have to handle and close duplicates.

**Note**: Please be sure you are using the latest released version of `statsmodels`, or a recent build of `master`. If your problem has been fixed in an unreleased version, you might be able to use `master` until a new release occurs. 

**Note**: If you are using a released version, have you verified that the bug exists in the master branch of this repository? It helps the limited resources if we know problems exist in the current master so that they do not need to check whether the code sample produces a bug in the next release.


If the issue has not been resolved, please file it in the issue tracker.

#### Expected Output

A clear and concise description of what you expected to happen.

#### Output of ``import statsmodels.api as sm; sm.show_versions()``

<details>

[paste the output of ``sm.show_versions()`` here below this line]

</details>



Discussion:
Happy to accept a PR if you want to write one. If not, could you provide an example that shows the function being called that raises? This makes it easier to see the issue.
 Hi, 
So  i called 
sm.graphics.plot_partregress_grid(result_wine, grid=(5,4), fig=fig)
to see more plots in one row.(default is 2),

model_wine = sm.OLS(y, X)
result_wine = model_wine.fit()

this is the data i put into sm.graphics.plot_partregress_grid function.

Thank you.

> 2019. 6. 19. 오후 5:42, Kevin Sheppard <notifications@github.com> 작성:
> 
> Happy to accept a PR if you want to write one. If not, could you provide an example that shows the function being called that raises? This makes it easier to see the issue.
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub <https://github.com/statsmodels/statsmodels/issues/5873?email_source=notifications&email_token=AEHJ5UGZ43S5STLBTWFNT5TP3HWNZA5CNFSM4HZG7EC2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODYBETHY#issuecomment-503466399>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AEHJ5UEKMREU7BRNTHKIEL3P3HWNZANCNFSM4HZG7ECQ>.
> 


Could you make a self-contained example, ideally one that uses simulated data so that it can be copied and pasted to produce the bug? 

So This is the sample code that produce the bug

```
from sklearn.datasets import load_boston
import statsmodels.api as sm

boston = load_boston()

dfX0 = pd.DataFrame(boston.data, columns=boston.feature_names)
dfX = sm.add_constant(dfX0)
dfy = pd.DataFrame(boston.target, columns=["MEDV"])
df = pd.concat([dfX, dfy], axis=1)

model_boston = sm.OLS(dfy, dfX)
result_boston = model_boston.fit()

fig = plt.figure(figsize=(8, 20))
sm.graphics.plot_partregress_grid(result_boston, grid=(5,4), fig=fig)
fig.suptitle("")
plt.show()
```

I attached code in below

Thank you,

> 2019. 6. 19. 오후 6:00, Kevin Sheppard <notifications@github.com> 작성:
> 
> Could you make a self-contained example, ideally one that uses simulated data so that it can be copied and pasted to produce the bug?
> 
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub <https://github.com/statsmodels/statsmodels/issues/5873?email_source=notifications&email_token=AEHJ5UAFLFY5LOYNZ7GGLDLP3HYRRA5CNFSM4HZG7EC2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODYBGEWQ#issuecomment-503472730>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AEHJ5UBQMJUWILCIY2UXUNDP3HYRRANCNFSM4HZG7ECQ>.
> 


Resolving patch:
diff --git a/statsmodels/graphics/regressionplots.py b/statsmodels/graphics/regressionplots.py
--- a/statsmodels/graphics/regressionplots.py
+++ b/statsmodels/graphics/regressionplots.py
@@ -520,24 +520,19 @@ def plot_partregress_grid(results, exog_idx=None, grid=None, fig=None):
 
     exog_name, exog_idx = utils.maybe_name_or_idx(exog_idx, results.model)
 
-    #maybe add option for using wendog, wexog instead
+    # TODO: maybe add option for using wendog, wexog instead
     y = pandas.Series(results.model.endog, name=results.model.endog_names)
     exog = results.model.exog
 
     k_vars = exog.shape[1]
-    #this function doesn't make sense if k_vars=1
+    # this function doesn't make sense if k_vars=1
 
+    nrows = (len(exog_idx) + 1) // 2
+    ncols = 1 if nrows == len(exog_idx) else 2
     if grid is not None:
         nrows, ncols = grid
-    else:
-        if len(exog_idx) > 2:
-            nrows = int(np.ceil(len(exog_idx)/2.))
-            ncols = 2
-            title_kwargs = {"fontdict" : {"fontsize" : 'small'}}
-        else:
-            nrows = len(exog_idx)
-            ncols = 1
-            title_kwargs = {}
+    if ncols > 1:
+        title_kwargs = {"fontdict": {"fontsize": 'small'}}
 
     # for indexing purposes
     other_names = np.array(results.model.exog_names)
@@ -546,7 +541,7 @@ def plot_partregress_grid(results, exog_idx=None, grid=None, fig=None):
         others.pop(idx)
         exog_others = pandas.DataFrame(exog[:, others],
                                        columns=other_names[others])
-        ax = fig.add_subplot(nrows, ncols, i+1)
+        ax = fig.add_subplot(nrows, ncols, i + 1)
         plot_partregress(y, pandas.Series(exog[:, idx],
                                           name=other_names[idx]),
                          exog_others, ax=ax, title_kwargs=title_kwargs,
@@ -554,10 +549,9 @@ def plot_partregress_grid(results, exog_idx=None, grid=None, fig=None):
         ax.set_title("")
 
     fig.suptitle("Partial Regression Plot", fontsize="large")
-
     fig.tight_layout()
-
     fig.subplots_adjust(top=.95)
+
     return fig
 
 


Test patch:
diff --git a/statsmodels/graphics/tests/test_regressionplots.py b/statsmodels/graphics/tests/test_regressionplots.py
--- a/statsmodels/graphics/tests/test_regressionplots.py
+++ b/statsmodels/graphics/tests/test_regressionplots.py
@@ -28,21 +28,23 @@ def close_or_save(pdf, fig):
     if pdf_output:
         pdf.savefig(fig)
 
+
 class TestPlot(object):
 
-    def setup(self):
+    @classmethod
+    def setup_class(cls):
         nsample = 100
         sig = 0.5
         x1 = np.linspace(0, 20, nsample)
-        x2 = 5 + 3* np.random.randn(nsample)
-        X = np.c_[x1, x2, np.sin(0.5*x1), (x2-5)**2, np.ones(nsample)]
+        x2 = 5 + 3 * np.random.randn(nsample)
+        x = np.c_[x1, x2, np.sin(0.5 * x1), (x2 - 5) ** 2, np.ones(nsample)]
         beta = [0.5, 0.5, 1, -0.04, 5.]
-        y_true = np.dot(X, beta)
+        y_true = np.dot(x, beta)
         y = y_true + sig * np.random.normal(size=nsample)
         exog0 = sm.add_constant(np.c_[x1, x2], prepend=False)
-        res = sm.OLS(y, exog0).fit()
 
-        self.res = res
+        cls.res = sm.OLS(y, exog0).fit()
+        cls.res_true = sm.OLS(y, x).fit()
 
     @pytest.mark.matplotlib
     def test_plot_fit(self, close_figures):
@@ -66,10 +68,12 @@ def test_plot_fit(self, close_figures):
 
     @pytest.mark.matplotlib
     def test_plot_oth(self, close_figures):
-        #just test that they run
+        # just test that they run
         res = self.res
         plot_fit(res, 0, y_true=None)
-        plot_partregress_grid(res, exog_idx=[0,1])
+        plot_partregress_grid(res, exog_idx=[0, 1])
+        # GH 5873
+        plot_partregress_grid(self.res_true, grid=(2, 3))
         plot_regress_exog(res, exog_idx=0)
         plot_ccpr(res, exog_idx=0)
         plot_ccpr_grid(res, exog_idx=[0])