def cumul_rows_by_count(df, *, count_col='count', n_col='n_rows', tot_col=
    'total_rows', group_cols=None, group_cols_as_str=False):
    """Cumulative number of rows with >= each count.

    Parameters
    ----------
    df : pandas.DataFrame
        Data frame with rows to analyze.
    count_col : str
        Column in `df` with count for row.
    n_col : str
        Name of column in result giving cumulative count threshold.
    tot_col : str
        Name of column in result giving total number of rows.
    group_cols : None or list
        Group by these columns and analyze each group separately.
    group_cols_as_str : bool
        Convert any `group_cols` columns to str. This is needed if calling
        in ``R`` using `reticulate <https://rstudio.github.io/reticulate/>`_.

    Returns
    -------
    pandas.DataFrame
        Give cumulative counts. For each count in `count_col`, column
        `n_col` gives number of rows with >= that many counts, and
        `tot_col` gives total number of counts.

    Examples
    --------
    >>> df = pd.DataFrame({'sample': ['a', 'a', 'b', 'b', 'a', 'a'],
    ...                    'count': [9, 0, 1, 4, 3, 3]})
    >>> cumul_rows_by_count(df)
       count  n_rows  total_rows
    0      9       1           6
    1      4       2           6
    2      3       4           6
    3      1       5           6
    4      0       6           6
    >>> cumul_rows_by_count(df, group_cols=['sample'])
      sample  count  n_rows  total_rows
    0      a      9       1           4
    1      a      3       3           4
    2      a      0       4           4
    3      b      4       1           2
    4      b      1       2           2

    """
    if count_col not in df.columns:
        raise ValueError(f'df does not have column {count_col}')
    if not group_cols:
        drop_group_cols = True
        group_cols = ['dummy_col']
        df[group_cols[0]] = '_'
    else:
        drop_group_cols = False
        if isinstance(group_cols, str):
            group_cols = [group_cols]
    if not set(group_cols).issubset(set(df.columns)):
        raise ValueError(f'invalid `group_cols` {group_cols}')
    for col in [count_col, n_col, tot_col]:
        if col in group_cols:
            raise ValueError(f'`group_cols` cannot contain {col}')
    df = df.assign(**{n_col: 1}).groupby(group_cols + [count_col]).aggregate({
        n_col: 'count'}).reset_index().sort_values(group_cols + [count_col],
        ascending=[True] * len(group_cols) + [False]).reset_index(drop=True
        ).assign(**{n_col: lambda x: x.groupby(group_cols)[n_col].cumsum()}
        ).assign(**{tot_col: lambda x: x.groupby(group_cols)[n_col].
        transform('max')})
    if drop_group_cols:
        df = df.drop(group_cols, axis='columns')
    elif group_cols_as_str:
        for col in group_cols:
            df[col] = df[col].astype('str')
    return df
