Please add it so it takes over the random function in this code:

import pandas as pd
import random
import numpy as np
import matplotlib.pyplot as plt

def generate_images(num_samples, num_images):
    # Take list (with 3 equal chunks)
    # Give each chunk the same number of distortions
    chunk_size = num_samples // 3

    imgs_chunk1 = random.sample(range(1, num_images // 3 + 1), chunk_size)
    imgs_chunk2 = random.sample(range(num_images // 3 + 1, 2 * num_images // 3 + 1), chunk_size)
    imgs_chunk3 = random.sample(range(2 * num_images // 3 + 1, num_images + 1), chunk_size)
    imgs = imgs_chunk1 + imgs_chunk2 + imgs_chunk3

    # Create letters for each chunk
    distortions = [f'{d}{l}' for d in [1, 2, 3] for l in ['a', 'b', 'c']] + ['original']
    num_distortions = len(distortions)
    num_chunks = 3

    let = pd.Series(distortions * (num_samples // (num_distortions * num_chunks)))
    # Combine chunks, each randomized
    test = pd.concat([let.sample(frac=1) for _ in range(num_chunks)], ignore_index=True)

    # Check that each chunk has the same number of letters
    test = test.astype('category')  # Convert to category for overview

    # Combine and shuffle
    images = pd.DataFrame({'imgs': imgs, 'test': test})

    return images

num_samples = 420  # Default 420
num_images = 540  # Default 540
num_observers = 10000  # Number of observers

# Set the seed for reproducibility
seed = run+38595
random.seed(seed)
np.random.seed(seed)

# Create an empty list to store the DataFrames for each observer
observer_dfs = []

# Generate images for each observer and store the DataFrames in the list
for observer in range(1, num_observers + 1):
    observer_images = generate_images(num_samples, num_images)
    observer_images['observer_nr'] = observer  # Add observer number column
    observer_dfs.append(observer_images)

# Concatenate the DataFrames from each observer into a single result DataFrame
result_df = pd.concat(observer_dfs, ignore_index=True)

# Convert 'imgs' and 'test' columns to category data type for overview
result_df['imgs'] = result_df['imgs'].astype('category')
result_df['test'] = result_df['test'].astype('category')

# Count the occurrences of each combination
combination_counts = result_df.groupby(['imgs', 'test']).size().reset_index(name='count')

# Measure the variance in the count
variance = np.var(combination_counts['count'])
sum(combination_counts['count']<=2)
sum(combination_counts['count']>=11)
np.mean(combination_counts['count'])

            
def combine_data2(result_df, num_subsetting_range):
    previous_variance = None
    for num_subsetting in num_subsetting_range:
        random.seed(num_subsetting)
        combined_df=result_df[result_df['observer_nr'].isin(random.sample(range(1,10000),80))]
        combination_counts = combined_df.groupby(['imgs', 'test']).size().reset_index(name='count')
        variance = np.var(combination_counts['count'])
        #print(variance)
        if previous_variance is None or variance < previous_variance:
            print(f"num_subsetting: {num_subsetting}, variance: {variance}")
            previous_variance = variance            

Currently we take 80 random digits with this function: combined_df=result_df[result_df['observer_nr'].isin(random.sample(range(1,10000),80))]

And evaluate which random numbers have the lowest variance here:
combination_counts = combined_df.groupby(['imgs', 'test']).size().reset_index(name='count')
variance = np.var(combination_counts['count'])

I want a genetic algorithm that gradually replaces a few of the 80 numbers at a time.