davis-stats 1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: davis_stats
3
+ Version: 1.0
4
+ Summary: davis_stats is a teaching-focused repository
5
+ Author: Justin G. Davis
6
+ Author-email:
7
+ Keywords: statistics,data science,business analytics
8
+ Requires-Python: >=3.7
9
+ Requires-Dist: pandas>=1.0.0
10
+ Requires-Dist: openpyxl>=3.0.0
11
+ Requires-Dist: numpy>=1.20.0
12
+ Requires-Dist: matplotlib>=3.0.0
13
+ Requires-Dist: scipy>=1.6.0
14
+ Requires-Dist: seaborn>=0.12.0
15
+ Requires-Dist: statsmodels>=0.14.0
16
+ Requires-Dist: ipympl>=0.8.0
17
+ Dynamic: author
18
+ Dynamic: keywords
19
+ Dynamic: requires-dist
20
+ Dynamic: requires-python
21
+ Dynamic: summary
@@ -0,0 +1,6 @@
1
+ from .visualization.trim import trim
2
+ from .visualization.boxplot import boxplot
3
+ from .visualization.histogram import histogram
4
+ from .visualization.scatter import scatter
5
+ from .data import ceo_comp
6
+ from .stats.reg import reg
@@ -0,0 +1,18 @@
1
+ import pandas as pd
2
+ from pathlib import Path
3
+
4
+ DATA_DIR = Path(__file__).parent
5
+
6
+ class DataFrames:
7
+ _ceo_comp = None
8
+
9
+ @property
10
+ def ceo_comp(self):
11
+ if self._ceo_comp is None:
12
+ self._ceo_comp = pd.read_excel(DATA_DIR / 'ceo_comp.xlsx')
13
+ return self._ceo_comp
14
+
15
+ _data = DataFrames()
16
+
17
+ def ceo_comp():
18
+ return _data.ceo_comp
@@ -0,0 +1 @@
1
+ from .reg import reg
@@ -0,0 +1,110 @@
1
+ def reg(df, y, x, dummies=None, logistic=False):
2
+ """
3
+ Run linear (OLS) or logistic regression.
4
+
5
+ Parameters
6
+ ----------
7
+ df : pd.DataFrame
8
+ Input dataframe containing all variables
9
+ y : str
10
+ Name of the dependent variable column
11
+ x : str or list of str
12
+ Name(s) of independent variable column(s)
13
+ dummies : str or list of str, optional
14
+ Categorical variable(s) to convert to dummy variables
15
+ logistic : bool, default False
16
+ If True, run logistic regression; otherwise run OLS
17
+
18
+ Returns
19
+ -------
20
+ statsmodels results object or None if fitting fails
21
+ """
22
+ import statsmodels.api as sm
23
+ import pandas as pd
24
+
25
+ # Convert x to list if string, make copy to avoid modifying original
26
+ if isinstance(x, str):
27
+ x = [x]
28
+ else:
29
+ x = list(x)
30
+
31
+ df_reg = df.copy()
32
+
33
+ # Convert main variables to numeric
34
+ for col in [y] + x:
35
+ df_reg[col] = pd.to_numeric(df_reg[col], errors='coerce')
36
+
37
+ # Handle dummy variables
38
+ if dummies:
39
+ if isinstance(dummies, str):
40
+ dummies = [dummies]
41
+
42
+ for dummy_var in dummies:
43
+ # For logistic: filter to categories with variation in y
44
+ if logistic:
45
+ ct = pd.crosstab(df_reg[dummy_var], df_reg[y])
46
+ valid_categories = ct[(ct > 0).all(axis=1)].index
47
+ df_reg = df_reg[df_reg[dummy_var].isin(valid_categories)]
48
+
49
+ dummy_cols = pd.get_dummies(
50
+ df_reg[dummy_var],
51
+ prefix=dummy_var,
52
+ drop_first=True,
53
+ dtype=float
54
+ )
55
+ df_reg = pd.concat([df_reg, dummy_cols], axis=1)
56
+ x.extend(dummy_cols.columns.tolist())
57
+
58
+ # Drop rows with missing values
59
+ df_reg = df_reg.dropna(subset=[y] + x)
60
+
61
+ if len(df_reg) == 0:
62
+ print("Error: No observations remaining after dropping missing values")
63
+ return None
64
+
65
+ # Prepare X and y
66
+ X = sm.add_constant(df_reg[x].astype(float))
67
+ y_data = df_reg[y].astype(float)
68
+
69
+ # Fit model
70
+ try:
71
+ if logistic:
72
+ model = sm.Logit(y_data, X)
73
+ results = model.fit(method='bfgs', maxiter=100, disp=0)
74
+ else:
75
+ model = sm.OLS(y_data, X)
76
+ results = model.fit()
77
+
78
+ print(results.summary())
79
+ return results
80
+
81
+ except Exception as e:
82
+ print(f"Error fitting model: {e}")
83
+
84
+ # For logistic with dummies, try again with larger categories only
85
+ if logistic and dummies:
86
+ min_obs = 30
87
+ print(f"\nRetrying with categories having at least {min_obs} observations...")
88
+
89
+ for dummy_var in dummies:
90
+ value_counts = df_reg[dummy_var].value_counts()
91
+ valid_categories = value_counts[value_counts >= min_obs].index
92
+ df_reg = df_reg[df_reg[dummy_var].isin(valid_categories)]
93
+
94
+ if len(df_reg) == 0:
95
+ print("Error: No observations remaining after filtering")
96
+ return None
97
+
98
+ try:
99
+ X = sm.add_constant(df_reg[x].astype(float))
100
+ y_data = df_reg[y].astype(float)
101
+
102
+ model = sm.Logit(y_data, X)
103
+ results = model.fit(method='bfgs', maxiter=100, disp=0)
104
+ print("\nResults with reduced sample:")
105
+ print(results.summary())
106
+ return results
107
+ except Exception as e2:
108
+ print(f"Error fitting reduced model: {e2}")
109
+
110
+ return None
@@ -0,0 +1,4 @@
1
+ from .trim import trim
2
+ from .boxplot import boxplot
3
+ from .histogram import histogram
4
+ from .scatter import scatter
@@ -0,0 +1,34 @@
1
+ import matplotlib.pyplot as plt
2
+ import numpy as np
3
+ from .trim import trim
4
+
5
+ def boxplot(series, title=None, trim_outliers=100, dpi=150, figsize=(6, 4)):
6
+ # Create figure and axis
7
+ fig, ax = plt.subplots(figsize=figsize, dpi=dpi)
8
+
9
+ if not title:
10
+ title = series.name
11
+
12
+ # Apply trimming if trim < 100
13
+ if trim_outliers < 100:
14
+ series = trim(series, trim_outliers)
15
+ title = f"{title} (outliers removed at {trim_outliers}% level)"
16
+
17
+ # Convert to numpy array and ensure 1D
18
+ data = np.array(series.dropna()).flatten()
19
+
20
+ # Create boxplot on the axis
21
+ ax.boxplot(data,
22
+ patch_artist=True,
23
+ boxprops=dict(facecolor='skyblue', color='black'),
24
+ medianprops=dict(color='black'),
25
+ flierprops=dict(marker='o', markerfacecolor='gray'),
26
+ whiskerprops=dict(color='black'),
27
+ capprops=dict(color='black'))
28
+
29
+ # Add labels and styling
30
+ ax.set_title(title)
31
+ ax.ticklabel_format(style='plain', axis='y')
32
+ ax.grid(True, linestyle='--', alpha=0.7)
33
+
34
+ plt.show(block=False)
@@ -0,0 +1,71 @@
1
+ import matplotlib.pyplot as plt
2
+ import numpy as np
3
+ import pandas as pd
4
+ from .trim import trim
5
+
6
+ def histogram(series, title=None, bins=30, trim_outliers=100, details=False, dpi=150, figsize=(6, 4)):
7
+ # Create figure and axis
8
+ fig, ax = plt.subplots(figsize=figsize, dpi=dpi)
9
+
10
+ # Get base title
11
+ if not title:
12
+ title = series.name
13
+
14
+ # Store original data for statistics BEFORE any trimming
15
+ original_data = np.array(series.dropna()).flatten()
16
+
17
+ # Apply trimming if trim < 100
18
+ if trim_outliers < 100:
19
+ series = trim(series, trim_outliers)
20
+ title = f"{title} (outliers removed at {trim_outliers}% level)"
21
+
22
+ # Convert to numpy array and handle NaN
23
+ data = np.array(series.dropna()).flatten()
24
+
25
+ # Create histogram
26
+ ax.hist(data,
27
+ bins=bins,
28
+ edgecolor='black',
29
+ color='skyblue',
30
+ alpha=0.7)
31
+
32
+ # Add labels and styling
33
+ ax.set_title(title)
34
+ ax.set_ylabel('Count')
35
+ ax.ticklabel_format(style='plain', axis='x')
36
+ ax.grid(True, linestyle='--', alpha=0.7)
37
+
38
+ if details:
39
+ # Add mean and median lines
40
+ mean = np.mean(original_data)
41
+ median = np.median(original_data)
42
+ ax.axvline(mean, color='red', linestyle='--', label=f'Mean: {mean:.2f}')
43
+ ax.axvline(median, color='green', linestyle='--', label=f'Median: {median:.2f}')
44
+
45
+ # Calculate standard deviations
46
+ std = np.std(original_data)
47
+
48
+ # Calculate percentages within SDs
49
+ within_1sd = np.sum((original_data >= mean - std) & (original_data <= mean + std)) / len(original_data) * 100
50
+ within_2sd = np.sum((original_data >= mean - 2*std) & (original_data <= mean + 2*std)) / len(original_data) * 100
51
+ within_3sd = np.sum((original_data >= mean - 3*std) & (original_data <= mean + 3*std)) / len(original_data) * 100
52
+
53
+ skew_val = pd.Series(original_data).skew()
54
+
55
+ # Add text box with statistics
56
+ stats_text = (
57
+ f'Skewness: {skew_val:.3f}\n'
58
+ f'Within 1 SD: {within_1sd:.1f}%\n'
59
+ f'Within 2 SD: {within_2sd:.1f}%\n'
60
+ f'Within 3 SD: {within_3sd:.1f}%'
61
+ )
62
+ plt.text(0.95, 0.95, stats_text,
63
+ transform=ax.transAxes,
64
+ verticalalignment='top',
65
+ horizontalalignment='right',
66
+ bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
67
+
68
+ # Add legend
69
+ plt.legend()
70
+
71
+ plt.show(block=False)
@@ -0,0 +1,132 @@
1
+ def scatter(df, y, x, z=None, fit_line=False, dpi=150, figsize=(6, 4)):
2
+ """
3
+ Create a nice scatter plot with optional fit line and correlation coefficient
4
+
5
+ Parameters:
6
+ df (pandas DataFrame): Input data
7
+ y (str): Column name for y-axis variable (vertical axis in 3D)
8
+ x (str): Column name for x-axis variable
9
+ z (str, optional): Column name for z-axis variable (creates 3D plot)
10
+ fit_line (bool): If True, adds best fit line (2D) or plane (3D)
11
+ dpi (int): Plot resolution
12
+ figsize (tuple): Figure size
13
+ """
14
+ import seaborn as sns
15
+ import matplotlib.pyplot as plt
16
+ import numpy as np
17
+ from mpl_toolkits.mplot3d import Axes3D
18
+
19
+ # 2D scatter plot (original functionality)
20
+ if z is None:
21
+ # Calculate correlation coefficient
22
+ corr = df[x].corr(df[y])
23
+
24
+ # Set style
25
+ sns.set_style("whitegrid")
26
+
27
+ # Create figure
28
+ fig, ax = plt.subplots(figsize=figsize, dpi=dpi)
29
+
30
+ # Create scatter plot
31
+ if fit_line:
32
+ # Use seaborn's regplot for scatter + fit line
33
+ sns.regplot(data=df,
34
+ x=x,
35
+ y=y,
36
+ scatter_kws={'alpha':0.5},
37
+ line_kws={'color': 'red'},
38
+ ci=None)
39
+ else:
40
+ # Use seaborn's scatterplot
41
+ sns.scatterplot(data=df,
42
+ x=x,
43
+ y=y,
44
+ alpha=0.5)
45
+
46
+ # Customize plot - 2 variable title format
47
+ plt.title(f'{y} and {x}\nCorrelation: {corr:.3f}', pad=15)
48
+ plt.xlabel(x)
49
+ plt.ylabel(y)
50
+
51
+ # Adjust layout
52
+ plt.tight_layout()
53
+
54
+ # 3D scatter plot
55
+ else:
56
+ # Create 3D figure with larger size for better visibility
57
+ fig = plt.figure(figsize=figsize, dpi=dpi)
58
+ ax = fig.add_subplot(111, projection='3d')
59
+
60
+ # Remove NaN values for plotting
61
+ plot_df = df[[x, y, z]].dropna()
62
+
63
+ # Create 3D scatter plot with color spectrum and edge color
64
+ # In matplotlib 3D: (x-axis, y-axis, z-axis) where z-axis is VERTICAL
65
+ # So to make y variable vertical, it goes in the 3rd position
66
+ scatter = ax.scatter(plot_df[x], plot_df[z], plot_df[y],
67
+ c=plot_df[y], cmap='RdYlBu_r', s=50,
68
+ edgecolor='black', linewidth=0.5,
69
+ alpha=0.8)
70
+
71
+ # Add colorbar with more space
72
+ cbar = plt.colorbar(scatter, ax=ax, pad=0.15, shrink=0.8)
73
+ cbar.set_label(y, rotation=270, labelpad=15)
74
+
75
+ # Add best-fit plane if requested
76
+ if fit_line:
77
+ # Prepare data for plane fitting: y = a*x + b*z + c
78
+ X_data = np.column_stack([plot_df[x], plot_df[z], np.ones(len(plot_df))])
79
+ y_data = plot_df[y].values
80
+
81
+ # Fit plane using least squares: [a, b, c]
82
+ coeffs, residuals, rank, s = np.linalg.lstsq(X_data, y_data, rcond=None)
83
+ a, b, c = coeffs
84
+
85
+ # Create mesh grid for the regression plane
86
+ x_surf = np.linspace(plot_df[x].min(), plot_df[x].max(), 20)
87
+ z_surf = np.linspace(plot_df[z].min(), plot_df[z].max(), 20)
88
+ X_mesh, Z_mesh = np.meshgrid(x_surf, z_surf)
89
+
90
+ # Calculate y values for the plane
91
+ Y_mesh = a * X_mesh + b * Z_mesh + c
92
+
93
+ # Plot the regression plane with grid lines and semi-transparency
94
+ # Order: (x-axis, y-axis, z-axis) where z-axis is vertical
95
+ surf = ax.plot_surface(X_mesh, Z_mesh, Y_mesh,
96
+ alpha=0.4, cmap='coolwarm',
97
+ edgecolor='black', linewidth=0.5,
98
+ rstride=1, cstride=1,
99
+ antialiased=True)
100
+
101
+ # 3 variable title format
102
+ title = f'{y}, {x}, and {z}'
103
+
104
+ # Set axis labels
105
+ # In matplotlib 3D: x is horizontal left-right, y is horizontal front-back, z is VERTICAL
106
+ ax.set_xlabel(x, labelpad=10) # horizontal axis
107
+ ax.set_ylabel(z, labelpad=10) # horizontal axis (front-back)
108
+ ax.set_zlabel(y, labelpad=10) # VERTICAL axis (up-down)
109
+
110
+ # Move z-axis (vertical) ticks and label to the left to avoid colorbar overlap
111
+ ax.zaxis._axinfo['juggled'] = (1, 2, 0) # Move z-axis to left side
112
+
113
+ # Flip axes to go from least to greatest (normal order)
114
+ ax.invert_xaxis()
115
+ ax.invert_yaxis()
116
+
117
+ # Adjust the viewing angle for better perspective
118
+ ax.view_init(elev=25, azim=135)
119
+
120
+ # Make the grid lines more visible
121
+ ax.xaxis._axinfo["grid"]['color'] = (0.5, 0.5, 0.5, 0.5)
122
+ ax.yaxis._axinfo["grid"]['color'] = (0.5, 0.5, 0.5, 0.5)
123
+ ax.zaxis._axinfo["grid"]['color'] = (0.5, 0.5, 0.5, 0.5)
124
+
125
+ # Set title after everything else
126
+ ax.set_title(title, pad=15)
127
+
128
+ # Adjust layout
129
+ plt.tight_layout()
130
+
131
+ # Show plot
132
+ plt.show()
@@ -0,0 +1,16 @@
1
+ import numpy as np
2
+
3
+ def trim(series, percentile_keep = 100):
4
+ # Calculate how much to remove
5
+ remove_pct = 100 - percentile_keep
6
+
7
+ # Find the threshold for most extreme values
8
+ # This gets the absolute distance from median for each point
9
+ median_val = np.median(series)
10
+ abs_deviations = np.abs(series - median_val)
11
+
12
+ # Find the threshold - keep values with smaller deviations
13
+ threshold = np.percentile(abs_deviations, percentile_keep)
14
+
15
+ # Return values within the threshold
16
+ return series[abs_deviations <= threshold]
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: davis_stats
3
+ Version: 1.0
4
+ Summary: davis_stats is a teaching-focused repository
5
+ Author: Justin G. Davis
6
+ Author-email:
7
+ Keywords: statistics,data science,business analytics
8
+ Requires-Python: >=3.7
9
+ Requires-Dist: pandas>=1.0.0
10
+ Requires-Dist: openpyxl>=3.0.0
11
+ Requires-Dist: numpy>=1.20.0
12
+ Requires-Dist: matplotlib>=3.0.0
13
+ Requires-Dist: scipy>=1.6.0
14
+ Requires-Dist: seaborn>=0.12.0
15
+ Requires-Dist: statsmodels>=0.14.0
16
+ Requires-Dist: ipympl>=0.8.0
17
+ Dynamic: author
18
+ Dynamic: keywords
19
+ Dynamic: requires-dist
20
+ Dynamic: requires-python
21
+ Dynamic: summary
@@ -0,0 +1,16 @@
1
+ setup.py
2
+ davis_stats/__init__.py
3
+ davis_stats.egg-info/PKG-INFO
4
+ davis_stats.egg-info/SOURCES.txt
5
+ davis_stats.egg-info/dependency_links.txt
6
+ davis_stats.egg-info/requires.txt
7
+ davis_stats.egg-info/top_level.txt
8
+ davis_stats/data/__init__.py
9
+ davis_stats/data/ceo_comp.xlsx
10
+ davis_stats/stats/__init__.py
11
+ davis_stats/stats/reg.py
12
+ davis_stats/visualization/__init__.py
13
+ davis_stats/visualization/boxplot.py
14
+ davis_stats/visualization/histogram.py
15
+ davis_stats/visualization/scatter.py
16
+ davis_stats/visualization/trim.py
@@ -0,0 +1,8 @@
1
+ pandas>=1.0.0
2
+ openpyxl>=3.0.0
3
+ numpy>=1.20.0
4
+ matplotlib>=3.0.0
5
+ scipy>=1.6.0
6
+ seaborn>=0.12.0
7
+ statsmodels>=0.14.0
8
+ ipympl>=0.8.0
@@ -0,0 +1 @@
1
+ davis_stats
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,28 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name='davis_stats',
5
+ version='1.0',
6
+ packages=find_packages(),
7
+ package_data={
8
+ 'davis_stats': ['data/*.xlsx']},
9
+ install_requires=[
10
+ 'pandas>=1.0.0',
11
+ 'openpyxl>=3.0.0',
12
+ 'numpy>=1.20.0',
13
+ 'matplotlib>=3.0.0',
14
+ 'scipy>=1.6.0',
15
+ 'seaborn>=0.12.0',
16
+ 'statsmodels>=0.14.0',
17
+ 'ipympl>=0.8.0'],
18
+ python_requires='>=3.7',
19
+ author='Justin G. Davis',
20
+ author_email='',
21
+ description='''
22
+ davis_stats is a teaching-focused repository
23
+ for applied statistics, data science, and
24
+ business analytics. It contains functions
25
+ and datasets to help W&L students develop
26
+ practical skills in data analysis, statistical
27
+ modeling, and real-world decision making.''',
28
+ keywords='statistics, data science, business analytics')