import numpy as np
import matplotlib.pyplot as plt
from functools import partial
import inspect
import warnings as w
import numbers
import pandas as pd
from Thermobar.core import *

def av_noise_samples_series(calc, sampleID):
    '''
    This function calculates the mean, median, standard devation, maximum and
    minimum value of rows specified by "calc" based on values in "Sample ID" where both inputs are panda series.

    Parameters
    -------
    calc: Series
        Panda series of inputs you want to average.
    SampleID: str
        column heading for the thing you want to average by (e.g., Sample_ID_Cpx)

    Returns
    -------

    Dataframe with headings "Sample", "Mean_calc", "Median_calc",
    "St_dev_calc", "Max_calc", "Min_calc"

    '''


    if isinstance(calc, pd.Series):
        N = sampleID.unique()
        Av_mean = np.empty(len(N), dtype=float)
        Av_median = np.empty(len(N), dtype=float)
        Max = np.empty(len(N), dtype=float)
        Min = np.empty(len(N), dtype=float)
        Std = np.empty(len(N), dtype=float)
        i=0
        for ID in sampleID.unique():
            sam=ID
            Av_mean[i] = np.nanmean(calc[sampleID == sam])
            Av_median[i] = np.nanmedian(calc[sampleID == sam])
            Std[i] = np.nanstd(calc[sampleID == sam])
            Min[i] = np.nanmin(calc[sampleID == sam])
            Max[i] = np.nanmax(calc[sampleID == sam])

            i=i+1
    len1=len(calc[sampleID == sam])
    Err_out = pd.DataFrame(data={'Sample': N, '# averaged': len1, 'Mean_calc': Av_mean,
    'Median_calc': Av_median, 'St_dev_calc': Std,
    'Max_calc': Max, 'Min_calc': Min})

    return Err_out


def av_noise_samples_df(dataframe, calc_heading, ID_heading):
    '''
    This function calculates the mean, median, standard devation, maximum and
    minimum value of rows in a datarame with column heading "calc_heading"
    grouping by values in "ID_heading".
    Parameters
    -------
    dataframe: pandas.DataFrame
        Panda datframe of inputs you want to average.
        Must contain column headings "calc_heading" and "ID_heading".
    calc_heading: str
        column heading for the thing you want to average (e.g, P_kbar_calc)
    ID_heading: str
        column heading for the thing you want to average by (e.g., Sample_ID)

    Returns
    -------

    Dataframe with headings "Sample", "Mean_calc", "Median_calc",
    "St_dev_calc", "Max_calc", "Min_calc"

    '''
    calc=dataframe[calc_heading]
    sampleID=dataframe[ID_heading]
    if isinstance(calc, pd.Series):
        N = sampleID.unique()
        Av_mean = np.empty(len(N), dtype=float)
        Av_median = np.empty(len(N), dtype=float)
        Max = np.empty(len(N), dtype=float)
        Min = np.empty(len(N), dtype=float)
        Std = np.empty(len(N), dtype=float)
        for i in range(0, len(N)):
            Av_mean[i] = np.nanmean(calc[sampleID == i])
            Av_median[i] = np.nanmedian(calc[sampleID == i])
            Std[i] = np.nanstd(calc[sampleID == i])
            Min[i] = np.nanmin(calc[sampleID == i])
            Max[i] = np.nanmax(calc[sampleID == i])

    Err_out = pd.DataFrame(data={'Sample': N, 'Mean_calc': Av_mean,
    'Median_calc': Av_median, 'St_dev_calc': Std,
    'Max_calc': Max, 'Min_calc': Min})

    return Err_out


