Simulating Auto-Correlated Data in Python: Code conversion
In the previous article, we explored a detailed R script designed to simulate measurement data across a large number of individuals. This script utilized R’s dplyr and other powerful tools to efficiently generate and manipulate synthetic data, offering insights into complex data structures and statistical modeling.
In this article, we will implement the same logic using Python. By leveraging pandas and numpy, we will recreate the data generation process while maintaining the integrity of the original R workflow. This is a Python exercise for me, in aims to provide a comparison between the R and Python approaches in order to gain a deeper understanding of how to adapt data simulation techniques across different programming environments.
Get Simon Greaves’s stories in your inbox
Join Medium for free to get updates from this writer.
The code can be found here: GitHub repo
Import Libraries
We start by importing essential Python libraries for data manipulation and visualization:
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as pltSet the random seed:
np.random.seed(42)Define Parameters
Next, we define the parameters for our simulation:
- Number of instances (
n_instances): Represents the total number of individuals in the study. - Lambda parameter (
lambda_param): Used to determine the number of measurements for each individual, following an exponential distribution. This value cannot be named simply ‘lambda’ like in the R version of this code, this is because in Python lambda is a key word, reserved for the creation of anonymous functions. - Gender Probability (
prob_male): The likelihood of an individual being male. - Measurement Baseline and Offsets: Parameters such as the baseline value of measurements (
baseline), gender-specific offsets, and the slopes indicating how measurements change over time.
Simulate Individual Profiles
Using these parameters, we set up a pandas DataFrame to represent individual profiles:
wide = pd.DataFrame({
'id': np.arange(1, n_instances + 1),
'total_measurements': np.ceil(np.random.exponential(1/lambda_param, n_instances)).astype(int),
'gender': np.random.binomial(1, prob_male, n_instances),
'id_offset': np.random.normal(0, 1, n_instances)
})id: A unique identifier for each individual.total_measurements: Number of measurements each individual will have, derived using an exponential distribution.gender: A binary indicator (0 for female, 1 for male) assigned based on the specified probability.id_offset: A random offset value for each individual's measurements to introduce some variation.
Expand to a “Long” Format
Next, we reshape the data into a “long” format, where each measurement is represented by a row:
long = wide.loc[wide.index.repeat(wide['total_measurements'])].reset_index(drop=True)In this step:
locandrepeat: Create multiple rows for each individual based on their total number of measurements. It’s optional to reset the index.
We then add a simulated time component to represent days between measurements and simulate measurement success:
long['days'] = np.random.uniform(0, max_days, len(long))
long['day'] = long.groupby('id')['days'].cumsum()
long['n_appointment'] = long.groupby('id').cumcount() + 1
long['successful_measurement'] = np.random.binomial(1, measurement_prob, len(long))
long['n_measurements'] = long.groupby('id')['successful_measurement'].cumsum()
long['n_successful_measurements'] = long.groupby('id')['successful_measurement'].transform('sum')days: Randomly generated intervals (in days) between measurements.day: Cumulative sum ofdays, giving the total time elapsed for each measurement.n_appointment: Tracks the appointment number (or measurement count) for each individual.successful_measurement: Indicates if a given measurement was successful (1) or not (0).n_measurements: Cumulative count of attempts for each individual.n_successful_measurements: Total successful measurements for each individual.
Calculate Measurement Values
Finally, we compute the measurement values based on gender, baseline values, slopes, and individual offsets. We need to ensures that only successful measurements receive a value using np.where(long[‘successful_measurement’] == 1, if_true, if_false), then we calculate the if_true value using the baseline plus id_offset value for every individual and add the male_baseline_offset and male_slope_offset to males only:
long['value'] = np.where(
long['successful_measurement'] == 1,
baseline + long['id_offset'] + long['gender'] * male_baseline_offset +
slope * long['day'] + long['gender'] * male_slope_offset * long['day'] +
np.random.normal(0, 1, len(long)),
np.nan
)np.random.normal: Adds random noise to simulate variability.- Note that if the measurement is not successful (
long[‘successful_measurement’] == 1) thenvaluewill benp.nan.
Visualization
The final step is to view the data and assess the results.
fig = sns.lmplot(x='day',
y='value',
hue='sex',
palette={'M': '#4a90e2', 'F': '#e57373'},
scatter_kws={'alpha': 0.2},
data=long)
# Add title
plt.title('Regression of values by gender')
# Set x and y labels
fig.set_axis_labels('Days', 'Value')Conclusion
This example demonstrates how Python can be used to simulate complex datasets. Such simulated data is valuable for testing and validating analytical models and approaches before applying them to real-world data.










