Skip to content

Instantly share code, notes, and snippets.

@soodoku
Last active September 13, 2025 22:22
Show Gist options
  • Select an option

  • Save soodoku/dfa01216f9f3f7d51caa2b086eed76fe to your computer and use it in GitHub Desktop.

Select an option

Save soodoku/dfa01216f9f3f7d51caa2b086eed76fe to your computer and use it in GitHub Desktop.
Personality Change

Personality Change

Personality psychology reports "moderate stability" of traits across adulthood, but what does this mean for individuals? We simulated how likely someone is to obtain similar Big Five personality test results over a 30-year span, critically accounting for measurement error.

Method: We generated 10,000 individuals with Big Five traits following a multivariate normal distribution with empirical intercorrelations (e.g., Neuroticism with Extraversion: -0.22). We modeled personality at Time 2 using the test-retest formula: New_Score = Old_Score × r + error × √(1-r²), adding measurement error (reliability = 0.82, per Gnambs 2014) at both time points. We defined "meaningful change" as shifts exceeding 0.5 standard deviations. Crucially, we established a baseline scenario with perfect stability (r=1.0) to isolate measurement error effects, finding that 33% appear to change due to measurement error alone. We then examined: Soldz & Vaillant's (1999) 30-year correlations (E: 0.19, N: 0.20, O: 0.38, C: 0.12, A: 0.07), Roberts & DelVecchio's (2000) moderate correlations (0.40), optimistic correlations (0.50), and a time-decay model where stability decreases from ~0.80 at 2 years to ~0.45 at 30 years.

Results: Observed change on 3+ traits ranged from 76-83% across scenarios. After accounting for the 33% baseline measurement error, we can confidently attribute true personality change to 43-50% of individuals. For the remaining ~50%, the data cannot distinguish between stability and change—measurement noise makes their smaller changes uninterpretable. Thus, we know that at least 43-50% of people genuinely change their personality over 30 years. The others may have changed or remained stable; measurement limitations prevent us from knowing. This finding clarifies what "moderate stability" means: roughly half of people demonstrably change on most personality dimensions over a lifetime.

