Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Six phases: (1) familiarization, (2) initial coding, (3) searching for themes, (4) reviewing themes, (5) defining themes, (6) writing up. NLP assists phases 2–4 by clustering similar text segments.
Grounded Theory Coding
Open coding: Break text into conceptual units, assign preliminary codes
Axial coding: Find relationships between codes (cause-effect, context-condition)
Selective coding: Identify core category integrating all codes
Sentence Embeddings for Semantic Similarity
Sentence transformers map text to dense vectors. Cosine similarity identifies semantically similar passages even with different vocabulary — helpful for suggesting code assignments.
Inter-Rater Reliability
Cohen's $\kappa$ for two raters:
$$
\kappa = \frac{P_o - P_e}{1 - P_e}
$$
where $P_o$ = observed agreement, $P_e$ = expected agreement by chance.
Krippendorff's $\alpha$ generalizes to multiple raters and ordinal/interval scales.
Step 1: Interview Corpus Processing and Initial Coding
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from collections import Counter
import re
# ------------------------------------------------------------------ ## Simulate interview transcripts with open coding# In practice: load from CSV/Word/PDF# ------------------------------------------------------------------ #
transcripts = [
{
"id": "INT_01", "participant": "P01", "background": "working_class",
"text": """
I feel like the system is just not set up for people like us. You know, I work
hard every day but the bills keep coming. My kids need things and I can't always
provide. The landlord raised the rent again last month — third time in two years.
I don't know what we're going to do. Sometimes I feel invisible, like nobody
sees what families like mine are going through.
"""
},
{
"id": "INT_02", "participant": "P02", "background": "middle_class",
"text": """
The cost of living is definitely a concern, but I think with proper planning
you can manage. I own my home, so I'm somewhat insulated from rent increases.
But I do worry about my kids' college costs and whether they'll have the same
opportunities I had. The economy feels more uncertain than when I was young.
I think community support networks are important.
"""
},
{
"id": "INT_03", "participant": "P03", "background": "working_class",
"text": """
Healthcare is the biggest issue for me. I had to choose between buying medicine
and buying food last winter. The ER is too expensive so you just try to manage.
My workplace doesn't offer benefits. I feel like if you don't have money you're
on your own. The government programs are too complicated to access — too much
paperwork, too many hoops to jump through.
"""
},
{
"id": "INT_04", "participant": "P04", "background": "upper_middle_class",
"text": """
I think the biggest challenge in society right now is the division — people
are not talking to each other across class lines. I volunteer at the food bank
and I see what people are going through. I feel fortunate but also guilty
sometimes. I believe in collective responsibility. Strong communities make
stronger individuals.
"""
},
{
"id": "INT_05", "participant": "P05", "background": "working_class",
"text": """
Work is exhausting. Two jobs, still not enough. The gig economy is brutal —
no stability, no benefits, no predictability. I feel like I'm running on a
treadmill. Childcare costs eat everything. Nobody talks about how hard it is
to be a single parent and work. The system wasn't built for us.
"""
},
]
df = pd.DataFrame(transcripts)
# ---- Segment text into coded units (sentence-level) --------------- #defsegment_text(text, participant_id):
"""Split text into coded units (non-trivial sentences)."""
sentences = re.split(r'(?<=[.!?])\s+', text.strip())
units = []
for i, sent inenumerate(sentences):
sent = sent.strip()
iflen(sent.split()) >= 5: # Min 5 words
units.append({
'participant': participant_id,
'segment_id': f"{participant_id}_S{i:02d}",
'text': sent,
})
return units
coded_units = []
for _, row in df.iterrows():
coded_units.extend(segment_text(row['text'], row['participant']))
df_units = pd.DataFrame(coded_units)
print(f"Total coded units: {len(df_units)}")
print(df_units[['segment_id','text']].head(8).to_string(index=False))
# ---- Manual codebook (open codes) -------------------------------- ## In practice: researcher reads and assigns codes# Here: keyword-based heuristic for demonstration
codebook = {
'Economic stress': ['rent', 'bills', 'money', 'cost', 'afford', 'income', 'pay'],
'Systemic critique': ['system', 'government', 'access', 'bureaucracy', 'program', 'built'],
'Healthcare': ['healthcare', 'medicine', 'hospital', 'ER', 'benefits', 'health'],
'Work precarity': ['work', 'job', 'gig', 'stability', 'hours', 'treadmill', 'jobs'],
'Social visibility': ['invisible', 'see', 'nobody', 'community', 'collective', 'division'],
'Family/children': ['kids', 'children', 'family', 'parent', 'childcare', 'child'],
'Emotional state': ['feel', 'worry', 'exhausting', 'guilty', 'fortunate', 'lucky'],
}
defassign_codes(text, codebook):
"""Assign codes based on keyword matching (approximation of manual coding)."""
text_lower = text.lower()
assigned = []
for code, keywords in codebook.items():
ifany(kw in text_lower for kw in keywords):
assigned.append(code)
return assigned if assigned else ['Uncoded']
df_units['codes'] = df_units['text'].apply(lambda t: assign_codes(t, codebook))
df_units['n_codes'] = df_units['codes'].apply(len)
# Code frequency
code_counts = Counter(code for codes in df_units['codes'] for code in codes)
code_df = pd.DataFrame(code_counts.most_common(), columns=['Code','Count'])
print("\nCode frequencies:")
print(code_df.to_string(index=False))
fig, ax = plt.subplots(figsize=(10, 5))
ax.barh(code_df['Code'], code_df['Count'], color='steelblue', edgecolor='black', linewidth=0.6)
ax.set_xlabel("Frequency"); ax.set_title("Code Frequency — Thematic Analysis")
ax.invert_yaxis(); ax.grid(axis='x', alpha=0.3)
plt.tight_layout()
plt.savefig("code_frequency.png", dpi=150)
plt.show()
Step 2: Semantic Clustering with Sentence Embeddings
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.preprocessing import normalize
# ------------------------------------------------------------------ ## Use sentence embeddings to cluster coded units semantically# ------------------------------------------------------------------ #try:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = model.encode(df_units['text'].tolist(), show_progress_bar=True)
print(f"Embeddings shape: {embeddings.shape}")
except ImportError:
print("sentence-transformers not available; using TF-IDF fallback")
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer(max_features=200, stop_words='english')
embeddings = vectorizer.fit_transform(df_units['text']).toarray()
# Normalize embeddings for cosine similarity
embeddings_norm = normalize(embeddings)
# ---- UMAP or PCA for 2D visualization -------------------------- #try:
import umap
reducer = umap.UMAP(n_components=2, random_state=42, min_dist=0.3)
embedding_2d = reducer.fit_transform(embeddings_norm)
except ImportError:
# Fallback: PCA
pca = PCA(n_components=2, random_state=42)
embedding_2d = pca.fit_transform(embeddings_norm)
print("Using PCA for 2D projection (install umap-learn for better results)")
# ---- HDBSCAN clustering ---------------------------------------- #try:
import hdbscan
clusterer = hdbscan.HDBSCAN(min_cluster_size=3, min_samples=2)
labels = clusterer.fit_predict(embedding_2d)
n_clusters = len(set(labels)) - (1if -1in labels else0)
print(f"Found {n_clusters} semantic clusters")
except ImportError:
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=4, random_state=42, n_init=10)
labels = kmeans.fit_predict(embeddings_norm)
n_clusters = 4
df_units['cluster'] = labels
# Visualize
fig, ax = plt.subplots(figsize=(10, 7))
scatter_colors = plt.cm.tab10(np.linspace(0, 1, n_clusters + 1))
for cluster_id insorted(set(labels)):
mask = labels == cluster_id
label = f"Noise"if cluster_id == -1elsef"Cluster {cluster_id}"
color = 'lightgray'if cluster_id == -1else scatter_colors[cluster_id % len(scatter_colors)]
ax.scatter(embedding_2d[mask, 0], embedding_2d[mask, 1],
c=[color], label=label, s=60, edgecolors='black', linewidths=0.3, alpha=0.8)
# Annotate with participantfor i, (x, y) inenumerate(embedding_2d):
ax.annotate(df_units.iloc[i]['participant'], (x, y),
fontsize=6, alpha=0.6, xytext=(2, 2), textcoords='offset points')
ax.set_title("Semantic Clustering of Coded Units\n(UMAP/PCA + HDBSCAN)")
ax.legend(loc='upper left', fontsize=8); ax.grid(True, alpha=0.2)
plt.tight_layout()
plt.savefig("semantic_clusters.png", dpi=150)
plt.show()
# Retrieve representative quotes per clusterprint("\nRepresentative quotes per cluster:")
for cid insorted(set(labels)):
if cid == -1:
continue
cluster_texts = df_units[df_units['cluster'] == cid]['text'].tolist()
# Most central quote (closest to cluster centroid)
cluster_emb = embeddings_norm[labels == cid]
centroid = cluster_emb.mean(axis=0)
dists = np.linalg.norm(cluster_emb - centroid, axis=1)
rep_idx = dists.argmin()
print(f"\nCluster {cid}: {cluster_texts[rep_idx][:120]}...")
Step 3: Inter-Rater Reliability and Codebook Validation
import numpy as np
import pandas as pd
from sklearn.metrics import cohen_kappa_score, confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns
# ------------------------------------------------------------------ ## Compute inter-rater reliability for two coders# ------------------------------------------------------------------ ## Simulated coding by two researchers
np.random.seed(42)
n_segments = 40
codes_list = ['Economic stress', 'Systemic critique', 'Healthcare',
'Work precarity', 'Social visibility', 'Family/children']
# Rater 1 (gold standard)
rater1 = np.random.choice(codes_list, n_segments)
# Rater 2 (agree ~75% of time, disagree on 25%)
rater2 = rater1.copy()
disagree_idx = np.random.choice(n_segments, int(n_segments * 0.25), replace=False)
rater2[disagree_idx] = np.random.choice(codes_list, len(disagree_idx))
# Cohen's kappa
kappa = cohen_kappa_score(rater1, rater2)
pct_agreement = np.mean(rater1 == rater2) * 100print(f"Percentage agreement: {pct_agreement:.1f}%")
print(f"Cohen's κ: {kappa:.4f}")
if kappa > 0.80:
strength = "Almost perfect"elif kappa > 0.60:
strength = "Substantial"elif kappa > 0.40:
strength = "Moderate"else:
strength = "Fair or less"print(f"Interpretation: {strength} agreement")
# Confusion matrix
cm = confusion_matrix(rater1, rater2, labels=codes_list)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
sns_available = Falsetry:
import seaborn as sns
sns_available = Trueexcept ImportError:
passif sns_available:
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=codes_list, yticklabels=codes_list, ax=axes[0])
axes[0].set_xlabel("Rater 2"); axes[0].set_ylabel("Rater 1")
else:
axes[0].imshow(cm, cmap='Blues')
axes[0].set_xticks(range(len(codes_list))); axes[0].set_xticklabels(codes_list, rotation=45, ha='right')
axes[0].set_yticks(range(len(codes_list))); axes[0].set_yticklabels(codes_list)
for i inrange(cm.shape[0]):
for j inrange(cm.shape[1]):
axes[0].text(j, i, str(cm[i,j]), ha='center', va='center')
axes[0].set_title(f"Confusion Matrix\n(κ={kappa:.3f})")
# Code-level agreement
code_agreement = {}
for i, code inenumerate(codes_list):
mask = (rater1 == code) | (rater2 == code)
if mask.sum() > 0:
agree = ((rater1 == code) & (rater2 == code)).sum()
total = mask.sum()
code_agreement[code] = agree / total * 100
codes_sorted = sorted(code_agreement, key=code_agreement.get)
axes[1].barh(codes_sorted, [code_agreement[c] for c in codes_sorted],
color='steelblue', edgecolor='black', linewidth=0.6)
axes[1].axvline(75, color='red', linestyle='--', linewidth=1.5, label='75% threshold')
axes[1].set_xlabel("Code-level agreement (%)"); axes[1].set_title("Agreement by Code")
axes[1].legend(); axes[1].grid(axis='x', alpha=0.3)
plt.tight_layout()
plt.savefig("irr_analysis.png", dpi=150)
plt.show()
# Codes below threshold need discussion
problematic = [c for c, a in code_agreement.items() if a < 75]
if problematic:
print(f"\nCodes requiring codebook clarification: {problematic}")
Advanced Usage
Saturation Analysis
import numpy as np
import matplotlib.pyplot as plt
# ------------------------------------------------------------------ ## Theoretical saturation: track new codes discovered per interview# ------------------------------------------------------------------ #
all_codes_by_interview = [
['Economic stress', 'Family/children'],
['Economic stress', 'Systemic critique', 'Healthcare'],
['Work precarity', 'Systemic critique', 'Emotional state'],
['Social visibility', 'Emotional state'],
['Economic stress', 'Work precarity', 'Family/children'],
['Healthcare', 'Systemic critique'],
['Social visibility'],
['Economic stress'],
]
cumulative_codes = set()
new_codes_per_interview = []
for codes in all_codes_by_interview:
new = [c for c in codes if c notin cumulative_codes]
cumulative_codes.update(codes)
new_codes_per_interview.append(len(new))
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].bar(range(1, len(new_codes_per_interview)+1), new_codes_per_interview,
color='steelblue', edgecolor='black', linewidth=0.7)
axes[0].set_xlabel("Interview number"); axes[0].set_ylabel("New codes discovered")
axes[0].set_title("Code Discovery Rate (Saturation Curve)")
axes[0].grid(axis='y', alpha=0.3)
cumulative_unique = np.cumsum([len(c) for c in [set()] + [set(cs) for cs in all_codes_by_interview[:-1]]])
cum_actual = []
seen = set()
for codes in all_codes_by_interview:
seen.update(codes)
cum_actual.append(len(seen))
axes[1].plot(range(1, len(cum_actual)+1), cum_actual, 'bo-', linewidth=2, markersize=8)
axes[1].set_xlabel("Interview number"); axes[1].set_ylabel("Unique codes")
axes[1].set_title("Cumulative Code Growth (Saturation)"); axes[1].grid(True, alpha=0.3)
plt.tight_layout(); plt.savefig("saturation_curve.png", dpi=150); plt.show()
print(f"Theoretical saturation reached ~interview {next((i+1for i,v inenumerate(new_codes_per_interview[3:],3) if v==0), 'not reached')}")
Troubleshooting
Error: OSError: No model found for SentenceTransformer
Braun, V. & Clarke, V. (2006). Using thematic analysis in psychology. Qualitative Research in Psychology.
Glaser, B.G. & Strauss, A.L. (1967). The Discovery of Grounded Theory. Aldine.
Examples
Example 1: Co-occurrence Network of Codes
import networkx as nx
import matplotlib.pyplot as plt
from itertools import combinations
# Build code co-occurrence network
G = nx.Graph()
for _, row in df_units.iterrows():
codes = [c for c in row['codes'] if c != 'Uncoded']
for c1, c2 in combinations(codes, 2):
if G.has_edge(c1, c2):
G[c1][c2]['weight'] += 1else:
G.add_edge(c1, c2, weight=1)
if G.number_of_nodes() > 0:
pos = nx.spring_layout(G, seed=42)
weights = [G[u][v]['weight'] for u, v in G.edges()]
fig, ax = plt.subplots(figsize=(9, 7))
nx.draw_networkx(G, pos, ax=ax, node_size=1500, node_color='lightblue',
width=[w*2for w in weights], edge_color='gray',
font_size=8, font_weight='bold')
ax.set_title("Code Co-occurrence Network"); ax.axis('off')
plt.tight_layout(); plt.savefig("code_network.png", dpi=150); plt.show()
Example 2: Word Cloud per Theme
from wordcloud import WordCloud
import matplotlib.pyplot as plt
themes = {
'Economic stress': 'rent bills money cost afford income pay landlord work struggling',
'Systemic critique': 'system government program bureaucracy access paperwork hoops built',
'Healthcare': 'healthcare medicine hospital benefits doctor insurance sick treatment',
}
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
for ax, (theme, text) inzip(axes, themes.items()):
wc = WordCloud(width=400, height=200, background_color='white',
colormap='Blues', max_words=30).generate(text)
ax.imshow(wc, interpolation='bilinear')
ax.set_title(theme, fontsize=10); ax.axis('off')
plt.tight_layout(); plt.savefig("theme_wordclouds.png", dpi=150); plt.show()
Last updated: 2026-03-17 | Maintainer: @xjtulycIssues: GitHub Issues