<reponame>dgr113/helpful-vectors
# coding: utf-8

import numpy as np
import pandas as pd
from typing import Iterable, Any, List, Union
from functools import partial
from more_itertools import always_iterable
from helpful_vectors.type_hints import TABLE_DATA_TYPE




def convert_na_values(df: pd.DataFrame, value: Any = None) -> pd.DataFrame:
    """ Конвертировать все типы <numpy/pandas> в типы Python
            (например можно использовать перед сериализацией в json)
    """

    # noinspection PyUnresolvedReferences
    result = df.astype(object).where(df.notnull(), value)
    return result




def get_consecutive_segments(
    data: TABLE_DATA_TYPE,
    columns: Union[str, List[str], None] = None

) -> pd.Series:

    """ Получить индексы последовательностей с одинаковыми идущими подряд элементами

        >>> df = pd.DataFrame({'A': [1,1,1, 4,4,4,4, 3,3], 'B': [1,1,4, 4,4,4,3, 3,3]})
        >>> df_res = df.reset_index().groupby(['A', 'B'])['index'].apply(np.array)
        >>> print(df_res)
                A  B
        1  1       [0, 1]
           4          [2]
        3  3       [7, 8]
        4  3          [6]
           4    [3, 4, 5]
    """
    if not isinstance(data, pd.DataFrame):
        data = pd.DataFrame(data)

    ### Если переданы конкретные столбцы для группировки - используем их, иначе - используем все столбцы
    if columns is not None:
        columns = data.columns.intersection(always_iterable(columns))
    else:
        columns = data.columns.tolist()

    results = data.reset_index().groupby(columns)['index'].apply(np.array)

    return results




def cartesian_product(
    arrays: Iterable[Iterable],
    dtype=None,
    try_type_conversion: bool = False,
    verbose_mode: bool = True

) -> np.array:

    """ Декартово произведение

        :param arrays: Входная матрица в виде списка списков
        :param dtype: Тип данных в <arrays>
        :param try_type_conversion: При возникновении ошибки попытаться выполнить процедуру с конвертирование dtype=object
        :type verbose_mode: Подробный вывод
    """

    def _apply(dtype):
        res = np.empty_like(ix, dtype=dtype)
        for n, arr in enumerate(arrays):
            res[:, n] = arrays[n][ix[:, n]]

        return res


    arrays = list(map(
        np.asarray,
        (
            x if isinstance(x, Iterable) and not isinstance(x, str) else [x, ]
            for x in arrays
        )
    ))

    shape = map(len, arrays)
    dtype = dtype or arrays[0].dtype

    ix = np.indices(shape)
    ix = ix.reshape(len(arrays), -1).T


    try:
        results = _apply(dtype=dtype)

    except (TypeError, ValueError) as e:
        if try_type_conversion:
            results = _apply(dtype='object')
            if verbose_mode:
                print(e)

        else: raise e

    return results




def explode_dataframe_rows(X: pd.DataFrame, dtype=None, try_type_conversion=False, verbose_mode=True) -> pd.DataFrame:
    """ Разбивка ("разрыв") строк DataFrame, где элементы - массивы, на новые строки
        (на основе последовательного Декартового произведения элементов(массивов) каждой исходной строки)

        >>> df = pd.DataFrame({'A': [np.nan, [1, 2]], 'B': [[11, 22], 'first']})
        >>> print(df)
             A         B
        0  [1]  [22, 33]
        1  [5]      [55]

        >>> print(explode_dataframe_rows(df, dtype=float, try_type_conversion=True, verbose_mode=False))
             A      B
        0  NaN     11
        1  NaN     22
        2    1  first
        3    2  first
    """

    ### Если не сделать проверку на НЕпустой <DataFrame>, то полезут ошибки в функциях <numpy>
    if not X.empty:
        extended_rows = np.vstack(X.apply(
            ### все элементы должны быть массивами для корректной работы <cartesian>
            partial(cartesian_product, dtype=dtype, try_type_conversion=try_type_conversion, verbose_mode=verbose_mode),
            axis=1
        ))
        result = pd.DataFrame(extended_rows, columns=X.columns)

    else: result = pd.DataFrame()

    return result

# coding: utf-8

import numpy as np
import pandas as pd
from typing import Iterable, Any, List, Union
from functools import partial
from more_itertools import always_iterable
from helpful_vectors.type_hints import TABLE_DATA_TYPE




def convert_na_values(df: pd.DataFrame, value: Any = None) -> pd.DataFrame:
    """ Конвертировать все типы <numpy/pandas> в типы Python
            (например можно использовать перед сериализацией в json)
    """

    # noinspection PyUnresolvedReferences
    result = df.astype(object).where(df.notnull(), value)
    return result