import numpy as np
from scipy.stats import multivariate_normal
def simulate_personality_change(stability_scenario='baseline', years=30, n=10000):
"""
Simulate personality change over specified years.
Parameters:
- stability_scenario: 'baseline' (no true change, r=1.0),
'low' (Soldz & Vaillant), 'moderate' (Roberts & DelVecchio),
'high' (optimistic), or 'time_decay' (realistic decay function)
- years: time span (relevant for time_decay scenario)
- n: number of simulated individuals
Returns:
- Dictionary with percentages for each number of traits changed
"""
# Trait intercorrelations matrix (E, N, O, C, A) from literature
trait_corr = np.array([
[1.00, -0.22, 0.17, 0.12, 0.12], # Extraversion
[-0.22, 1.00, 0.05, -0.27, -0.22], # Neuroticism
[0.17, 0.05, 1.00, 0.05, 0.12], # Openness
[0.12, -0.27, 0.05, 1.00, 0.22], # Conscientiousness
[0.12, -0.22, 0.12, 0.22, 1.00] # Agreeableness
])
# Time-based stability decay function
def get_stability(years):
"""Based on meta-analyses: ~.80 at 2 years, ~.65 at 10 years, ~.45 at 30 years"""
if years <= 2:
return 0.85 - 0.025 * years
elif years <= 10:
return 0.80 - 0.015 * years
else:
return 0.65 - 0.007 * years
# Set stability correlations based on scenario
if stability_scenario == 'baseline':
stability = [1.00, 1.00, 1.00, 1.00, 1.00] # No true change
elif stability_scenario == 'time_decay':
base_stability = [get_stability(years) for _ in range(5)]
# Trait-specific adjustments based on literature
stability = [
base_stability[0] * 1.05, # E slightly more stable
base_stability[1], # N normal
base_stability[2] * 0.90, # O slightly less stable
base_stability[3], # C normal
base_stability[4] # A normal
]
elif stability_scenario == 'low':
stability = [0.19, 0.20, 0.38, 0.12, 0.07] # Soldz & Vaillant 1999
elif stability_scenario == 'moderate':
stability = [0.40, 0.40, 0.40, 0.40, 0.40] # Roberts & DelVecchio 2000
elif stability_scenario == 'high':
stability = [0.50, 0.50, 0.50, 0.50, 0.50] # Optimistic
measurement_reliability = 0.82 # Gnambs 2014 meta-analysis
changes = []
for _ in range(n):
# Generate correlated traits at Time 1 (true scores)
true_t1 = multivariate_normal.rvs(mean=[0, 0, 0, 0, 0], cov=trait_corr)
# Add measurement error for observed Time 1 scores
error_t1 = np.random.normal(0, np.sqrt(1 - measurement_reliability), 5)
observed_t1 = true_t1 * np.sqrt(measurement_reliability) + error_t1
# Generate Time 2 true scores using stability correlations
true_t2 = []
for i, r in enumerate(stability):
change_error = np.random.normal(0, 1)
new_true = true_t1[i] * r + change_error * np.sqrt(1 - r**2)
true_t2.append(new_true)
true_t2 = np.array(true_t2)
# Add measurement error for observed Time 2 scores
error_t2 = np.random.normal(0, np.sqrt(1 - measurement_reliability), 5)
observed_t2 = true_t2 * np.sqrt(measurement_reliability) + error_t2
# Calculate absolute changes in observed scores
trait_changes = [abs(o2 - o1) for o2, o1 in zip(observed_t2, observed_t1)]
changes.append(trait_changes)
changes = np.array(changes)
# Calculate distribution of changes
distribution = {}
for i in range(6):
distribution[i] = np.mean(np.sum(changes > 0.5, axis=1) == i) * 100
prop_3plus = np.mean(np.sum(changes > 0.5, axis=1) >= 3) * 100
return {'distribution': distribution, 'prop_3plus': prop_3plus, 'stability': stability}
# Run baseline first to establish measurement error rates
print("="*60)
print("PERSONALITY CHANGE OVER 30 YEARS")
print("10,000 simulated individuals per scenario")
print("="*60)
baseline_results = simulate_personality_change('baseline', n=10000)
baseline_3plus = baseline_results['prop_3plus']
print("\nBASELINE: Perfect stability (r=1.0)")
print("Shows change due to measurement error alone")
print(f"→ {baseline_3plus:.0f}% show apparent change on 3+ traits")
# Now run substantive scenarios and calculate true change
print("\n" + "="*60)
print("TRUE PERSONALITY CHANGE (accounting for measurement error)")
print("="*60)
scenarios = ['low', 'moderate', 'high', 'time_decay']
labels = {
'low': 'Soldz & Vaillant (1999)',
'moderate': 'Roberts & DelVecchio (2000)',
'high': 'Optimistic (r=0.50)',
'time_decay': 'Time-decay model'
}
results = {}
for scenario in scenarios:
results[scenario] = simulate_personality_change(scenario, years=30, n=10000)
observed = results[scenario]['prop_3plus']
true_change = observed - baseline_3plus
print(f"\n{labels[scenario]}")
print(f"Stability: {results[scenario]['stability']}")
print(f"Observed change: {observed:.0f}%")
print(f"Measurement error: {baseline_3plus:.0f}%")
print(f"TRUE CHANGE: {true_change:.0f}% of people genuinely change on 3+ traits")
# Calculate adjusted percentages for true changers
print("\n" + "="*60)
print("SUMMARY: Who Truly Changes?")
print("="*60)
print(f"Measurement error alone affects {baseline_3plus:.0f}% (baseline)")
print("\nPercentage with TRUE personality change on 3+ traits:")
for scenario in scenarios:
true_change = results[scenario]['prop_3plus'] - baseline_3plus
print(f" {labels[scenario]:35} {true_change:.0f}%")
print("\n" + "="*60)
print("INTERPRETATION")
print("="*60)
print("After accounting for measurement error:")
true_changes = [results[s]['prop_3plus'] - baseline_3plus for s in scenarios]
min_change = min(true_changes)
max_change = max(true_changes)
print(f"• Between {min_change:.0f}% and {max_change:.0f}% of people show TRUE personality change")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment