{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n# Analyze Feature-Rich Free Recall (FRFR) Data\n\nThis example demonstrates analyzing the Feature-Rich Free Recall (FRFR) dataset,\nwhich investigates how different word features affect memory organization during\nfree recall. The dataset contains 452 subjects across 11 experimental conditions,\neach varying which word features were made salient during encoding.\n\nExperimental conditions:\n- feature-rich: All features varied (color, location, category, size, etc.)\n- category: Only category information varied\n- color: Only color information varied\n- length: Only word length varied\n- first-letter: Only first letter varied\n- location: Only spatial location varied\n- size: Only semantic size varied\n- adaptive: Features adapted based on participant performance\n- reduced: Minimal feature variation\n- reduced-early: Reduced features in early lists\n- reduced-late: Reduced features in late lists\n\nEach subject studied 16 lists of 16 words. Lists 1-8 are considered \"early\" lists\nand lists 9-16 are considered \"late\" lists.\n\nWe'll analyze recall performance using:\n1. Probability of First Recall (PFR) - probability of recalling each position first\n2. Lag-CRP - conditional recall probability by temporal lag\n3. Serial Position Curve (SPC) - recall probability by encoding position\n4. Memory Fingerprint - clustering by multiple features\n\nReference:\nHeusser, A.C., Fitzpatrick, P.C., & Manning, J.R. (2018). How is experience\ntransformed into memory? bioRxiv. https://doi.org/10.1101/409987\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Code source: Contextual Dynamics Laboratory\n# License: MIT\n\nfrom collections import Counter\n\nimport quail\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport warnings\n\n# Suppress RuntimeWarnings about empty slices\nwarnings.filterwarnings('ignore', category=RuntimeWarning)\n\n# Create viridis palette for distinguishing 11 conditions\nviridis_palette = sns.color_palette(\"viridis\", n_colors=11)\n\n# Load the FRFR dataset\negg = quail.load_example_data('frfr')\n\nprint(f\"Loaded FRFR data: {egg.n_subjects} subjects, {egg.n_lists} lists, \"\n f\"{egg.list_length} items per list\")\n\n# Build subjgroup: map each subject to its experimental condition\nsubjgroup = []\nfor subj_idx in range(egg.n_subjects):\n try:\n sample = egg.pres.loc[(subj_idx, 0)][0]\n if sample and 'Condition' in sample:\n subjgroup.append(sample['Condition'])\n else:\n subjgroup.append('Unknown')\n except (KeyError, IndexError, TypeError):\n subjgroup.append('Unknown')\n\n# Define condition order for consistent plotting\ncondition_order = [\n 'Feature rich',\n 'Reduced early',\n 'Reduced late',\n 'Reduced',\n 'Adaptive',\n 'Category',\n 'Size',\n 'Color',\n 'Location',\n 'Word length',\n 'First letter'\n]\n\n# Count subjects per condition\ncondition_counts = Counter(subjgroup)\nprint(\"\\nSubjects per condition:\")\nfor cond in condition_order:\n if cond in condition_counts:\n print(f\" {cond}: {condition_counts[cond]}\")\n\n# Split egg into early (lists 0-7) and late (lists 8-15) lists\nprint(\"\\nSplitting data into early and late lists...\")\negg_early = egg.crack(lists=list(range(8)))\negg_late = egg.crack(lists=list(range(8, 16)))\n\nprint(f\"Early lists egg: {egg_early.n_subjects} subjects, {egg_early.n_lists} lists\")\nprint(f\"Late lists egg: {egg_late.n_subjects} subjects, {egg_late.n_lists} lists\")\n\n# Create listgroup for averaging across lists within each split\nlistgroup_early = ['average'] * egg_early.n_lists\nlistgroup_late = ['average'] * egg_late.n_lists\n\n# ============================================================================\n# Figure 1: PFR, Lag-CRP, SPC for Early (top) and Late (bottom) lists\n# ============================================================================\nprint(\"\\n\" + \"=\" * 60)\nprint(\"Creating Figure 1: PFR, Lag-CRP, SPC by condition\")\nprint(\"=\" * 60)\n\nfig1, axes1 = plt.subplots(2, 3, figsize=(18, 10), sharey='col')\n\n# --- Top row: Early lists ---\nprint(\"\\nAnalyzing early lists...\")\n\n# PFR - Early\nprint(\" Computing PFR (early)...\")\npfr_early = egg_early.analyze('pfr', listgroup=listgroup_early)\npfr_early.plot(ax=axes1[0, 0], subjgroup=subjgroup, plot_type='subject', legend=True,\n hue_order=condition_order, palette=viridis_palette)\naxes1[0, 0].set_title('Probability of First Recall (Early Lists)')\naxes1[0, 0].set_xlabel('Serial Position')\naxes1[0, 0].set_ylabel('Probability')\naxes1[0, 0].set_ylim([0, 0.3])\naxes1[0, 0].legend(loc='upper right', fontsize=6, ncol=2)\n\n# Lag-CRP - Early\nprint(\" Computing Lag-CRP (early)...\")\nlagcrp_early = egg_early.analyze('lagcrp', listgroup=listgroup_early)\nlagcrp_early.plot(ax=axes1[0, 1], subjgroup=subjgroup, plot_type='subject', legend=False,\n hue_order=condition_order, palette=viridis_palette)\naxes1[0, 1].set_title('Lag-CRP (Early Lists)')\naxes1[0, 1].set_xlabel('Lag')\naxes1[0, 1].set_ylabel('Conditional Recall Probability')\naxes1[0, 1].set_xlim([-10, 10])\naxes1[0, 1].axvline(x=0, color='gray', linestyle='--', alpha=0.5)\n\n# SPC - Early\nprint(\" Computing SPC (early)...\")\nspc_early = egg_early.analyze('spc', listgroup=listgroup_early)\nspc_early.plot(ax=axes1[0, 2], subjgroup=subjgroup, plot_type='subject', legend=False,\n hue_order=condition_order, palette=viridis_palette)\naxes1[0, 2].set_title('Serial Position Curve (Early Lists)')\naxes1[0, 2].set_xlabel('Serial Position')\naxes1[0, 2].set_ylabel('Recall Probability')\naxes1[0, 2].set_ylim([0, 1])\n\n# --- Bottom row: Late lists ---\nprint(\"\\nAnalyzing late lists...\")\n\n# PFR - Late\nprint(\" Computing PFR (late)...\")\npfr_late = egg_late.analyze('pfr', listgroup=listgroup_late)\npfr_late.plot(ax=axes1[1, 0], subjgroup=subjgroup, plot_type='subject', legend=False,\n hue_order=condition_order, palette=viridis_palette)\naxes1[1, 0].set_title('Probability of First Recall (Late Lists)')\naxes1[1, 0].set_xlabel('Serial Position')\naxes1[1, 0].set_ylabel('Probability')\naxes1[1, 0].set_ylim([0, 0.3])\n\n# Lag-CRP - Late\nprint(\" Computing Lag-CRP (late)...\")\nlagcrp_late = egg_late.analyze('lagcrp', listgroup=listgroup_late)\nlagcrp_late.plot(ax=axes1[1, 1], subjgroup=subjgroup, plot_type='subject', legend=False,\n hue_order=condition_order, palette=viridis_palette)\naxes1[1, 1].set_title('Lag-CRP (Late Lists)')\naxes1[1, 1].set_xlabel('Lag')\naxes1[1, 1].set_ylabel('Conditional Recall Probability')\naxes1[1, 1].set_xlim([-10, 10])\naxes1[1, 1].axvline(x=0, color='gray', linestyle='--', alpha=0.5)\n\n# SPC - Late\nprint(\" Computing SPC (late)...\")\nspc_late = egg_late.analyze('spc', listgroup=listgroup_late)\nspc_late.plot(ax=axes1[1, 2], subjgroup=subjgroup, plot_type='subject', legend=False,\n hue_order=condition_order, palette=viridis_palette)\naxes1[1, 2].set_title('Serial Position Curve (Late Lists)')\naxes1[1, 2].set_xlabel('Serial Position')\naxes1[1, 2].set_ylabel('Recall Probability')\naxes1[1, 2].set_ylim([0, 1])\n\nplt.tight_layout()\nfig1.suptitle('FRFR Dataset: Recall Analyses by Condition (Early vs Late Lists)',\n y=1.02, fontsize=14)\nplt.savefig('frfr_recall_analysis.png', dpi=150, bbox_inches='tight')\nprint(\"\\nSaved Figure 1 to frfr_recall_analysis.png\")\n\n# ============================================================================\n# Figure 2: Memory Fingerprints for Early (top) and Late (bottom) lists\n# ============================================================================\nprint(\"\\n\" + \"=\" * 60)\nprint(\"Creating Figure 2: Memory Fingerprints by condition\")\nprint(\"=\" * 60)\n\n# Stacked layout: 2 rows, 1 column (larger to avoid overlapping text)\nfig2, axes2 = plt.subplots(2, 1, figsize=(8, 8), sharey=True)\n\n# Features for fingerprint analysis (sentence case)\nfingerprint_features = ['Category', 'Size', 'Color', 'Location',\n 'Word length', 'First letter', 'Temporal']\n\n# Fingerprint - Early lists (top panel)\nprint(\"\\nComputing fingerprints (early lists)...\")\nfp_early = egg_early.analyze('fingerprint', features=fingerprint_features,\n listgroup=listgroup_early)\nfp_early.plot(ax=axes2[0], subjgroup=subjgroup, plot_type='subject',\n plot_style='bar', ylim=[0.5, 0.81], legend=False,\n hue_order=condition_order, palette=viridis_palette)\naxes2[0].set_title('Memory fingerprints\\nEarly lists', fontsize=14)\naxes2[0].set_xlabel('') # Remove x-label on top panel\naxes2[0].set_ylabel('Clustering score', fontsize=14)\naxes2[0].tick_params(axis='x', labelbottom=False) # Hide x tick labels on top\n\n# Fingerprint - Late lists (bottom panel)\nprint(\"Computing fingerprints (late lists)...\")\nfp_late = egg_late.analyze('fingerprint', features=fingerprint_features,\n listgroup=listgroup_late)\nfp_late.plot(ax=axes2[1], subjgroup=subjgroup, plot_type='subject',\n plot_style='bar', ylim=[0.5, 0.81], legend=True,\n hue_order=condition_order, palette=viridis_palette)\naxes2[1].set_title('Late lists', fontsize=14)\naxes2[1].set_xlabel('Feature', fontsize=14)\naxes2[1].set_ylabel('Clustering score', fontsize=14)\n# Move legend to upper right of bottom panel\naxes2[1].legend(loc='upper right', fontsize=6, ncol=3, title='Condition')\n\nplt.tight_layout()\nplt.savefig('frfr_fingerprint_analysis.png', dpi=150, bbox_inches='tight')\nprint(\"Saved Figure 2 to frfr_fingerprint_analysis.png\")\n\nplt.show()\n\nprint(\"\\nAnalysis complete!\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.12" } }, "nbformat": 4, "nbformat_minor": 0 }