Skip to content

cupid_streamlit_utils module

Utility code for the Streamlit app in cupid_streamlit.py.

download_parameters_results(file_name, do_estimates, pars_res)

formats the summary of the simulation (parameters and results) and offers to download the summary as a text file.

Parameters:

Name Type Description Default
file_name str

the summary will be downloaded in file_name

required
do_estimates bool

if True, the two estimators were run and we also give the estimation results

required
pars_res Any

a tuple with what we need to format the summary

required

Returns:

Type Description
None

nothing

Source code in cupid_matching/cupid_streamlit_utils.py
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
def download_parameters_results(
    file_name: str, do_estimates: bool, pars_res: Any
) -> None:
    """formats the summary of the simulation (parameters and results) and offers to download
    the summary as a text file.

    Args:
        file_name: the summary will be downloaded in file_name
        do_estimates: if True, the two estimators were run and we also give the estimation results
        pars_res: a tuple with what we need to format the summary

    Returns:
        nothing
    """
    if do_estimates:
        (
            n_households,
            ncat_men,
            ncat_women,
            proportion_men,
            scenario_men,
            scenario_women,
            true_coeffs,
            coeff_names,
            muxy_sim,
            mux0_sim,
            mu0y_sim,
            n_sim,
            m_sim,
            df_mde,
            mde_test_results,
            df_poisson,
            df_u_estimates,
            df_v_estimates,
        ) = pars_res
        n_sim = cast(np.ndarray, n_sim)
        m_sim = cast(np.ndarray, m_sim)
        muxy_sim = cast(np.ndarray, muxy_sim)
        mux0_sim = cast(np.ndarray, mux0_sim)
        mu0y_sim = cast(np.ndarray, mu0y_sim)
        results_str = (
            f"You chose a model with {ncat_men} types of men and {ncat_women} types of"
            " women,\n"
        )
        results_str += (
            f"   with the number of men {scenario_men} and a number of wwomen"
            f" {scenario_women},\n"
        )
        results_str += (
            f"    and {n_households} households with a proportion of"
            f" {100*proportion_men:.1f} percent of men.\n\n"
        )
        results_str += "The numbers of men in each category are:\n"
        for x in range(ncat_men):
            results_str += f"{x+1}: {n_sim[x]: d}\n"
        results_str += "\n  the numbers of women in each category are:\n"
        for y in range(ncat_women):
            results_str += f"{y+1}: {m_sim[y]: d}\n"
        results_str += "\nYou chose the following coefficients:\n"
        for coeff, value in zip(coeff_names, true_coeffs, strict=True):
            results_str += f"{coeff}:  {value: >10.3f}\n"
        results_str += "\n\n"
        results_str += "This gives the following numbers of marriages:\n"
        for x in range(ncat_men):
            for y in range(ncat_women):
                results_str += f"{muxy_sim[x, y]: d}   "
            results_str += "\n"
        results_str += "\n\n"
        results_str += "The numbers of single men are:\n"
        for x in range(ncat_men):
            results_str += f"{x+1}: {mux0_sim[x]: d}\n"
        results_str += "\n\n"
        results_str += "The numbers of single women are:\n"
        for y in range(ncat_women):
            results_str += f"{y+1}: {mu0y_sim[y]: d}\n"
        results_str += "\n\n Minimum distance estimation gives\n"
        results_str += df_mde.to_string()
        specif_test_ndf, specif_test_stat, specif_test_pval = mde_test_results
        results_str += (
            f"\n\nSpecification test statistic: chi2({specif_test_ndf}) ="
            f" {specif_test_stat}\n"
        )
        results_str += f"     with  p-value {specif_test_pval}\n\n"
        results_str += "\n\n Poisson-GLM estimation gives\n"
        results_str += df_poisson.to_string()
        results_str += "\n\n  the expected utilities of men are:\n"
        results_str += df_u_estimates.to_string()
        results_str += "\n\n  the expected utilities of women are:\n"
        results_str += df_v_estimates.to_string()
    else:
        ncat_men, ncat_women, n_sim, m_sim, Phi, muxy_sim, mux0_sim, mu0y_sim = pars_res
        n_sim = cast(np.ndarray, n_sim)
        m_sim = cast(np.ndarray, m_sim)
        muxy_sim = cast(np.ndarray, muxy_sim)
        mux0_sim = cast(np.ndarray, mux0_sim)
        mu0y_sim = cast(np.ndarray, mu0y_sim)
        results_str = (
            f"You chose a model with {ncat_men} types of men and {ncat_women} types of"
            " women;\n"
        )
        results_str += "\n  the numbers of men in each category are:\n"
        for x in range(ncat_men):
            results_str += f"{x+1}: {n_sim[x]: d}\n"
        results_str += "\n  the numbers of women in each category are:\n"
        for y in range(ncat_women):
            results_str += f"{y+1}: {m_sim[y]: d}\n"
        results_str += "\nYou chose the following joint surplus matrix:\n"
        for x in range(ncat_men):
            for y in range(ncat_women):
                results_str += f"{Phi[x, y]: 10.2f} "
            results_str += "\n"
        results_str += "\n\n"
        results_str += "This gives the following numbers of marriages:\n"
        for x in range(ncat_men):
            for y in range(ncat_women):
                results_str += f"{muxy_sim[x, y]: d}   "
            results_str += "\n"
        results_str += "\n\n"
        results_str += "The numbers of single men are:\n"
        for x in range(ncat_men):
            results_str += f"{x+1}: {mux0_sim[x]: d}\n"
        results_str += "\n\n"
        results_str += "The numbers of single women are:\n"
        for y in range(ncat_women):
            results_str += f"{y+1}: {mu0y_sim[y]: d}\n"

    _ = st.download_button(
        label=f"Download summary to {file_name}",
        data=results_str,
        file_name=f"{file_name}",
        mime="text/csv",
    )

