toucan_data_sdk/utils/postprocess/waterfall.py
def waterfall(
        df,
        date: str,
        value: str,
        start: Dict[str, str],
        end: Dict[str, str],
        upperGroup: Dict[str, str],
        insideGroup: Dict[str, str] = None,
        filters: List[str] = None
):
    """
    Return a line for each bars of a waterfall chart, totals, groups, subgroups.
    Compute the variation and variation rate for each line.

    ---

    ### Parameters

    *mandatory :*
    - `date` (*str*): name of the column that id the period of each lines
    - `value` (*str*): name of the column that contains the vaue for each lines
    - `start` (*dict*):
        - `label`: text displayed under the first master column
        - `id`: value in the date col that id lines for the first period
    - `end` (*dict*):
        - `label`: text displayed under the last master column
        - `id`: value in the date col that id lines for the second period

    *optional :*
    - `upperGroup` (*dict*):
        - `id`: name of the column that contains upperGroups unique IDs
        - `label`: not required, text displayed under each upperGroups bars,
          using ID when it's absent
        - `groupsOrder`: not required, order of upperGroups
    - `insideGroup` (*dict*):
        - `id`: name of the column that contains insideGroups unique IDs
        - `label`: not required, text displayed under each insideGroups bars,
          using ID when it's absent
        - `groupsOrder`: not required, order of insideGroups
    - `filters` (*list*): columns to filters on

    ---

    ### Example

    **Input**

    | product_id   |   played | date   |   ord | category_id   | category_name   |
    |:------------:|:--------:|:------:|:-----:|:-------------:|:---------------:|
    | super clap   |       12 | t1     |     1 | clap          | Clap            |
    | clap clap    |        1 | t1     |    10 | clap          | Clap            |
    | tac          |        1 | t1     |     1 | snare         | Snare           |
    | super clap   |       10 | t2     |     1 | clap          | Clap            |
    | tac          |      100 | t2     |     1 | snare         | Snare           |
    | bom          |        1 | t2     |     1 | tom           | Tom             |


    ```cson
    waterfall:
      upperGroup:
        id: 'category_id'
        label: 'category_name'
      insideGroup:
        id: 'product_id'
        groupsOrder: 'ord'
      date: 'date'
      value: 'played'
      start:
        label: 'Trimestre 1'
        id: 't1'
      end:
        label: 'Trimester 2'
        id: 't2'
    ```

    **Output**

    |   value | label       |   variation | groups   | type   |   order |
    |:-------:|:-----------:|:-----------:|:--------:|:------:|:-------:|
    |      14 | Trimestre 1 |  NaN        | NaN      | NaN    |     NaN |
    |      -3 | Clap        |   -0.230769 | clap     | parent |     NaN |
    |      -2 | super clap  |   -0.166667 | clap     | child  |       1 |
    |      -1 | clap clap   |   -1        | clap     | child  |      10 |
    |      99 | Snare       |   99        | snare    | parent |     NaN |
    |      99 | tac         |   99        | snare    | child  |       1 |
    |       1 | Tom         |  inf        | tom      | parent |     NaN |
    |       1 | bom         |  inf        | tom      | child  |       1 |
    |     111 | Trimester 2 |  NaN        | NaN      | NaN    |     NaN |
    """

    if len(df) == 0:
        return df

    if filters is not None:
        if isinstance(filters, str):
            filters = [filters]

        def sub_waterfall(df):
            wa_df = waterfall(df, date, value, start, end, upperGroup, insideGroup)
            for filters_col in filters:
                wa_df[filters_col] = df[filters_col].values[0]
            return wa_df

        # filters df into a list of sub_df
        list_of_sub_df = [df[(df[filters].values == i).all(axis=1)]
                          for i in df[filters].drop_duplicates().values]

        return pd.concat([sub_waterfall(df) for df in list_of_sub_df], sort=False)

    groups = {
        'upperGroup': {
            'type': 'parent',
            'id':  'upperGroup',
            'order': {
                'by': ['upperGroup_order', 'groups'],
                'ascending': [True, True]
            },
            'obj': upperGroup
        }
    }
    if insideGroup is not None:
        groups['insideGroup'] = {
            'type': 'child',
            'id': 'insideGroup',
            'order': {
                'by': ['type', 'insideGroup_order', 'label'],
                'ascending': [False, True, True]
            },
            'obj': insideGroup
        }
    # prepare the dataframe with standard column names
    df = _compute_rename(df, date, value, groups)

    agg_conf = {'value': sum}
    agg_conf.update({f'{col}_label': 'first' for col in groups.keys()})
    agg_conf.update({f'{col}_order': 'first' for col in groups.keys()})
    df = df.groupby(list(groups.keys()) + ['date']).agg(agg_conf).reset_index()

    df_start, df_end = _compute_start_end(df, start, end)

    df = _compute_value_diff(df, start, end, groups)

    middle = _compute_upper_group(df)
    if insideGroup is not None:
        middle = pd.concat([middle, _compute_inside_group(df)])

    ret = _compute_order(df_start, df_end, middle, groups)

    return ret
