|
20 | 20 | "- [pandas](https://pandas.pydata.org/), for reading the data from a file;\n",
|
21 | 21 | "- [seaborn](https://seaborn.pydata.org/), for plotting the data;\n",
|
22 | 22 | "- [scipy.stats](https://docs.scipy.org/doc/scipy/reference/stats.html), for calculating correlation coefficients;\n",
|
23 |
| - "- [statsmodels.api](https://www.statsmodels.org/dev/index.html), for linear regression;\n", |
| 23 | + "- [pingouin](https://pingouin-stats.org/), for linear regression;\n", |
24 | 24 | "- [pathlib](https://docs.python.org/3/library/pathlib.html), for working with filesystem paths."
|
25 | 25 | ]
|
26 | 26 | },
|
|
34 | 34 | "import pandas as pd\n",
|
35 | 35 | "import seaborn as sns\n",
|
36 | 36 | "from scipy import stats\n",
|
37 |
| - "import statsmodels.api as sm\n", |
| 37 | + "import pingouin as pg\n", |
38 | 38 | "from pathlib import Path"
|
39 | 39 | ]
|
40 | 40 | },
|
|
73 | 73 | "metadata": {},
|
74 | 74 | "outputs": [],
|
75 | 75 | "source": [
|
| 76 | + "rain_tmax_plot = sns.lmplot(data=station_data, x='rain', y='tmax', hue='season', markers=['o', 'x', 's', '+'])\n", |
76 | 77 | "# your code goes here!\n",
|
77 | 78 | "rain_tmax_plot # show the plot"
|
78 | 79 | ]
|
|
204 | 205 | "print(f\"calculated p-value of r: {corr.pvalue}\")"
|
205 | 206 | ]
|
206 | 207 | },
|
| 208 | + { |
| 209 | + "cell_type": "markdown", |
| 210 | + "id": "6c4cb657-182d-4cbc-a022-45a595332a8f", |
| 211 | + "metadata": {}, |
| 212 | + "source": [ |
| 213 | + "And, using `pg.corr()` ([documentation](https://pingouin-stats.org/build/html/generated/pingouin.corr.html)) gives us even more information, such as the confidence interval for the correlation value, as well as additional options for calculating the correlation coefficient:" |
| 214 | + ] |
| 215 | + }, |
| 216 | + { |
| 217 | + "cell_type": "code", |
| 218 | + "execution_count": null, |
| 219 | + "id": "fa3e2280-bc03-4ad0-8ef7-7118775db44f", |
| 220 | + "metadata": {}, |
| 221 | + "outputs": [], |
| 222 | + "source": [ |
| 223 | + "# calculate the biweight midcorrelation between rain and tmax\n", |
| 224 | + "pg.corr(station_data.dropna(subset=['rain', 'tmax'])['rain'], \n", |
| 225 | + " station_data.dropna(subset=['rain', 'tmax'])['tmax'], method='bicor')" |
| 226 | + ] |
| 227 | + }, |
207 | 228 | {
|
208 | 229 | "cell_type": "markdown",
|
209 | 230 | "id": "318a6171-4a05-4136-93d0-196a5eff85e8",
|
|
233 | 254 | "\n",
|
234 | 255 | "$$ y = \\beta + \\alpha x, $$\n",
|
235 | 256 | "\n",
|
236 |
| - "where $\\beta$ is the intercept and $\\alpha$ is the slope of the line. To fit a linear model using ordinary least squares, we can first use `sm.OLS()` ([documentation](https://www.statsmodels.org/dev/generated/statsmodels.regression.linear_model.OLS.html)) to create an **OLS** object, then use the `.fit()` method ([documentation](https://www.statsmodels.org/dev/generated/statsmodels.regression.linear_model.OLS.fit.html)) of that object.\n", |
| 257 | + "where $\\beta$ is the intercept and $\\alpha$ is the slope of the line. To fit a linear model using `pingouin`, we use `pg.linear_regression()` ([documentation](https://pingouin-stats.org/build/html/generated/pingouin.linear_regression.html)). \n", |
237 | 258 | "\n",
|
238 |
| - "When we create the **OLS** object, we pass the observations of the *response* (*dependent*) variable with the first argument, and the observations of the *explanatory* (*independent*) variable(s) in the second argument. Note that by default, **OLS** will not fit a constant, but we can use `sm.add_constant()` ([documentation](https://www.statsmodels.org/dev/generated/statsmodels.tools.tools.add_constant.html)) to add a column of ones to the array.\n", |
| 259 | + "The main inputs to `pg.linear_regression()` are `X`, the observations of the *explanatory* (*independent*) variable(s), and `y`, the observations of the *response* (*dependent*) variables. We can also specify the significance level (`alpha`) to use when calculating the statistics of the fitted model, as well as additional arguments. By default, `pg.linear_regression()` adds an intercept to be fitted. \n", |
239 | 260 | "\n",
|
240 | 261 | "So, the process to fit a linear relationship between `tmax` and `rain` would look like this:"
|
241 | 262 | ]
|
|
247 | 268 | "metadata": {},
|
248 | 269 | "outputs": [],
|
249 | 270 | "source": [
|
250 |
| - "xdata = station_data.dropna(subset=['rain', 'tmax'])['rain'] # select the rain variable, after dropping NaN values\n", |
251 |
| - "ydata = station_data.dropna(subset=['rain', 'tmax'])['tmax'] # select the tmax variable, after dropping NaN values\n", |
| 271 | + "xdata = spring.dropna(subset=['rain', 'tmax'])['rain'] # select the rain variable, after dropping NaN values\n", |
| 272 | + "ydata = spring.dropna(subset=['rain', 'tmax'])['tmax'] # select the tmax variable, after dropping NaN values\n", |
252 | 273 | "\n",
|
253 |
| - "xdata = sm.add_constant(xdata) # add a constant to xdata - otherwise, we're only fitting the slope\n", |
| 274 | + "lin_model = pg.linear_regression(xdata, ydata, alpha=0.01) # run the regression at the 99% significance level\n", |
254 | 275 | "\n",
|
255 |
| - "lin_model = sm.OLS(ydata, xdata) # initialize the OLS object\n", |
256 |
| - "lm_results = lin_model.fit() # fit the model to the data" |
257 |
| - ] |
258 |
| - }, |
259 |
| - { |
260 |
| - "cell_type": "markdown", |
261 |
| - "id": "bda01e81-8b28-45d9-9324-0f6f45b0b395", |
262 |
| - "metadata": {}, |
263 |
| - "source": [ |
264 |
| - "The `params` attribute has the estimated values for the intercept (`const`) and slope (`rain`):" |
265 |
| - ] |
266 |
| - }, |
267 |
| - { |
268 |
| - "cell_type": "code", |
269 |
| - "execution_count": null, |
270 |
| - "id": "9b18194d-37c8-4672-abd4-80661309c135", |
271 |
| - "metadata": {}, |
272 |
| - "outputs": [], |
273 |
| - "source": [ |
274 |
| - "lm_results.params # see the regression parameters: const is the intercept, rain is the coefficient for 'rain'" |
| 276 | + "lin_model.round(3) # round the output table to 3 decimal places" |
275 | 277 | ]
|
276 | 278 | },
|
277 | 279 | {
|
278 | 280 | "cell_type": "markdown",
|
279 |
| - "id": "0106f89d-bf97-409d-90c3-22f21d70235e", |
| 281 | + "id": "ed86c6dc-ace9-4c3a-81c3-fe8457b03e2a", |
280 | 282 | "metadata": {},
|
281 | 283 | "source": [
|
282 |
| - "Other useful attributes include:\n", |
| 284 | + "The output of `pg.linear_regression()` is a **DataFrame** with the following columns:\n", |
283 | 285 | "\n",
|
284 |
| - "- `bse`, the estimates of the standard error for the parameters;\n", |
285 |
| - "- `pvalues`, the two-tailed *p*-values for the *t*-statistics of the parameter estimates;\n", |
286 |
| - "- `resid`, the model residuals;\n", |
287 |
| - "- `rsquared` and `rsquared_adj`, the R-squared and adjusted R-squared values for the model.\n", |
| 286 | + "- `names`: the names of the outputs (`intercept`) and the slope for each explanatory variable;\n", |
| 287 | + "- `coef`: the values of the regression coefficients;\n", |
| 288 | + "- `se`: the standard error of the estimated coefficients;\n", |
| 289 | + "- `T`: the *t*-statistic of the estimates;\n", |
| 290 | + "- `pval`: the *p*-values of the *t*-statistics;\n", |
| 291 | + "- `r2`: the coefficient of determination;\n", |
| 292 | + "- `adj_r2`: the adjusted coefficient of determination;\n", |
| 293 | + "- `CI{alpha/2}%`: the lower value of the confidence interval;\n", |
| 294 | + "- `CI{1-alpha/2}%`: the upper value of the confidence interval;\n", |
| 295 | + "- `relimp`: the relative contribution of each predictor to the final (if `relimp=True`);\n", |
| 296 | + "- `relimp_perc`: the percent relative contribution\n", |
288 | 297 | "\n",
|
289 |
| - "Note that each of these attributes are **pandas.Series**:" |
290 |
| - ] |
291 |
| - }, |
292 |
| - { |
293 |
| - "cell_type": "code", |
294 |
| - "execution_count": null, |
295 |
| - "id": "301b2c1e-350c-4201-973e-eb26f64260c2", |
296 |
| - "metadata": {}, |
297 |
| - "outputs": [], |
298 |
| - "source": [ |
299 |
| - "type(lm_results.bse) # show the type of lm_results.bse" |
300 |
| - ] |
301 |
| - }, |
302 |
| - { |
303 |
| - "cell_type": "markdown", |
304 |
| - "id": "5a6e076d-a45d-4887-bbd8-6636035c992d", |
305 |
| - "metadata": {}, |
306 |
| - "source": [ |
307 |
| - "This means that we can easily combine these into a **DataFrame** using `pd.concat()`:" |
308 |
| - ] |
309 |
| - }, |
310 |
| - { |
311 |
| - "cell_type": "code", |
312 |
| - "execution_count": null, |
313 |
| - "id": "bfc83cfc-3f20-4e86-9ed2-b8c0621c2430", |
314 |
| - "metadata": {}, |
315 |
| - "outputs": [], |
316 |
| - "source": [ |
317 |
| - "res_df = pd.concat([lm_results.params, lm_results.bse, lm_results.tvalues, lm_results.pvalues, lm_results.conf_int()], axis=1) # join params, bse, tvalues, pvalues, confidence intervals along the column axis\n", |
318 |
| - "res_df.columns = ['coef', 'std err', 't-value', 'p-value', 'ci_low', 'ci_up'] # set the column names\n", |
| 298 | + "The ouptut **DataFrame** also has hidden attributes such as the residuals (`lin_model.residuals_`), the degrees of freedom of the model (`lin_model.df_model_`), and the degrees of freedom of the residuals (`lin_model.df_resid_`).\n", |
319 | 299 | "\n",
|
320 |
| - "res_df # show the dataframe" |
321 |
| - ] |
322 |
| - }, |
323 |
| - { |
324 |
| - "cell_type": "markdown", |
325 |
| - "id": "debcd1ce-377f-4690-a237-4d392f6cbbb9", |
326 |
| - "metadata": {}, |
327 |
| - "source": [ |
328 |
| - "To get the full summary of the regression results, use `.summary()` ([documentation](https://www.statsmodels.org/dev/generated/statsmodels.regression.linear_model.RegressionResults.summary.html)):" |
329 |
| - ] |
330 |
| - }, |
331 |
| - { |
332 |
| - "cell_type": "code", |
333 |
| - "execution_count": null, |
334 |
| - "id": "16b5879d-8162-404e-a03a-344914cde420", |
335 |
| - "metadata": {}, |
336 |
| - "outputs": [], |
337 |
| - "source": [ |
338 |
| - "lm_results.summary() # show the summary of the results" |
339 |
| - ] |
340 |
| - }, |
341 |
| - { |
342 |
| - "cell_type": "markdown", |
343 |
| - "id": "ed86c6dc-ace9-4c3a-81c3-fe8457b03e2a", |
344 |
| - "metadata": {}, |
345 |
| - "source": [ |
346 | 300 | "## multiple linear regression\n",
|
347 | 301 | "\n",
|
348 | 302 | "Now, let's try to fit a linear model of `tmax` with two variables: `rain` and `sun`. Remember that multiple linear regression tries to fit a model with the form:\n",
|
|
353 | 307 | "\n",
|
354 | 308 | "$$ y = \\beta + \\alpha_1 x_1 + \\alpha_2 x_2 $$\n",
|
355 | 309 | "\n",
|
356 |
| - "The code to fit this model using `statsmodels` looks like this:" |
| 310 | + "The code to fit this model using `pingouin` looks like this:" |
357 | 311 | ]
|
358 | 312 | },
|
359 | 313 | {
|
|
366 | 320 | "xdata = station_data.dropna(subset=['rain', 'tmax', 'sun'])[['rain', 'sun']] # select the rain and sun variables, after dropping NaN values\n",
|
367 | 321 | "ydata = station_data.dropna(subset=['rain', 'tmax', 'sun'])['tmax'] # select the tmax variable, after dropping NaN values\n",
|
368 | 322 | "\n",
|
369 |
| - "xdata = sm.add_constant(xdata) # add a constant to xdata - otherwise, we're only fitting the slope\n", |
| 323 | + "ml_model = pg.linear_regression(xdata, ydata, alpha=0.01) # run the regression at the 99% significance level\n", |
370 | 324 | "\n",
|
371 |
| - "ml_model = sm.OLS(ydata, xdata) # initialize the OLS object\n", |
372 |
| - "mlm_results = ml_model.fit() # fit the model to the data\n", |
373 |
| - "\n", |
374 |
| - "mlm_results.params # see the regression parameters: const is the intercept, rain is the coefficient for 'rain'" |
375 |
| - ] |
376 |
| - }, |
377 |
| - { |
378 |
| - "cell_type": "markdown", |
379 |
| - "id": "e43f2037-dd88-4f8d-baa6-f65cfd08e6ef", |
380 |
| - "metadata": {}, |
381 |
| - "source": [ |
382 |
| - "Just as with the simple linear regression case, we can look at the summary of the regression results:" |
383 |
| - ] |
384 |
| - }, |
385 |
| - { |
386 |
| - "cell_type": "code", |
387 |
| - "execution_count": null, |
388 |
| - "id": "7385af31-d0b5-42ab-b80d-0b1175f71a7b", |
389 |
| - "metadata": {}, |
390 |
| - "outputs": [], |
391 |
| - "source": [ |
392 |
| - "mlm_results.summary() # show the summary of the results" |
| 325 | + "ml_model.round(3) # round the output table to 3 decimal places" |
393 | 326 | ]
|
394 | 327 | },
|
395 | 328 | {
|
|
419 | 352 | " xdata = season_data['rain'] # select the rain variable\n",
|
420 | 353 | " ydata = season_data['tmax'] # select the tmax variable\n",
|
421 | 354 | " \n",
|
422 |
| - " xdata = sm.add_constant(xdata) # add a constant to xdata - otherwise, we're only fitting the slope\n", |
423 |
| - " \n", |
424 |
| - " model = sm.OLS(ydata, xdata) # initialize the OLS object\n", |
425 |
| - " results[season] = model.fit() # add the result to the results dict, with season as the key" |
| 355 | + " results[season] = pg.linear_regression(xdata, ydata, alpha=0.01) # add the result to the results dict, with season as the key" |
426 | 356 | ]
|
427 | 357 | },
|
428 | 358 | {
|
|
440 | 370 | "metadata": {},
|
441 | 371 | "outputs": [],
|
442 | 372 | "source": [
|
443 |
| - "results['spring'].summary() # view the summary for spring" |
444 |
| - ] |
445 |
| - }, |
446 |
| - { |
447 |
| - "cell_type": "markdown", |
448 |
| - "id": "cc0a971a-fc7d-458c-b2ea-9053ab907980", |
449 |
| - "metadata": {}, |
450 |
| - "source": [ |
451 |
| - "Next, let's see how we can combine these results into a single **DataFrame**. First, we'll write a **function** to create the **DataFrame** for a single model result - as we have discussed, it is often preferable to write functions for repeated lines of code, as it can make the code more readable, it helps avoid mistakes, and also because programmers are often lazy.\n", |
452 |
| - "\n", |
453 |
| - "Run the next cell to define the function - the only new bits of code here are the use of `.reset_index()` ([documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.reset_index.html)), which will turn the current index parameter names into a column, `index`, and the use of `.rename()` to rename this column from `index` to `parameter`. The reason for doing this will be clear in a moment." |
454 |
| - ] |
455 |
| - }, |
456 |
| - { |
457 |
| - "cell_type": "code", |
458 |
| - "execution_count": null, |
459 |
| - "id": "c4311d9a-afdf-4d14-9baf-fdefb3441e65", |
460 |
| - "metadata": {}, |
461 |
| - "outputs": [], |
462 |
| - "source": [ |
463 |
| - "def get_results_df(res):\n", |
464 |
| - " res_df = pd.concat([res.params, res.bse, res.tvalues, res.pvalues, res.conf_int()], axis=1) # join params, bse, tvalues, pvalues, confidence intervals along the column axis\n", |
465 |
| - " res_df.columns = ['coef', 'std err', 't-value', 'p-value', 'ci_low', 'ci_up'] # set the column names\n", |
466 |
| - " res_df.reset_index(inplace=True) # unset the index in-place\n", |
467 |
| - " \n", |
468 |
| - " return res_df.rename(columns={'index': 'parameter'}) # return the dataframe with 'index' renamed to 'parameter'" |
| 373 | + "results['spring'] # view the results for spring" |
469 | 374 | ]
|
470 | 375 | },
|
471 | 376 | {
|
472 | 377 | "cell_type": "markdown",
|
473 |
| - "id": "95d0940c-1166-462c-add0-c207e43591ae", |
| 378 | + "id": "84d78003-02f3-4a3f-8f96-967ba800647b", |
474 | 379 | "metadata": {},
|
475 | 380 | "source": [
|
476 |
| - "Now, we can loop over the season names to get the parameter table for each season, then use `pd.concat()` to combine these results into a single **DataFrame**:" |
| 381 | + "Now, let's see how we can combine these results into a single **DataFrame**. First, we'll add a column, `season`, to each **DataFrame**:" |
477 | 382 | ]
|
478 | 383 | },
|
479 | 384 | {
|
480 | 385 | "cell_type": "code",
|
481 | 386 | "execution_count": null,
|
482 |
| - "id": "7545bfd8-7804-4ea6-a472-ce4c5d810017", |
| 387 | + "id": "2cecf29d-452f-4ced-ac00-f5f88677826d", |
483 | 388 | "metadata": {},
|
484 | 389 | "outputs": [],
|
485 | 390 | "source": [
|
486 |
| - "all_results = []\n", |
487 |
| - "\n", |
488 | 391 | "for season in seasons:\n",
|
489 |
| - " this_df = get_results_df(results[season]) # get a dataframe for this season\n", |
490 |
| - " this_df['season'] = season\n", |
491 |
| - " all_results.append(this_df)\n", |
492 |
| - "\n", |
493 |
| - "all_results = pd.concat(all_results) # combine the list of dataframes into a single dataframe" |
| 392 | + " results[season]['season'] = season # add a season column" |
494 | 393 | ]
|
495 | 394 | },
|
496 | 395 | {
|
497 | 396 | "cell_type": "markdown",
|
498 |
| - "id": "13cecabb-0683-48aa-9efd-4843720c78c3", |
| 397 | + "id": "c0651758-b6e6-45fb-aad4-7500a86422d8", |
499 | 398 | "metadata": {},
|
500 | 399 | "source": [
|
501 |
| - "Finally, we'll use `.set_index()` ([documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.set_index.html)) to set the index of the **DataFrame** using the `season` and `parameter` columns:" |
| 400 | + "Next, we use `pd.concatenate()`, along with the `values()` of the results **dict**, to combine the tables into a single table:" |
502 | 401 | ]
|
503 | 402 | },
|
504 | 403 | {
|
505 | 404 | "cell_type": "code",
|
506 | 405 | "execution_count": null,
|
507 |
| - "id": "d45b2c26-98a2-4b4f-aa8b-cf6efd8ef29a", |
| 406 | + "id": "07c39ce4-8fa8-46fd-a45f-66671a4d5510", |
508 | 407 | "metadata": {},
|
509 | 408 | "outputs": [],
|
510 | 409 | "source": [
|
511 |
| - "all_results.set_index(['season', 'parameter'], inplace=True) # set a multi-level index with season and parameter values\n", |
512 |
| - "all_results # show the dataframe" |
| 410 | + "all_results = pd.concat(results.values()) # concatenate the results dataframes into a single dataframe" |
513 | 411 | ]
|
514 | 412 | },
|
515 | 413 | {
|
516 | 414 | "cell_type": "markdown",
|
517 |
| - "id": "73f66b7d-7960-46cc-962c-cb8880c15d08", |
| 415 | + "id": "f01eb88b-1d5e-456d-86fa-f793f5bfa5f5", |
518 | 416 | "metadata": {},
|
519 | 417 | "source": [
|
520 |
| - "Now, in the final **DataFrame**, we can use the season name with `.loc` to get the parameter results:" |
| 418 | + "Next, we'll use `.set_index()` ([documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.set_index.html)) to set the `season` and `names` columns to be the `index` of the **DataFrame**:" |
521 | 419 | ]
|
522 | 420 | },
|
523 | 421 | {
|
524 | 422 | "cell_type": "code",
|
525 | 423 | "execution_count": null,
|
526 |
| - "id": "a36faa97-80a7-4a9e-9b53-2ba559aa8f8d", |
| 424 | + "id": "ca84bbe4-24e3-4a23-99fb-cb2c618a8bd1", |
527 | 425 | "metadata": {},
|
528 | 426 | "outputs": [],
|
529 | 427 | "source": [
|
530 |
| - "all_results.loc['spring'] # show the rows of the dataframe corresponding to spring" |
| 428 | + "all_results.set_index(['season', 'names'], inplace=True) # set the index to be a multi-index with season and names\n", |
| 429 | + "\n", |
| 430 | + "all_results # show the updated table" |
531 | 431 | ]
|
532 | 432 | },
|
533 | 433 | {
|
534 | 434 | "cell_type": "markdown",
|
535 | 435 | "id": "eeaffe66-ab7c-4f22-8943-6c179ca914ce",
|
536 | 436 | "metadata": {},
|
537 | 437 | "source": [
|
538 |
| - "and, save the table of regression parameter results to a file, using `pd.to_csv()`:" |
| 438 | + "Finally, we'll save the table of regression parameter results to a file, using `pd.to_csv()`:" |
539 | 439 | ]
|
540 | 440 | },
|
541 | 441 | {
|
|
0 commit comments