def add_noise_sample_1phase(phase_comp, phase_err=None,
phase_err_type="Abs",
variable=None, variable_err=None, variable_err_type=None, duplicates=10,
noise_percent=None, err_dist="normal", positive=True,
filter_q=None, append=False):
    '''
    This function generates N duplicates containing random noise from the
    compositions in the dataframe specified by phase_comp.


    Parameters
    -------

    Phase Comps: pandas dataframe
        Pandas dataframe of phase compositions. This can be generated
        from the import_excel function, or any dataframe with the
        headings _Liq for liquids, _Cpx for clinopyroxenes etc.

    Options for adding different types of error:

    1) If you want to specifying an error for >1 variable:

        phase_err: pandas dataframe
            Pandas dataframe with headings for the error of the oxide in each
            phase (e.g., SiO2_Liq_Err, or SiO2_Cpx_Err).
            This dataframe can be generated from a user-inputted spreadsheet
            with these column headings using the function import_excel_errors.
            Errors can be absolute, or percentage errors.
            the default is absolute errors (in wt%), but users can overwrite
            this using phase_err_Type="Perc".


        phase_err_type: "Abs" (default) or "Perc"
            Determins if specified errors are absolute (Abs) or percentage errors.

    2) If you want to specify error for a single variable:

        variable: str
            Name of column you wish to add error to (e.g. "Na2O" for Na2O in Liq)

        variable_err: flt, int
            Specifies how much error to add

    3) If you want to add a fixed percent of noise to all variables.

        noise_percent: flt, int
            Adds a fixed noise percent to all input variables.




    duplicates: flt, int (Default: 10)
        Number of new synthetic samples generated per sample in the original
        dataframe. E.g., if the user enteres 7 samples, and duplicates=1000,
        the function returns 7000 compositions by default.
        If append=True, the original dataframe is appended onto the end of the
        returned dataframe

    err_dist: "normal" (default) or "uniform"
        determins whether added error is normally distributed with
        1 sigma = entered value.
        Or uniformly distributed between +noise value and - noise value.

    positive: True (default) or False
        If True, doesn't allow negative values of oxide species,
        temperature or pressure. Can result in a non-normally distributed
        error distribution. If False, negative values are allowed.

    filter_q: str
        Filter criteria, e.g. if SiO2_Liq>60,
        only returns samples with SiO2_Liq>60

    append: False (default) or True
        If True, appends user-entered dataframe onto the synthetic dataframe
        once noise has been added.



    Returns
    -------

    Panda dataframe containing user-inputted samples with noise added.
    The output is sorted such that the first row in the input * the number
    of noise samples requested are the first N rows, then the new synthetic
    compositions for the second row in the input database are next.
    A heading called "Sample_ID_Liq_Num" is added, with all synthetic samples
    from the first row in the input dataframe have an index 0, the 2nd row have
    an index 1. etc.

    '''

    # if variable_err is not None:
    #     if (type(variable_err) is not float) and (type(variable_err) is not int) and (type(variable_err) is not np.ndarray):
    #         raise Exception('variable error must be a float, integer, or np.ndarray. If youve entered a pandas series, do series.values')
    #
    if variable is not None and noise_percent is not None:
        raise Exception('noise_percent is an arguement on its own '
        'it adds noise to all variables. Either specify variable or '
        'noise_percent not both')
    if variable_err is not None and noise_percent is not None:
        raise Exception('noise_percent adds noise to all variables' \
        'while variable_err adds noise to a single variable'\
        'specify only one of these arguements')
    if filter_q is not None:
        Sample_c = phase_comp.query(filter_q).copy()

    else:
        Sample_c = phase_comp.copy()
    if phase_err is not None and noise_percent is not None:
        raise Exception('You have entered both a dataframe of noise and '\
        'specified a percent noise. Select only 1 of these options')

    # This works out what phase you have entered data for
    if any(Sample_c.columns.str.contains("_Cpx")):
        elx = 'Cpx'
    if any(Sample_c.columns.str.contains("_Plag")):
        elx = 'Plag'
    if any(Sample_c.columns.str.contains("_Opx")):
        elx = 'Opx'
    if any(Sample_c.columns.str.contains("_Sp")):
        elx = 'Sp'
    if any(Sample_c.columns.str.contains("_Kspar")):
        elx = 'Kspar'
    if any(Sample_c.columns.str.contains("_Amp")):
        elx = 'Amp'
    if any(Sample_c.columns.str.contains("_Liq")):
        elx = 'Liq'
    if any(Sample_c.columns.str.contains("_Ol")):
        elx = 'Ol'

    if any(Sample_c.columns.str.contains('Sample_ID_{}'.format(elx))):
        name=True
    else:
        Sample_c['Sample_ID_{}'.format(elx)]='No Name Entered'

    if len(Sample_c['Sample_ID_{}'.format(elx)].unique() ) !=  len(Sample_c):
        w.warn('Non unique sample names. We have appended the index onto all sample names to save issues with averaging later')
        TEST=Sample_c.index.values
        for i in range(0, len(Sample_c)):
            Sample_c.loc[i, 'Sample_ID_Liq']=Sample_c['Sample_ID_Liq'].iloc[i]+'_'+str(TEST[i])


    if phase_err is None or (phase_err is not None and err_dist == "uniform"):

        Sample_c['Sample_ID_{}_Num'.format(elx)] = Sample_c.index

        # This duplicates your entered composition the number of times
        # specified by noise samples (Cpx1-Cpx1-Cpx1, Cpx2, Cpx2,...)
        Dup_Sample = pd.DataFrame(
            np.repeat(Sample_c.values, duplicates, axis=0))
        Dup_Sample.columns = Sample_c.columns

        # Dropping sample name so it doesnt get averaged.
        Sample_name_num = Dup_Sample['Sample_ID_{}_Num'.format(elx)]
        Sample_name_str=Dup_Sample['Sample_ID_{}'.format(elx)]
        Dup_Sample.drop('Sample_ID_{}_Num'.format(elx), axis=1, inplace=True)
        Dup_Sample.drop('Sample_ID_{}'.format(elx), axis=1, inplace=True)

        if variable is not None:

            ely = variable
            if variable == "P_kbar" or variable == "T_K":
                if variable_err_type == "Abs":
                    if err_dist == "normal":
                        Noise = np.random.normal(0, variable_err,
                        Dup_Sample.shape[0])
                    if err_dist == "uniform":
                        Noise = np.random.uniform(- variable_err, +
                        variable_err, Dup_Sample.shape[0])
                if variable_err_type == "Perc":
                    variable_err_abs = Dup_Sample['{}'.format(
                        ely)] * (variable_err / 100)
                    if err_dist == "normal":
                        Noise = np.random.normal(
                        0, variable_err_abs, Dup_Sample.shape[0])
                    if err_dist == "uniform":
                        Noise = np.random.uniform(- variable_err_abs, +
                        variable_err_abs, Dup_Sample.shape[0])

                mynoisedDataframe = Dup_Sample.copy()

                mynoisedDataframe['{}'.format(
                    ely)] = mynoisedDataframe['{}'.format(ely)] + Noise

            else:
                if variable_err_type == "Abs":
                    if err_dist == "normal":
                        Noise = np.random.normal(
                            0, variable_err, Dup_Sample.shape[0])
                    if err_dist == "uniform":
                        Noise = np.random.uniform(- variable_err, +
                                                  variable_err, Dup_Sample.shape[0])
                if variable_err_type == "Perc":
                    variable_err_abs = Dup_Sample['{}_{}'.format(
                        ely, elx)] * (variable_err / 100)
                    if err_dist == "normal":
                        Noise = np.random.normal(
                            0, variable_err_abs, Dup_Sample.shape[0])
                    if err_dist == "uniform":
                        Noise = np.random.uniform(- variable_err_abs, +
                                                  variable_err_abs, Dup_Sample.shape[0])

                mynoisedDataframe = Dup_Sample.copy()
                mynoisedDataframe['{}_{}'.format(
                    ely, elx)] = mynoisedDataframe['{}_{}'.format(ely, elx)] + Noise

        if noise_percent is not None and err_dist == "uniform":
            noise = np.random.uniform(- noise_percent /
                                      100, + noise_percent / 100, Dup_Sample.shape)
            mynoisedDataframe = Dup_Sample + Dup_Sample * noise
        if noise_percent is not None and err_dist == "normal":
            noise = np.random.normal(0, noise_percent / 100, Dup_Sample.shape)
            mynoisedDataframe = Dup_Sample + Dup_Sample * noise



        if phase_err is not None and err_dist == "uniform":
            Sample_Err = phase_err.copy()
            Dup_Noise = pd.DataFrame(
                np.repeat(Sample_Err.values, duplicates, axis=0))
            Dup_Noise.columns = Sample_Err.columns
            noise = np.random.uniform(1, -1, Dup_Noise.shape)
            mynoisedDataframe = (Dup_Noise * noise).to_numpy() + Dup_Sample

    if phase_err is not None and err_dist == "normal":

        # This is for when users enter 2 dataframes, 1 of measurements, 1 of 1
        # sigma errors
        Data = Sample_c




        SiO2_Err = np.empty((duplicates * len(Data)), dtype=float)
        TiO2_Err = np.empty((duplicates * len(Data)), dtype=float)
        Al2O3_Err = np.empty((duplicates * len(Data)), dtype=float)
        FeOt_Err = np.empty((duplicates * len(Data)), dtype=float)
        MnO_Err = np.empty((duplicates * len(Data)), dtype=float)
        MgO_Err = np.empty((duplicates * len(Data)), dtype=float)
        CaO_Err = np.empty((duplicates * len(Data)), dtype=float)
        Na2O_Err = np.empty((duplicates * len(Data)), dtype=float)
        K2O_Err = np.empty((duplicates * len(Data)), dtype=float)
        Cr2O3_Err = np.empty((duplicates * len(Data)), dtype=float)
        NiO_Err = np.empty((duplicates * len(Data)), dtype=float)
        P2O5_Err = np.empty((duplicates * len(Data)), dtype=float)
        H2O_Err = np.empty((duplicates * len(Data)), dtype=float)
        P_kbar_Err = np.empty((duplicates * len(Data)), dtype=float)
        T_K_Err = np.empty((duplicates * len(Data)), dtype=float)
        F_Err = np.empty((duplicates * len(Data)), dtype=float)
        Cl_Err = np.empty((duplicates * len(Data)), dtype=float)
        Sample_name_num = np.empty((duplicates * len(Data)), dtype=float)
        Sample_name_str = np.empty((duplicates * len(Data)), dtype=object)

        if phase_err_type == "Abs":
            Err = phase_err
        if phase_err_type == "Perc":
            Err_perc = phase_err.copy()
            # removing headings so can multiply 2 pandas
            Err_perc.columns = Err_perc.columns.str.replace('_Err', '')
            Err = Data * (Err_perc / 100)
            # adding Err back in
            Err.columns = [str(col) + '_Err' for col in Err.columns]

        for i in range(0, len(Data)):

            if len(Err) != len(Data):
                raise Exception('Your data and error input data frames arent the same length')
            Sample_name_num[i * duplicates:(i * duplicates + duplicates)] = i
            Sample_name_str[i * duplicates:(i * duplicates + duplicates)] = Data['Sample_ID_{}'.format(elx)].iloc[i]


            SiO2_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['SiO2_{}'.format(
                elx)].iloc[i], scale=Err['SiO2_{}_Err'.format(elx)].iloc[i], size=duplicates)

            TiO2_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['TiO2_{}'.format(
                elx)].iloc[i], scale=Err['TiO2_{}_Err'.format(elx)].iloc[i], size=duplicates)

            Al2O3_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['Al2O3_{}'.format(
                elx)].iloc[i], scale=Err['Al2O3_{}_Err'.format(elx)].iloc[i], size=duplicates)

            FeOt_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['FeOt_{}'.format(
                elx)].iloc[i], scale=Err['FeOt_{}_Err'.format(elx)].iloc[i], size=duplicates)

            MnO_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['MnO_{}'.format(
                elx)].iloc[i], scale=Err['MnO_{}_Err'.format(elx)].iloc[i], size=duplicates)

            MgO_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['MgO_{}'.format(
                elx)].iloc[i], scale=Err['MgO_{}_Err'.format(elx)].iloc[i], size=duplicates)

            CaO_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['CaO_{}'.format(
                elx)].iloc[i], scale=Err['CaO_{}_Err'.format(elx)].iloc[i], size=duplicates)

            Na2O_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['Na2O_{}'.format(
                elx)].iloc[i], scale=Err['Na2O_{}_Err'.format(elx)].iloc[i], size=duplicates)

            K2O_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['K2O_{}'.format(
                elx)].iloc[i], scale=Err['K2O_{}_Err'.format(elx)].iloc[i], size=duplicates)

            Cr2O3_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['Cr2O3_{}'.format(
                elx)].iloc[i], scale=Err['Cr2O3_{}_Err'.format(elx)].iloc[i], size=duplicates)

            if variable == "P_kbar":


                P_kbar_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['P_kbar'.format(
                    elx)].iloc[i], scale=Err['P_kbar_Err'.format(elx)].iloc[i], size=duplicates)

            if variable == "T_K":
                T_K_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['T_K'.format(
                    elx)].iloc[i], scale=Err['T_K_Err'.format(elx)].iloc[i], size=duplicates)

            if any(Data.columns.str.contains("NiO")):
                NiO_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['NiO_{}'.format(
                    elx)].iloc[i], scale=Err['NiO_{}_Err'.format(elx)].iloc[i], size=duplicates)
            else:
                NiO_Err = 0 * Data['SiO2_{}'.format(elx)]

            if any(Data.columns.str.contains("F_")):
                F_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['F_{}'.format(
                    elx)].iloc[i], scale=Err['F_{}_Err'.format(elx)].iloc[i], size=duplicates)
            else:
                F_Err = 0 * Data['SiO2_{}'.format(elx)]

            if any(Data.columns.str.contains("Cl_")):
                Cl_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['Cl_{}'.format(
                    elx)].iloc[i], scale=Err['Cl_{}_Err'.format(elx)].iloc[i], size=duplicates)
            else:
                Cl_Err = 0 * Data['SiO2_{}'.format(elx)]

            if any(Data.columns.str.contains("P2O5")):
                P2O5_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['P2O5_{}'.format(
                    elx)].iloc[i], scale=Err['P2O5_{}_Err'.format(elx)].iloc[i], size=duplicates)
            else:
                P2O5_Err = 0 * Data['SiO2_{}'.format(elx)]

            if any(Data.columns.str.contains("H2O")):
                H2O_Err[i * duplicates:(i * duplicates + duplicates)] = np.random.normal(loc=Data['H2O_{}'.format(
                    elx)].iloc[i], scale=Err['H2O_{}_Err'.format(elx)].iloc[i], size=duplicates)
            else:
                H2O_Err = 0 * Data['SiO2_{}'.format(elx)]

            if elx == 'Cpx' or elx == "Opx" or elx == "Plag" or elx == "Kspar":
                mynoisedDataframe = pd.DataFrame(data={'SiO2_{}'.format(elx): SiO2_Err, 'TiO2_{}'.format(elx): TiO2_Err, 'Al2O3_{}'.format(elx): Al2O3_Err, 'FeOt_{}'.format(elx): FeOt_Err, 'MnO_{}'.format(
                    elx): MnO_Err, 'MgO_{}'.format(elx): MgO_Err, 'CaO_{}'.format(elx): CaO_Err, 'Na2O_{}'.format(elx): Na2O_Err, 'K2O_{}'.format(elx): K2O_Err, 'Cr2O3_{}'.format(elx): Cr2O3_Err})

            if elx == 'Ol' or elx == "Sp":
                mynoisedDataframe = pd.DataFrame(data={'SiO2_{}'.format(elx): SiO2_Err, 'TiO2_{}'.format(elx): TiO2_Err, 'Al2O3_{}'.format(elx): Al2O3_Err, 'FeOt_{}'.format(elx): FeOt_Err, 'MnO_{}'.format(elx): MnO_Err, 'MgO_{}'.format(elx): MgO_Err, 'CaO_{}'.format(elx): CaO_Err, 'Na2O_{}'.format(elx): Na2O_Err, 'K2O_{}'.format(elx): K2O_Err, 'Cr2O3_{}'.format(elx): Cr2O3_Err,
                                                       'NiO_{}'.format(elx): NiO_Err})
            if elx == "Amp":
                mynoisedDataframe = pd.DataFrame(data={'SiO2_{}'.format(elx): SiO2_Err, 'TiO2_{}'.format(elx): TiO2_Err, 'Al2O3_{}'.format(elx): Al2O3_Err, 'FeOt_{}'.format(elx): FeOt_Err, 'MnO_{}'.format(elx): MnO_Err, 'MgO_{}'.format(elx): MgO_Err, 'CaO_{}'.format(elx): CaO_Err, 'Na2O_{}'.format(elx): Na2O_Err, 'K2O_{}'.format(elx): K2O_Err, 'Cr2O3_{}'.format(elx): Cr2O3_Err,
                                                       'F_{}'.format(elx): F_Err, 'Cl_{}'.format(elx): Cl_Err})
            if elx == "Liq":
                mynoisedDataframe = pd.DataFrame(data={'SiO2_{}'.format(elx): SiO2_Err, 'TiO2_{}'.format(elx): TiO2_Err, 'Al2O3_{}'.format(elx): Al2O3_Err, 'FeOt_{}'.format(elx): FeOt_Err, 'MnO_{}'.format(elx): MnO_Err, 'MgO_{}'.format(elx): MgO_Err, 'CaO_{}'.format(elx): CaO_Err, 'Na2O_{}'.format(elx): Na2O_Err, 'K2O_{}'.format(elx): K2O_Err, 'Cr2O3_{}'.format(elx): Cr2O3_Err,
                                                       'P2O5_{}'.format(elx): P2O5_Err, 'H2O_{}'.format(elx): H2O_Err})
                mynoisedDataframe = mynoisedDataframe.reindex(
                    df_ideal_liq.columns, axis=1).fillna(0)
                mynoisedDataframe = mynoisedDataframe.apply(
                    pd.to_numeric, errors='coerce').fillna(0)

        if variable == "T_K":
            mynoisedDataframe['P_kbar'] = P_kbar_Err
        if variable == "P_kbar":
            mynoisedDataframe['T_K'] = T_K_Err

    mynoisedDataframe['Sample_ID_{}_Num'.format(elx)] = Sample_name_num

    mynoisedDataframe['Sample_ID_{}'.format(elx)] = Sample_name_str

    if positive is True:
        num = mynoisedDataframe._get_numeric_data()
        num[num < 0] = 0
        print('All negative numbers replaced with zeros. '\
        'If you wish to keep these, set positive=False')

    mynoisedDataframe=mynoisedDataframe.fillna(0)
    if append is True:
        mynoisedDataframe2 = pd.concat([Sample_c, mynoisedDataframe], axis=0)
        return mynoisedDataframe2
    else:
        return mynoisedDataframe


