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)
        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 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: 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="Abs", 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.

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

    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.

    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".


    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_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.

    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 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 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)]
        Dup_Sample.drop('Sample_ID_{}_Num'.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 = phase_comp

        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)
        Sample_name_num = np.empty((duplicates * len(Data)), dtype=float)

        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)):

            Sample_name_num[i * duplicates:(i * duplicates + duplicates)] = 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

    if positive is True:
        mynoisedDataframe[mynoisedDataframe < 0] = 0
        print('All negative numbers replaced with zeros. '\
        'If you wish to keep these, set positive=False')
    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: DataFrame
        Panda DataFrame of liquid compositions for end-member 1, with column headings SiO2_Liq etc.

    endmember2: 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
        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