def get_consecutive_segments(
    data: TABLE_DATA_TYPE,
    columns: Union[str, List[str], None] = None

) -> pd.Series:

    """ Получить индексы последовательностей с одинаковыми идущими подряд элементами

        >>> df = pd.DataFrame({'A': [1,1,1, 4,4,4,4, 3,3], 'B': [1,1,4, 4,4,4,3, 3,3]})
        >>> df_res = df.reset_index().groupby(['A', 'B'])['index'].apply(np.array)
        >>> print(df_res)
                A  B
        1  1       [0, 1]
           4          [2]
        3  3       [7, 8]
        4  3          [6]
           4    [3, 4, 5]
    """
    if not isinstance(data, pd.DataFrame):
        data = pd.DataFrame(data)

    ### Если переданы конкретные столбцы для группировки - используем их, иначе - используем все столбцы
    if columns is not None:
        columns = data.columns.intersection(always_iterable(columns))
    else:
        columns = data.columns.tolist()

    results = data.reset_index().groupby(columns)['index'].apply(np.array)

    return results




def cartesian_product(
    arrays: Iterable[Iterable],
    dtype=None,
    try_type_conversion: bool = False,
    verbose_mode: bool = True

) -> np.array:

    """ Декартово произведение

        :param arrays: Входная матрица в виде списка списков
        :param dtype: Тип данных в <arrays>
        :param try_type_conversion: При возникновении ошибки попытаться выполнить процедуру с конвертирование dtype=object
        :type verbose_mode: Подробный вывод
    """

    def _apply(dtype):
        res = np.empty_like(ix, dtype=dtype)
        for n, arr in enumerate(arrays):
            res[:, n] = arrays[n][ix[:, n]]

        return res


    arrays = list(map(
        np.asarray,
        (
            x if isinstance(x, Iterable) and not isinstance(x, str) else [x, ]
            for x in arrays
        )
    ))

    shape = map(len, arrays)
    dtype = dtype or arrays[0].dtype

    ix = np.indices(shape)
    ix = ix.reshape(len(arrays), -1).T


    try:
        results = _apply(dtype=dtype)

    except (TypeError, ValueError) as e:
        if try_type_conversion:
            results = _apply(dtype='object')
            if verbose_mode:
                print(e)

        else: raise e

    return results




def explode_dataframe_rows(X: pd.DataFrame, dtype=None, try_type_conversion=False, verbose_mode=True) -> pd.DataFrame:
    """ Разбивка ("разрыв") строк DataFrame, где элементы - массивы, на новые строки
        (на основе последовательного Декартового произведения элементов(массивов) каждой исходной строки)

        >>> df = pd.DataFrame({'A': [np.nan, [1, 2]], 'B': [[11, 22], 'first']})
        >>> print(df)
             A         B
        0  [1]  [22, 33]
        1  [5]      [55]

        >>> print(explode_dataframe_rows(df, dtype=float, try_type_conversion=True, verbose_mode=False))
             A      B
        0  NaN     11
        1  NaN     22
        2    1  first
        3    2  first
    """

    ### Если не сделать проверку на НЕпустой <DataFrame>, то полезут ошибки в функциях <numpy>
    if not X.empty:
        extended_rows = np.vstack(X.apply(
            ### все элементы должны быть массивами для корректной работы <cartesian>
            partial(cartesian_product, dtype=dtype, try_type_conversion=try_type_conversion, verbose_mode=verbose_mode),
            axis=1
        ))
        result = pd.DataFrame(extended_rows, columns=X.columns)

    else: result = pd.DataFrame()

    return result
# coding: utf-8 Конвертировать все типы <numpy/pandas> в типы Python             (например можно использовать перед сериализацией в json) # noinspection PyUnresolvedReferences Получить индексы последовательностей с одинаковыми идущими подряд элементами          >>> df = pd.DataFrame({'A': [1,1,1, 4,4,4,4, 3,3], 'B': [1,1,4, 4,4,4,3, 3,3]})         >>> df_res = df.reset_index().groupby(['A', 'B'])['index'].apply(np.array)         >>> print(df_res)                 A  B         1  1       [0, 1]            4          [2]         3  3       [7, 8]         4  3          [6]            4    [3, 4, 5] ### Если переданы конкретные столбцы для группировки - используем их, иначе - используем все столбцы Декартово произведение          :param arrays: Входная матрица в виде списка списков         :param dtype: Тип данных в <arrays>         :param try_type_conversion: При возникновении ошибки попытаться выполнить процедуру с конвертирование dtype=object         :type verbose_mode: Подробный вывод Разбивка ("разрыв") строк DataFrame, где элементы - массивы, на новые строки         (на основе последовательного Декартового произведения элементов(массивов) каждой исходной строки)          >>> df = pd.DataFrame({'A': [np.nan, [1, 2]], 'B': [[11, 22], 'first']})         >>> print(df)              A         B         0  [1]  [22, 33]         1  [5]      [55]          >>> print(explode_dataframe_rows(df, dtype=float, try_type_conversion=True, verbose_mode=False))              A      B         0  NaN     11         1  NaN     22         2    1  first         3    2  first ### Если не сделать проверку на НЕпустой <DataFrame>, то полезут ошибки в функциях <numpy> ### все элементы должны быть массивами для корректной работы <cartesian>