def calculate_bootstrap_mixes(
        endmember1, endmember2, num_samples, self_mixing=False):
    '''Specify 2 end-members, generates synthetic liquids from mixing between these end-members

   Parameters
    -------

    endmember1: pandas.DataFrame
        Panda DataFrame of liquid compositions for end-member 1, with column headings SiO2_Liq etc.

    endmember2: pandas.DataFrame
        Panda DataFrame of liquid compositions for end-member 2, with column headings SiO2_Liq etc.

    num_samples: float or int
        If num_samples is less than the length of the end members, will randomly resample liquids entered to get to sufficient N.
        If num_samples greater than length of end members, will randomly downsample liquids to N=num_samples.

    self_mixing: None, False, True, "Partial"
        If None or False, will mix 2 end members in various proportions, but no mixing between end members
        If True, will mix between samples from a given end member as well as between the 2 end members.
        If Partial, half of outputted liquids will be generated by mixing within and between end members, and the other half from mixing between end members.

    Returns:
    -------
    pandas DataFrame
        synthetic liquids generated by mixing between end-members with column headings "SiO2_Liq" etc.
    '''
    Elements = ['SiO2_Liq', 'TiO2_Liq', 'Al2O3_Liq', 'FeOt_Liq', 'FeOt_Liq',
                'MnO_Liq', 'MgO_Liq', 'CaO_Liq', 'Na2O_Liq', 'K2O_Liq', 'Cr2O3_Liq',
                'P2O5_Liq', 'H2O_Liq']
    f = np.repeat(np.random.uniform(0, 1, (num_samples, 1)),
                  (len(Elements)), axis=1)

    # Takes half mixes from self mixing, half from mixing between defined
    # end-members
    if self_mixing == "Partial":
        # self mixing part
        my_dataset3_self = endmember1
        my_dataset3_self = my_dataset3_self.append(
            endmember2, ignore_index=True)
        endmember1_self = my_dataset3_self[Elements].sample(
            n=num_samples, replace=True).to_numpy()
        endmember2_self = my_dataset3_self[Elements].sample(
            n=num_samples, replace=True).to_numpy()
        combined_model_self = endmember1_self * f + endmember2_self * (1 - f)
        # normal mixing part
        endmember1 = endmember1[Elements].sample(
            n=num_samples, replace=True).to_numpy()
        endmember2 = endmember2[Elements].sample(
            n=num_samples, replace=True).to_numpy()
        combined_model = endmember1 * f + endmember2 * (1 - f)
        myDataframe_self = pd.DataFrame()
        for ix, my_el in enumerate(Elements):
            myDataframe_self[my_el] = combined_model_self[:, ix]
        myDataframe_mix = pd.DataFrame()
        for ix, my_el in enumerate(Elements):
            myDataframe_mix[my_el] = combined_model[:, ix]

        myDataframe = pd.concat([myDataframe_mix, myDataframe_self], )
        myDataframe = myDataframe.sample(n=num_samples, replace=True)

    if self_mixing is True:
        my_dataset3 = endmember1
        my_dataset3 = my_dataset3.append(endmember2, ignore_index=True)

        endmember1 = my_dataset3[Elements].sample(
            n=num_samples, replace=True).to_numpy()
        endmember2 = my_dataset3[Elements].sample(
            n=num_samples, replace=True).to_numpy()

    if self_mixing is False or self_mixing is None:
        endmember1 = endmember1[Elements].sample(
            n=num_samples, replace=True).to_numpy()
        endmember2 = endmember2[Elements].sample(
            n=num_samples, replace=True).to_numpy()

    if self_mixing is False or self_mixing is None or self_mixing is True:
        combined_model = endmember1 * f + endmember2 * (1 - f)

        myDataframe = pd.DataFrame()
        for ix, my_el in enumerate(Elements):
            myDataframe[my_el] = combined_model[:, ix]
    myDataframe = myDataframe.fillna(0)
    return myDataframe  # , f, endmember1, endmember2


