I am running the following code to conduct a simulation. I have a dataset of around 100K observations. The simulation does the following:
- It randomly assign 10 "friends" to each observation
- Take the average of the variable
normalized_score
of the 10 friends - Run a regression where the left hand side variable is the
normalized_score
and the left hand side variable is the averagenormalized_score
of the 10 friends - Save the coefficient from the regression
I run this simulation 2500 times. It took around 20 days to finish. I tried parallelization to perform several simulations at once and randomization with replacement to make the code more efficient with no luck (the code crashed). I would like to ideally give each observation a 100 random friends, but I would like to make the code faster before doing this. Could you please provide some suggestions on how to make the iteration in the select_friends
function faster and more efficient?
Here is the python code I wrote. It works well and produces the desired results. It just takes way too long.
I cannot share the data since it is not publicly available.
import pandas as pd
import numpy as np
from pyprojroot.here import here
import statsmodels.api as sm # for linear regression
import random
random.seed(42) # You can use any number as the seed
# Read the CSV file into a DataFrame
dir = "path"
data1 = pd.read_csv(dir + 'data1.csv')
data2 = pd.read_csv("path")
data = pd.merge(data1, data2, how='right', on='ID', suffixes=('', '_inSchool'))
def calculate_average_score(data, friends_dict):
average_scores = []
for key in friends_dict:
friends = friends_dict[key]['friends']
friends_scores = data[data['ID'].isin(friends)]['normalized_score'].mean()
average_scores.append({
'ID': key,
'friends_avg_score': friends_scores
})
return pd.DataFrame(average_scores)
def select_friends(data):
friends_dict = {}
for index, row in data.iterrows():
friends = data.sample(n=10, replace=False)
friends_dict[row['ID']] = {
'friends': friends['ID'].tolist()
}
return friends_dict
coefficients = []
for i in range(2500):
friends_dict = select_friends(data)
avg_score_df = calculate_average_score(data, friends_dict)
data = data.merge(avg_score_df, on='ID', how='left')
formula = "normalized_score ~ 1 + friends_avg_score"
ols_model = sm.OLS.from_formula(formula, data=data).fit()
# Extract and store the coefficient of interest (friends_avg_score)
coefficients.append(ols_model.params['friends_avg_score'])
Edit: Here is a code that would generate a sample that is somewhat representative
import pandas as pd
import numpy as np
# Set the random seed for reproducibility
np.random.seed(0)
# Create an ID column
id_column = range(1, 100001)
# Create a normalized_score column with random values from a normal distribution
# The standard normal distribution has a mean of 0 and a standard deviation of 1
# We'll use np.clip to ensure that the values lie within the range [-2, 2]
normalized_score_column = np.clip(np.random.randn(100000), -2, 2)
# Create a DataFrame
data = pd.DataFrame({'ID': id_column, 'normalized_score': normalized_score_column})
# If you want to save this DataFrame to a CSV file, you can use the following line:
# data.to_csv('sample_data.csv', index=False)
# Print the first few rows to check the data
print(data.head())
```
for
loop into a well-named helper function. // The "not publicly available" aspect makes sense. But still, it would be worth your while to generate random records which roughly reproduce the performance issues you're observing. // This submission is about performance, yet it includes no profiling measurements. Describe your big-O complexity. Is it quadratic? (Run with 10% of data to see) \$\endgroup\$.isin(friends)
, which appears to take O(n) linear time. Would you prefer to make that a O(1) constant timeset
lookup? \$\endgroup\$select_friends
, which we prefer to avoid. Better to output an NDarray of friends. Create a vector of K copies of those N ids. Permute them. Optionally reshape to N × K before returning it. (An ID still has an 1/N chance of being its own friend, as in the OP code). This isn't algorithmically better, it just lets compiled / vectorized C code do the work, instead of the bytecode interpreter. Also, we saved N × K object pointers, by switching fromlist
to a numpy array. \$\endgroup\$