xbe.at

Data visualisation

Visualizing Pride and Prejudice with Python

12 min read, 15 sections

Contents
  1. Introduction to Pride and Prejudice
  2. The Bennet Family - Genetic Algorithm for Optimal Matchmaking
  3. Netherfield Ball - Social Network Analysis
  4. Elizabeth's First Impression of Darcy - Bayesian Inference
  5. Mr. Collins' Proposal - Decision Tree for Marriage Proposals
  6. Wickham's Deception - Natural Language Processing for Lie Detection
  7. Jane and Bingley's Separation - Time Series Analysis of Emotional States
  8. Lady Catherine's Interference - Game Theory for Strategic Interactions
  9. Character Development - Machine Learning for Personality Prediction
  10. The Bennet Estate - Monte Carlo Simulation for Financial Planning
  11. Character Relationships - Graph Theory for Social Connections
  12. Pemberley Estate - Fractal Analysis of Landscape Description
  13. Character Dialogue - Markov Chain Text Generation
  14. Marriage Proposals - Probability Theory and Combinatorics
  15. Additional Resources

Introduction to Pride and Prejudice

"Pride and Prejudice" is Jane Austen's beloved novel about the Bennet family in Regency-era England. This presentation will explore key events and themes from the book using innovative Python applications, providing a unique perspective on the classic tale of love, social class, and personal growth.

The Bennet Family - Genetic Algorithm for Optimal Matchmaking

The story begins with Mrs. Bennet's desire to find suitable husbands for her five daughters. We can use a genetic algorithm to simulate the process of finding optimal matches based on various attributes.

import random

class Person:
    def __init__(self, name, wealth, intelligence, beauty):
        self.name = name
        self.wealth = wealth
        self.intelligence = intelligence
        self.beauty = beauty

def fitness(couple):
    return (couple[0].wealth + couple[1].wealth) * 0.4 + \
           (couple[0].intelligence + couple[1].intelligence) * 0.3 + \
           (couple[0].beauty + couple[1].beauty) * 0.3

def crossover(parent1, parent2):
    child = Person(
        f"Child of {parent1.name} and {parent2.name}",
        (parent1.wealth + parent2.wealth) / 2,
        (parent1.intelligence + parent2.intelligence) / 2,
        (parent1.beauty + parent2.beauty) / 2
    )
    return child

def mutate(person):
    person.wealth += random.uniform(-0.1, 0.1)
    person.intelligence += random.uniform(-0.1, 0.1)
    person.beauty += random.uniform(-0.1, 0.1)

