def mutate_DataFrame(df, **kwargs):
    """Verb: add columns to a DataFrame defined by kwargs:

    Parameters
    ----------
            kwargs : scalar, pd.Series, callable, dict
                * scalar, pd.Series -> assign column
                * callable - call callable(df) and assign result
                * dict (None: column) - result of itergroups on non-grouped DF to have parity with mutate_DataFrameGroupBy
    Examples
    --------
    add a rank for one column::

        dp(mtcars).mutate(hp_rank = X.hp.rank)


    rank all columns::

        # dict comprehension for illustrative purposes
        dp(mtcars).mutate(**{f"{column}_rank": X[column].rank() for column in X.columns}).pd
        # more efficient
        dp(mtcars).rank().pd()

    one rank per group using callback::

        dp(mtcars).group_by('cyl').mutate(rank = lambda X: X['hp'].rank()).pd


    add_count variant 1 (see :func:`dppd.single_verbs.add_count`)::

        dp(mtcars).group_by('cyl').mutate(count=lambda x: len(x)).pd



    add_count variant 2::

        dp(mtcars).group_by('cyl').mutate(count={grp: len(sub_df) for (grp, sub_df) in X.itergroups()}).pd


    """
    for k, v in kwargs.items():
        if isinstance(v, dict):
            if len(v) != 1:
                raise KeyError("Expected dict with single key: None")
            kwargs[k] = v[None]
    to_assign = kwargs
    return df.assign(**to_assign)