def waterfall(
        df,
        date: str,
        value: str,
        start: Dict[str, str],
        end: Dict[str, str],
        upperGroup: Dict[str, str],
        insideGroup: Dict[str, str] = None,
        filters: List[str] = None
):
    """
    Return a line for each bars of a waterfall chart, totals, groups, subgroups.
    Compute the variation and variation rate for each line.

    ---

    ### Parameters

    *mandatory :*
    - `date` (*str*): name of the column that id the period of each lines
    - `value` (*str*): name of the column that contains the vaue for each lines
    - `start` (*dict*):
        - `label`: text displayed under the first master column
        - `id`: value in the date col that id lines for the first period
    - `end` (*dict*):
        - `label`: text displayed under the last master column
        - `id`: value in the date col that id lines for the second period

    *optional :*
    - `upperGroup` (*dict*):
        - `id`: name of the column that contains upperGroups unique IDs
        - `label`: not required, text displayed under each upperGroups bars,
          using ID when it's absent
        - `groupsOrder`: not required, order of upperGroups
    - `insideGroup` (*dict*):
        - `id`: name of the column that contains insideGroups unique IDs
        - `label`: not required, text displayed under each insideGroups bars,
          using ID when it's absent
        - `groupsOrder`: not required, order of insideGroups
    - `filters` (*list*): columns to filters on

    ---

    ### Example

    **Input**

    | product_id   |   played | date   |   ord | category_id   | category_name   |
    |:------------:|:--------:|:------:|:-----:|:-------------:|:---------------:|
    | super clap   |       12 | t1     |     1 | clap          | Clap            |
    | clap clap    |        1 | t1     |    10 | clap          | Clap            |
    | tac          |        1 | t1     |     1 | snare         | Snare           |
    | super clap   |       10 | t2     |     1 | clap          | Clap            |
    | tac          |      100 | t2     |     1 | snare         | Snare           |
    | bom          |        1 | t2     |     1 | tom           | Tom             |


    ```cson
    waterfall:
      upperGroup:
        id: 'category_id'
        label: 'category_name'
      insideGroup:
        id: 'product_id'
        groupsOrder: 'ord'
      date: 'date'
      value: 'played'
      start:
        label: 'Trimestre 1'
        id: 't1'
      end:
        label: 'Trimester 2'
        id: 't2'
    ```

    **Output**

    |   value | label       |   variation | groups   | type   |   order |
    |:-------:|:-----------:|:-----------:|:--------:|:------:|:-------:|
    |      14 | Trimestre 1 |  NaN        | NaN      | NaN    |     NaN |
    |      -3 | Clap        |   -0.230769 | clap     | parent |     NaN |
    |      -2 | super clap  |   -0.166667 | clap     | child  |       1 |
    |      -1 | clap clap   |   -1        | clap     | child  |      10 |
    |      99 | Snare       |   99        | snare    | parent |     NaN |
    |      99 | tac         |   99        | snare    | child  |       1 |
    |       1 | Tom         |  inf        | tom      | parent |     NaN |
    |       1 | bom         |  inf        | tom      | child  |       1 |
    |     111 | Trimester 2 |  NaN        | NaN      | NaN    |     NaN |
    """

    if len(df) == 0:
        return df

    if filters is not None:
        if isinstance(filters, str):
            filters = [filters]

        def sub_waterfall(df):
            wa_df = waterfall(df, date, value, start, end, upperGroup, insideGroup)
            for filters_col in filters:
                wa_df[filters_col] = df[filters_col].values[0]
            return wa_df

        # filters df into a list of sub_df
        list_of_sub_df = [df[(df[filters].values == i).all(axis=1)]
                          for i in df[filters].drop_duplicates().values]

        return pd.concat([sub_waterfall(df) for df in list_of_sub_df], sort=False)

    groups = {
        'upperGroup': {
            'type': 'parent',
            'id':  'upperGroup',
            'order': {
                'by': ['upperGroup_order', 'groups'],
                'ascending': [True, True]
            },
            'obj': upperGroup
        }
    }
    if insideGroup is not None:
        groups['insideGroup'] = {
            'type': 'child',
            'id': 'insideGroup',
            'order': {
                'by': ['type', 'insideGroup_order', 'label'],
                'ascending': [False, True, True]
            },
            'obj': insideGroup
        }
    # prepare the dataframe with standard column names
    df = _compute_rename(df, date, value, groups)

    agg_conf = {'value': sum}
    agg_conf.update({f'{col}_label': 'first' for col in groups.keys()})
    agg_conf.update({f'{col}_order': 'first' for col in groups.keys()})
    df = df.groupby(list(groups.keys()) + ['date']).agg(agg_conf).reset_index()

    df_start, df_end = _compute_start_end(df, start, end)

    df = _compute_value_diff(df, start, end, groups)

    middle = _compute_upper_group(df)
    if insideGroup is not None:
        middle = pd.concat([middle, _compute_inside_group(df)])

    ret = _compute_order(df_start, df_end, middle, groups)

    return ret
Return a line for each bars of a waterfall chart, totals, groups, subgroups.
    Compute the variation and variation rate for each line.

    ---

    ### Parameters

    *mandatory :*
    - `date` (*str*): name of the column that id the period of each lines
    - `value` (*str*): name of the column that contains the vaue for each lines
    - `start` (*dict*):
        - `label`: text displayed under the first master column
        - `id`: value in the date col that id lines for the first period
    - `end` (*dict*):
        - `label`: text displayed under the last master column
        - `id`: value in the date col that id lines for the second period

    *optional :*
    - `upperGroup` (*dict*):
        - `id`: name of the column that contains upperGroups unique IDs
        - `label`: not required, text displayed under each upperGroups bars,
          using ID when it's absent
        - `groupsOrder`: not required, order of upperGroups
    - `insideGroup` (*dict*):
        - `id`: name of the column that contains insideGroups unique IDs
        - `label`: not required, text displayed under each insideGroups bars,
          using ID when it's absent
        - `groupsOrder`: not required, order of insideGroups
    - `filters` (*list*): columns to filters on

    ---

    ### Example

    **Input**

    | product_id   |   played | date   |   ord | category_id   | category_name   |
    |:------------:|:--------:|:------:|:-----:|:-------------:|:---------------:|
    | super clap   |       12 | t1     |     1 | clap          | Clap            |
    | clap clap    |        1 | t1     |    10 | clap          | Clap            |
    | tac          |        1 | t1     |     1 | snare         | Snare           |
    | super clap   |       10 | t2     |     1 | clap          | Clap            |
    | tac          |      100 | t2     |     1 | snare         | Snare           |
    | bom          |        1 | t2     |     1 | tom           | Tom             |


    ```cson
    waterfall:
      upperGroup:
        id: 'category_id'
        label: 'category_name'
      insideGroup:
        id: 'product_id'
        groupsOrder: 'ord'
      date: 'date'
      value: 'played'
      start:
        label: 'Trimestre 1'
        id: 't1'
      end:
        label: 'Trimester 2'
        id: 't2'
    ```

    **Output**

    |   value | label       |   variation | groups   | type   |   order |
    |:-------:|:-----------:|:-----------:|:--------:|:------:|:-------:|
    |      14 | Trimestre 1 |  NaN        | NaN      | NaN    |     NaN |
    |      -3 | Clap        |   -0.230769 | clap     | parent |     NaN |
    |      -2 | super clap  |   -0.166667 | clap     | child  |       1 |
    |      -1 | clap clap   |   -1        | clap     | child  |      10 |
    |      99 | Snare       |   99        | snare    | parent |     NaN |
    |      99 | tac         |   99        | snare    | child  |       1 |
    |       1 | Tom         |  inf        | tom      | parent |     NaN |
    |       1 | bom         |  inf        | tom      | child  |       1 |
    |     111 | Trimester 2 |  NaN        | NaN      | NaN    |     NaN |