def genetic_algorithm(population, generations):
    for _ in range(generations):
        population = sorted(population, key=lambda x: fitness(x), reverse=True)
        new_population = population[:len(population)//2]
        
        while len(new_population) < len(population):
            parent1, parent2 = random.sample(population[:len(population)//2], 2)
            child = crossover(parent1[0], parent1[1])
            mutate(child)
            new_population.append((child, random.choice(population)[1]))
        
        population = new_population
    
    return population[0]

# Example usage
bennet_sisters = [
    Person("Jane", 0.6, 0.8, 0.9),
    Person("Elizabeth", 0.6, 0.9, 0.8),
    Person("Mary", 0.6, 0.7, 0.6),
    Person("Kitty", 0.6, 0.6, 0.7),
    Person("Lydia", 0.6, 0.5, 0.8)
]

potential_suitors = [
    Person("Bingley", 0.9, 0.8, 0.8),
    Person("Darcy", 1.0, 0.9, 0.9),
    Person("Collins", 0.7, 0.6, 0.5),
    Person("Wickham", 0.5, 0.7, 0.8)
]

population = [(sister, suitor) for sister in bennet_sisters for suitor in potential_suitors]
best_match = genetic_algorithm(population, 100)

print(f"Best match: {best_match[0].name} and {best_match[1].name}")

Netherfield Ball - Social Network Analysis

The Netherfield Ball is a pivotal event where characters interact and form connections. We can use social network analysis to visualize and analyze these relationships.

import networkx as nx
import matplotlib.pyplot as plt

def create_social_network():
    G = nx.Graph()
    characters = ["Elizabeth", "Darcy", "Jane", "Bingley", "Lydia", "Wickham", "Mr. Bennet", "Mrs. Bennet"]
    G.add_nodes_from(characters)
    
    interactions = [
        ("Elizabeth", "Darcy"), ("Jane", "Bingley"),
        ("Elizabeth", "Wickham"), ("Lydia", "Wickham"),
        ("Elizabeth", "Jane"), ("Mr. Bennet", "Mrs. Bennet"),
        ("Elizabeth", "Mr. Bennet"), ("Jane", "Mrs. Bennet")
    ]
    G.add_edges_from(interactions)
    
    return G

def analyze_network(G):
    degree_centrality = nx.degree_centrality(G)
    betweenness_centrality = nx.betweenness_centrality(G)
    
    print("Degree Centrality:")
    for char, score in sorted(degree_centrality.items(), key=lambda x: x[1], reverse=True):
        print(f"{char}: {score:.2f}")
    
    print("\nBetweenness Centrality:")
    for char, score in sorted(betweenness_centrality.items(), key=lambda x: x[1], reverse=True):
        print(f"{char}: {score:.2f}")

def visualize_network(G):
    pos = nx.spring_layout(G)
    plt.figure(figsize=(10, 8))
    nx.draw(G, pos, with_labels=True, node_color='lightblue', node_size=3000, font_size=10, font_weight='bold')
    edge_labels = nx.get_edge_attributes(G, 'weight')
    nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)
    plt.title("Social Network at the Netherfield Ball")
    plt.axis('off')
    plt.show()

G = create_social_network()
analyze_network(G)
visualize_network(G)

Elizabeth's First Impression of Darcy - Bayesian Inference

Elizabeth's initial negative impression of Darcy is based on limited information. We can use Bayesian inference to model how her opinion might change as she gathers more evidence.

import numpy as np
from scipy import stats

def bayesian_update(prior, likelihood, evidence):
    posterior = prior * likelihood(evidence)
    return posterior / np.sum(posterior)

def likelihood(evidence):
    return np.array([stats.norm(0, 1).pdf(evidence), stats.norm(2, 1).pdf(evidence)])

# Initial probabilities: [Negative, Positive]
prior = np.array([0.9, 0.1])

# Evidence scale: -3 (very negative) to 3 (very positive)
evidences = [-2, -1, 0, 1, 2]

print("Elizabeth's opinion of Darcy:")
print(f"Initial: Negative {prior[0]:.2f}, Positive {prior[1]:.2f}")

for i, evidence in enumerate(evidences, 1):
    prior = bayesian_update(prior, likelihood, evidence)
    print(f"After event {i}: Negative {prior[0]:.2f}, Positive {prior[1]:.2f}")

Mr. Collins' Proposal - Decision Tree for Marriage Proposals

Mr. Collins proposes to Elizabeth, leading to a complex decision-making process. We can model this using a decision tree to evaluate different outcomes.

from anytree import Node, RenderTree
import random

class DecisionNode(Node):
    def __init__(self, name, parent=None, probability=1.0, utility=0):
        super().__init__(name, parent)
        self.probability = probability
        self.utility = utility

def create_proposal_tree():
    root = DecisionNode("Proposal")
    
    accept = DecisionNode("Accept", parent=root, probability=0.3)
    DecisionNode("Happy Marriage", parent=accept, probability=0.2, utility=100)
    DecisionNode("Unhappy Marriage", parent=accept, probability=0.8, utility=-50)
    
    reject = DecisionNode("Reject", parent=root, probability=0.7)
    DecisionNode("Find Better Match", parent=reject, probability=0.6, utility=150)
    DecisionNode("Remain Single", parent=reject, probability=0.4, utility=50)
    
    return root

def calculate_expected_utility(node):
    if node.is_leaf:
        return node.probability * node.utility
    else:
        return sum(calculate_expected_utility(child) for child in node.children)

def print_tree(root):
    for pre, _, node in RenderTree(root):
        print(f"{pre}{node.name} (P: {node.probability:.2f}, U: {node.utility})")

proposal_tree = create_proposal_tree()
print_tree(proposal_tree)
print(f"\nExpected Utility: {calculate_expected_utility(proposal_tree):.2f}")

Wickham's Deception - Natural Language Processing for Lie Detection

Wickham's false account of his history with Darcy misleads Elizabeth. We can use NLP techniques to analyze speech patterns and detect potential deception.

import nltk
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
nltk.download('punkt')
nltk.download('stopwords')

def analyze_deception(text):
    tokens = word_tokenize(text.lower())
    stop_words = set(stopwords.words('english'))
    filtered_tokens = [word for word in tokens if word not in stop_words]
    
    deception_indicators = {
        'self_references': ['i', 'me', 'my', 'mine', 'myself'],
        'negative_emotions': ['hate', 'angry', 'sad', 'upset', 'disappointed'],
        'exclusionary_words': ['but', 'except', 'without', 'exclude'],
        'cognitive_complexity': ['think', 'know', 'consider', 'because', 'effect']
    }
    
    scores = {category: sum(1 for word in filtered_tokens if word in words) / len(filtered_tokens)
              for category, words in deception_indicators.items()}
    
    return scores

wickham_statement = """
I can never defy an object of charity. Mr. Darcy's father was my godfather and excessively attached to me. 
He had intended to provide for me amply and thought he had done so, but when he died it was discovered that 
the living he had promised me had been given to another man.
"""

darcy_statement = """
Wickham's vicious propensities, his want of principle, were shielded from the world by his ability to please. 
He has been a most delightful friend, but I cannot pretend to be sorry that he has met with his deserts.
"""

print("Wickham's statement analysis:")
print(analyze_deception(wickham_statement))
print("\nDarcy's statement analysis:")
print(analyze_deception(darcy_statement))

Jane and Bingley's Separation - Time Series Analysis of Emotional States

The separation of Jane and Bingley leads to emotional turmoil. We can use time series analysis to model and predict their emotional states over time.

import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.arima.model import ARIMA

def generate_emotion_data(length, initial_state, volatility):
    emotions = [initial_state]
    for _ in range(1, length):
        change = np.random.normal(0, volatility)
        new_emotion = max(0, min(10, emotions[-1] + change))
        emotions.append(new_emotion)
    return np.array(emotions)

def plot_emotions(jane_emotions, bingley_emotions):
    plt.figure(figsize=(12, 6))
    plt.plot(jane_emotions, label='Jane')
    plt.plot(bingley_emotions, label='Bingley')
    plt.title("Emotional States Over Time")
    plt.xlabel("Days")
    plt.ylabel("Emotional State (0-10)")
    plt.legend()
    plt.show()

def forecast_emotions(emotions, days_to_forecast):
    model = ARIMA(emotions, order=(1, 1, 1))
    results = model.fit()
    forecast = results.forecast(steps=days_to_forecast)
    return forecast

# Generate emotion data
days = 100
jane_emotions = generate_emotion_data(days, 8, 0.5)
bingley_emotions = generate_emotion_data(days, 8, 0.5)

# Plot emotions
plot_emotions(jane_emotions, bingley_emotions)

# Forecast future emotions
forecast_days = 30
jane_forecast = forecast_emotions(jane_emotions, forecast_days)
bingley_forecast = forecast_emotions(bingley_emotions, forecast_days)

print("Jane's emotional forecast for the next 30 days:")
print(jane_forecast)
print("\nBingley's emotional forecast for the next 30 days:")
print(bingley_forecast)

Lady Catherine's Interference - Game Theory for Strategic Interactions

Lady Catherine's attempt to prevent Elizabeth and Darcy's union can be analyzed using game theory to understand the strategic interactions between characters.

import numpy as np

def lady_catherine_game():
    # Payoff matrix: [Lady Catherine's payoff, Elizabeth's payoff]
    payoff_matrix = np.array([
        [[1, -1], [-2, 2]],  # Lady Catherine: Interfere, Elizabeth: Comply/Defy
        [[0, 0], [0, 1]]     # Lady Catherine: Don't Interfere, Elizabeth: Comply/Defy
    ])
    
    return payoff_matrix

def find_nash_equilibrium(payoff_matrix):
    num_strategies_1, num_strategies_2, _ = payoff_matrix.shape
    
    for i in range(num_strategies_1):
        for j in range(num_strategies_2):
            if (payoff_matrix[i, j, 0] >= np.max(payoff_matrix[:, j, 0]) and
                payoff_matrix[i, j, 1] >= np.max(payoff_matrix[i, :, 1])):
                return i, j
    
    return None

payoff_matrix = lady_catherine_game()
nash_eq = find_nash_equilibrium(payoff_matrix)

print("Payoff Matrix:")
print(payoff_matrix)

if nash_eq:
    print(f"\nNash Equilibrium: Lady Catherine's strategy: {nash_eq[0]}, Elizabeth's strategy: {nash_eq[1]}")
    print(f"Payoffs: Lady Catherine: {payoff_matrix[nash_eq][0]}, Elizabeth: {payoff_matrix[nash_eq][1]}")
else:
    print("\nNo pure strategy Nash Equilibrium found.")

Character Development - Machine Learning for Personality Prediction

Throughout the novel, characters evolve and change. We can use machine learning to predict personality traits based on their actions and dialogues.

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import numpy as np

def generate_character_data(n_samples):
    np.random.seed(42)
    X = np.random.rand(n_samples, 4)  # 4 features: assertiveness, empathy, pride, prejudice
    y = (X[:, 0] + X[:, 1] > X[:, 2] + X[:, 3]).astype(int)  # 1 if assertiveness + empathy > pride + prejudice
    return X, y

X, y = generate_character_data(1000)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train, y_train)

y_pred = clf.predict(X_test)
print(classification_report(y_test, y_pred))

# Predict for new characters
new_characters = np.array([
    [0.8, 0.7, 0.6, 0.3],  # Elizabeth
    [0.9, 0.6, 0.8, 0.7],  # Darcy
    [0.5, 0.9, 0.3, 0.2],  # Jane
    [0.7, 0.4, 0.8, 0.6]   # Lady Catherine
])

predictions = clf.predict(new_characters)
for i, pred in enumerate(predictions):
    print(f"Character {i+1}: {'Positive' if pred == 1 else 'Negative'} development")

The Bennet Estate - Monte Carlo Simulation for Financial Planning

Mr. Bennet's estate is entailed, causing financial uncertainty for his daughters. We can use Monte Carlo simulation to model various financial scenarios.

import numpy as np
import matplotlib.pyplot as plt

def simulate_estate_value(initial_value, years, num_simulations):
    annual_return_mean = 0.05
    annual_return_std = 0.1
    
    simulations = np.zeros((num_simulations, years))
    simulations[:, 0] = initial_value
    
    for year in range(1, years):
        returns = np.random.normal(annual_return_mean, annual_return_std, num_simulations)
        simulations[:, year] = simulations[:, year-1] * (1 + returns)
    
    return simulations

initial_estate_value = 10000
simulation_years = 20
num_simulations = 1000

results = simulate_estate_value(initial_estate_value, simulation_years, num_simulations)

plt.figure(figsize=(10, 6))
plt.plot(results.T, alpha=0.1, color='blue')
plt.title("Monte Carlo Simulation of Bennet Estate Value")
plt.xlabel("Years")
plt.ylabel("Estate Value")
plt.show()

final_values = results[:, -1]
print(f"Probability of estate value increasing: {np.mean(final_values > initial_estate_value):.2f}")
print(f"Mean final estate value: {np.mean(final_values):.2f}")
print(f"Median final estate value: {np.median(final_values):.2f}")

Character Relationships - Graph Theory for Social Connections

The complex web of relationships in Pride and Prejudice can be represented and analyzed using graph theory.

import networkx as nx
import matplotlib.pyplot as plt

def create_character_graph():
    G = nx.Graph()
    G.add_edges_from([
        ("Elizabeth", "Jane"), ("Elizabeth", "Darcy"),
        ("Jane", "Bingley"), ("Lydia", "Wickham"),
        ("Mr. Bennet", "Mrs. Bennet"), ("Elizabeth", "Charlotte"),
        ("Darcy", "Bingley"), ("Elizabeth", "Mr. Collins"),
        ("Lady Catherine", "Darcy"), ("Georgiana", "Darcy")
    ])
    return G

def analyze_character_graph(G):
    print("Degree Centrality:")
    print(nx.degree_centrality(G))
    
    print("\nBetweenness Centrality:")
    print(nx.betweenness_centrality(G))
    
    print("\nClustering Coefficient:")
    print(nx.clustering(G))

G = create_character_graph()
analyze_character_graph(G)

pos = nx.spring_layout(G)
plt.figure(figsize=(12, 8))
nx.draw(G, pos, with_labels=True, node_color='lightblue', 
        node_size=3000, font_size=10, font_weight='bold')
plt.title("Character Relationship Graph")
plt.axis('off')
plt.show()

Pemberley Estate - Fractal Analysis of Landscape Description

Austen's description of Pemberley's landscape can be analyzed using fractal geometry to understand its complexity and natural beauty.

import numpy as np
import matplotlib.pyplot as plt

def midpoint_displacement(length, roughness, iterations):
    points = np.array([[0, 0], [length, 0]])
    for _ in range(iterations):
        new_points = []
        for i in range(len(points) - 1):
            p1, p2 = points[i], points[i+1]
            mid = (p1 + p2) / 2
            displacement = np.random.normal(0, roughness * (length / (2 ** _)))
            mid[1] += displacement
            new_points.extend([p1, mid])
        new_points.append(points[-1])
        points = np.array(new_points)
    return points

landscape = midpoint_displacement(100, 0.6, 8)

plt.figure(figsize=(12, 6))
plt.plot(landscape[:, 0], landscape[:, 1])
plt.title("Fractal Landscape of Pemberley Estate")
plt.xlabel("Distance")
plt.ylabel("Elevation")
plt.show()

def calculate_fractal_dimension(landscape, min_scale, max_scale):
    scales = np.logspace(np.log10(min_scale), np.log10(max_scale), num=20)
    lengths = []
    for scale in scales:
        points = landscape[::int(scale)]
        lengths.append(np.sum(np.sqrt(np.sum(np.diff(points, axis=0)**2, axis=1))))
    
    coeffs = np.polyfit(np.log(scales), np.log(lengths), 1)
    return 1 - coeffs[0]

fractal_dim = calculate_fractal_dimension(landscape, 1, len(landscape)//4)
print(f"Estimated fractal dimension of the landscape: {fractal_dim:.3f}")

Character Dialogue - Markov Chain Text Generation

We can use Markov chains to generate synthetic dialogue in the style of different characters from Pride and Prejudice.

import random

def build_markov_chain(text, n):
    words = text.split()
    chain = {}
    for i in range(len(words) - n):
        state = tuple(words[i:i+n])
        next_word = words[i+n]
        if state not in chain:
            chain[state] = {}
        if next_word not in chain[state]:
            chain[state][next_word] = 0
        chain[state][next_word] += 1
    return chain

def generate_text(chain, n, length):
    current = random.choice(list(chain.keys()))
    result = list(current)
    for _ in range(length - n):
        if current not in chain:
            break
        next_word = random.choices(list(chain[current].keys()), 
                                   weights=chain[current].values())[0]
        result.append(next_word)
        current = tuple(result[-n:])
    return ' '.join(result)

elizabeth_text = """
I am only resolved to act in that manner, which will, in my own opinion, 
constitute my happiness, without reference to you, or to any person so 
wholly unconnected with me.
"""

darcy_text = """
In vain I have struggled. It will not do. My feelings will not be repressed. 
You must allow me to tell you how ardently I admire and love you.
"""

elizabeth_chain = build_markov_chain(elizabeth_text, 2)
darcy_chain = build_markov_chain(darcy_text, 2)

print("Generated Elizabeth dialogue:")
print(generate_text(elizabeth_chain, 2, 20))

print("\nGenerated Darcy dialogue:")
print(generate_text(darcy_chain, 2, 20))

Marriage Proposals - Probability Theory and Combinatorics

The novel features several marriage proposals. We can use probability theory and combinatorics to analyze the possible outcomes of these proposals.

from itertools import combinations
import math

def factorial(n):
    return math.factorial(n)

def nCr(n, r):
    return factorial(n) // (factorial(r) * factorial(n - r))

characters = ["Elizabeth", "Jane", "Lydia", "Darcy", "Bingley", "Wickham"]
n_characters = len(characters)

print(f"Total number of possible couple combinations: {nCr(n_characters, 2)}")

successful_proposals = [("Elizabeth", "Darcy"), ("Jane", "Bingley"), ("Lydia", "Wickham")]
n_successful = len(successful_proposals)

probability_all_successful = nCr(n_successful, n_successful) / nCr(n_characters, 2*n_successful)
print(f"Probability of exactly these {n_successful} couples forming: {probability_all_successful:.6f}")

def proposal_probability(n_proposals, n_acceptances):
    return nCr(n_proposals, n_acceptances) * (0.5**n_proposals)

print("\nProbabilities of acceptance for different numbers of proposals:")
for i in range(1, 6):
    for j in range(i+1):
        prob = proposal_probability(i, j)
        print(f"{i} proposals, {j} acceptances: {prob:.4f}")

Additional Resources

For those interested in exploring more advanced applications of data science and programming in literature analysis, consider the following resources:

  1. "Computational Stylistics and Text Analysis" - https://arxiv.org/abs/2010.05587
  2. "Digital Humanities and Literary Studies" - https://arxiv.org/abs/1811.06545
  3. "Machine Learning for Literary Analysis" - https://arxiv.org/abs/2004.08285
  4. "Network Analysis in Literature" - https://arxiv.org/abs/1707.05214

These papers provide in-depth discussions on various computational techniques applied to literary works, offering further insights into the intersection of technology and classic literature.

More in Data visualisation

  1. Visualizing Riemannian Manifolds with Python11 min

    Riemannian manifolds are fundamental structures in differential geometry, combining the concepts of smooth manifolds with inner product spaces. They provide...

  2. Visualizing Partial Differential Equations with Python9 min

    Partial Differential Equations (PDEs) are equations involving functions of multiple variables and their partial derivatives. They are essential in modeling...

  3. Visualizing Scheme Theory with Python5 min

    Schemes are fundamental objects in algebraic geometry, generalizing the concept of algebraic varieties. They provide a more flexible framework for studying...

  4. Visualizing Multiplicative Number Theory with Python.md6 min

    Multiplicative Number Theory is a branch of mathematics that focuses on the properties of integers under multiplication. It explores concepts like prime...

  5. Visualizing Skewness in Probability Distributions with Python9 min

    Skewness is a measure of asymmetry in probability distributions. It indicates the extent to which a distribution deviates from a symmetrical shape....

  6. Visualizing Machine Learning Insights8 min

    Visualization is a crucial tool in machine learning that helps us understand complex data, interpret models, and gain insights into the learning process. It...