make_margins(n, ncat, scenario='Constant')

generates the numbers by type on one side of the market

Parameters:

Name Type Description Default
n int

the total number of individuals on that side of the market

required
ncat int

the number of types on that side of the market

required
scenario str

"Constant", "Increasing", or "Decreasing" numbers as a function of type

'Constant'

Returns:

Type Description
np.ndarray

the numbers by type on this side

Source code in cupid_matching/cupid_streamlit_utils.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def make_margins(n: int, ncat: int, scenario: str = "Constant") -> np.ndarray:
    """generates the numbers by type on one side of the market

    Args:
        n: the total number of individuals on that side of the market
        ncat: the number of types on that side of the market
        scenario: "Constant", "Increasing", or "Decreasing" numbers as a function of type

    Returns:
        the numbers by type on this side
    """
    n_constant = n / ncat
    if scenario == "Constant":
        n_types = np.full(ncat, n_constant)
        return n_types
    elif scenario == "Increasing":
        lambda_val = pow(2.0, 1.0 / (ncat - 1))
    elif scenario == "Decreasing":
        lambda_val = pow(2.0, -1.0 / (ncat - 1))
    else:
        bs_error_abort(f"Unknown scenario {scenario}")
    n_types = _make_profile(lambda_val, n, ncat)
    return n_types

plot_heatmap(mat, str_format, str_tit=None)

Plots a heatmap of the matrix

Parameters:

Name Type Description Default
mat np.ndarray

the matrix to plot

required
str_tit str | None

a title, if any

None

Returns:

Type Description
alt.Chart

the heatmap

Source code in cupid_matching/cupid_streamlit_utils.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def plot_heatmap(
    mat: np.ndarray, str_format: str, str_tit: str | None = None
) -> alt.Chart:
    """Plots a heatmap of the matrix

    Args:
        mat: the matrix to plot
        str_tit: a title, if any

    Returns:
        the heatmap
    """
    ncat_men, ncat_women = check_matrix(mat)
    mat_arr = np.empty((mat.size, 4), dtype=int)
    imat = np.round(mat)
    mat_min = np.min(imat)
    i = 0
    for ix in range(ncat_men):
        for iy in range(ncat_women):
            m = imat[ix, iy]
            s = m - mat_min + 1
            mat_arr[i, :] = np.array([ix, iy, m, s])
            i += 1

    mat_df = pd.DataFrame(mat_arr, columns=["Men", "Women", "Value", "Size"])
    mat_df = mat_df.astype(
        dtype={"Men": int, "Women": int, "Value": int, "Size": float}
    )
    base = alt.Chart(mat_df).encode(x="Men:O", y=alt.Y("Women:O", sort="descending"))
    mat_map = base.mark_circle(opacity=0.4).encode(
        size=alt.Size("Size:Q", legend=None, scale=alt.Scale(range=[1000, 10000])),
        # color=alt.Color("Value:Q"),
        # tooltip=alt.Tooltip('Value', format=".2f")
    )
    text = base.mark_text(baseline="middle", fontSize=16).encode(
        text=alt.Text("Value:Q", format=str_format),
    )
    if str_tit is None:
        both = (mat_map + text).properties(width=500, height=500)
    else:
        both = (mat_map + text).properties(title=str_tit, width=400, height=400)
    return both

plot_matching(mus)

generates the complete plot of matching patterns

Parameters:

Name Type Description Default
mus Matching

the matching patterns

required

Returns:

Type Description
alt.Chart

the plot

Source code in cupid_matching/cupid_streamlit_utils.py
171
172
173
174
175
176
177
178
179
180
181
182
183
def plot_matching(mus: Matching) -> alt.Chart:
    """generates the complete plot of matching patterns

    Args:
        mus: the matching patterns

    Returns:
        the plot
    """
    muxy, mux0, mu0y, _, _ = mus.unpack()
    plotxy = plot_heatmap(muxy, "d", str_tit="Marriages")
    plotsingles = _plot_bars(mux0, mu0y)
    return plotxy | plotsingles

table_estimates(coeff_names, true_coeffs, estimates, stderrs)

returns a table of the estimates

Parameters:

Name Type Description Default
coeff_names list[str]

the names of the coefficients

required
true_coeffs np.ndarray

the true values of the coefficients

required
estimates np.ndarray

the estimated values of the coefficients

required
stderrs np.ndarray

the standard errors of the estimates

required

Returns:

Type Description
pd.DataFrame

the table

Source code in cupid_matching/cupid_streamlit_utils.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def table_estimates(
    coeff_names: list[str],
    true_coeffs: np.ndarray,
    estimates: np.ndarray,
    stderrs: np.ndarray,
) -> pd.DataFrame:
    """returns a table of the estimates

    Args:
        coeff_names: the names of the coefficients
        true_coeffs: the true values of the coefficients
        estimates: the estimated values of the coefficients
        stderrs: the standard errors of the estimates

    Returns:
        the table
    """
    st.write("The coefficients are:")
    df_coeffs_estimates = pd.DataFrame(
        {
            "True": true_coeffs,
            "Estimated": estimates,
            "Standard errors": stderrs,
        },
        index=coeff_names,
    )
    return df_coeffs_estimates