{"id":25117898,"url":"https://github.com/brody-lab/tzl_spglm","last_synced_at":"2026-01-16T00:47:41.470Z","repository":{"id":258715773,"uuid":"782542522","full_name":"Brody-Lab/tzl_spGLM","owner":"Brody-Lab","description":"Generalized linear model of the spike train of individual neurons recorded in the Poisson Clicks task","archived":false,"fork":false,"pushed_at":"2024-12-30T22:09:48.000Z","size":672,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-02-08T03:35:53.021Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Brody-Lab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-05T14:01:54.000Z","updated_at":"2024-12-30T22:09:51.000Z","dependencies_parsed_at":"2024-12-30T02:32:52.932Z","dependency_job_id":"0f7b10f5-6006-41f7-b14a-f5478d30026c","html_url":"https://github.com/Brody-Lab/tzl_spGLM","commit_stats":null,"previous_names":["brody-lab/tzl_spglm"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Brody-Lab%2Ftzl_spGLM","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Brody-Lab%2Ftzl_spGLM/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Brody-Lab%2Ftzl_spGLM/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Brody-Lab%2Ftzl_spGLM/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Brody-Lab","download_url":"https://codeload.github.com/Brody-Lab/tzl_spGLM/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246814910,"owners_count":20838349,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-08T03:36:02.829Z","updated_at":"2026-01-16T00:47:41.435Z","avatar_url":"https://github.com/Brody-Lab.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tzl_spGLM\nA package for fitting Poisson generalized linear models (GLM) to the spike trains of a neuron recorded in the Poisson Clicks task\n\n# system requirements\nThis software has been tested on Julia 1.10.0 and MATLAB R2023a.\n\n# installation\n\n```julia-repl\n\u003e using Pkg\n\u003e Pkg.add(url=\"https://github.com/Brody-Lab/tzl_spGLM.git\")\n```\nThe installation should take no more than 5 minutes.\n\n# data\nSpike times and the times of task events are loaded from a MATLAB `MAT` file. Spikes are counted in time bins of  $\\Delta t$ seconds aligned to a reference event on each trial to give spike train $y$. GLM's are fitted to the spike train $y$.\n\nAn example data file is located at [here](/example/analysis_2024_04_22a_test_SPGLM/T176_2018_05_04_619040938_426.mat).\n\n# model\nOn time step $t$ of trial $m$, the spike train observation $y_{m,t}$, given the inputs to the model, is modelled as a Poisson random variable whose intensity is given by \n\n$$\n\\lambda_{m,t} = \\text{softplus}\\left( w\\cdot b_m + \\sum_i (k^{(i)} * x_m^{(i)})(t) \\right)\n$$\n\nwhere $b_m$ is a scalar trial-varying input (described below). Both $b_m$ and its scalar encoding  parameter $w$ is learned from the data.\n\nThe time series $x_m^{(i)}$ indicates the input related to event $i$ in the trial (nose-fixation, stereoclick, movement, or response). The input is an impulse function that gives the value of 1 on the time step when the event occurred and is otherwise zero (i.e., the integral of a delta function over each time bin).\n\nThe input is convolved $(\\cdot * \\cdot)$ with linear filter $k_i$ to capture the time-varying effect of event $i$ on the neuron's probability of spiking. The filter is learned as a linear combination of basis functions.\n\n$$\nk_i = \\Phi_i w_i\n$$\n\nwhere the columns of matrix $\\Phi$ correspond to basis functions and rows time steps. Example basis functions are shown below. Each plot corresponds to a set of basis functions, and each color is a individual basis function of that set.\n\n\u003cimg src=\"/example/analysis_2024_04_25b_externalinput/plotbasisset.svg\" width = \"800\"\u003e\n\nFor the set related to \"response\" (i.e., \"spoke\"), $\\Phi$ has two columns, and the encoding vector $w$ is two dimensional. \n\n### trial-varying baseline\nThe trial-varying baseline $b_m$ is learned through L2-penalized linear regression as a preliminary step before fitting the GLM. The inputs (regressors) are the spike count of the simultaneously recorded population 2 seconds before fixation, giving a design matrix $X$ of dimensions trials-by-neurons. The response (regressand) $y$ are the mean firing rate between fixation onset and entry into the side port (side poke). The L2 regularization penalty $\\lambda$ was selected across ten values from $10$ to $1e10$, and selected using a 5-fold cross-validation scheme. If either the smallest ($\\lambda=10$) or the largest $(1e10)$ value gives the lowest out-of-sample mean squared error, the model was not fit. After identify the optimal L2 penalty $\\hat{\\lambda}$, the baseline is estimated using all trials as\n\n$$\nb \\equiv X \\left[ (X^\\top X + \\hat{\\lambda}I)^{-1} X^\\top y \\right]\n$$\n\n\n## example\n\n### Julia REPL\nThe optimization procedure learns both the parameters (i.e. regressor weights) and a hyperparamer (precision of the Gaussian prior on the parameters, analogous to a L2 penalty coefficient):\n```\njulia\u003e using SPGLM\njulia\u003e csvpath = \"/mnt/cup/people/zhihaol/Documents/tzluo/analyses/analysis_2024_04_22a_test_SPGLM/models.csv\";\njulia\u003e options = SPGLM.Options(csvpath);\njulia\u003e trials = SPGLM.loadtrials(options);\njulia\u003e model = SPGLM.Model(options, trials);\njulia\u003e eo = SPGLM.maximizeevidence!(model)\n\nIter     Function value   Gradient norm\n     0     3.553781e+04     1.285527e+03\n * time: 6.103515625e-5\n     1     3.340892e+04     1.124030e+03\n * time: 0.05215096473693848\n     2     3.038869e+04     7.491107e+02\n * time: 0.10418510437011719\n     3     2.782119e+04     3.308262e+02\n * time: 0.15614914894104004\n     4     2.628437e+04     1.192378e+02\n * time: 0.21809101104736328\n     5     2.546613e+04     2.909452e+01\n * time: 0.25608205795288086\n     6     2.535228e+04     3.249330e+00\n * time: 0.29502296447753906\n     7     2.535107e+04     4.872618e-02\n * time: 0.33403897285461426\n     8     2.535107e+04     1.218098e-05\n * time: 0.38610410690307617\n     9     2.535107e+04     1.225686e-12\n * time: 0.44210100173950195\nEvidence optimization iteration: 1: precision (a) = 0.5908446386657102\nEvidence optimization iteration: 1: norm of the residual gradient of the log-posterior (g_residual) = 1.2256862191861728e-12\nEvidence optimization iteration: 1: MAP optimization converged\nEvidence optimization iteration: 1: approximate log-evidence (𝐸) = -25382.187862691735\nIter     Function value   Gradient norm\n     0     2.493416e+04     1.770758e+01\n * time: 6.198883056640625e-5\n     1     2.491330e+04     1.655549e+01\n * time: 0.05198216438293457\n     2     2.487574e+04     1.496841e+01\n * time: 0.1039891242980957\n     3     2.481107e+04     1.249717e+01\n * time: 0.15512514114379883\n     4     2.471561e+04     8.400673e+00\n * time: 0.20711398124694824\n     5     2.462951e+04     2.547959e+00\n * time: 0.2629849910736084\n     6     2.461760e+04     4.920018e-01\n * time: 0.30104517936706543\n     7     2.461753e+04     2.734311e-03\n * time: 0.3390049934387207\n     8     2.461753e+04     1.063443e-07\n * time: 0.369035005569458\n     9     2.461753e+04     2.953931e-14\n * time: 0.4078941345214844\nEvidence optimization iteration: 2: precision (a) = 0.006298120303099656\nEvidence optimization iteration: 2: norm of the residual gradient of the log-posterior (g_residual) = 2.9539305029802065e-14\nEvidence optimization iteration: 2: MAP optimization converged\nEvidence optimization iteration: 2: approximate log-evidence (𝐸) = -24707.60295639283\nIter     Function value   Gradient norm\n     0     2.461416e+04     8.121148e-02\n * time: 6.29425048828125e-5\n     1     2.461414e+04     1.074387e-03\n * time: 0.05279088020324707\n     2     2.461414e+04     1.031625e-08\n * time: 0.10295701026916504\n     3     2.461414e+04     8.031770e-15\n * time: 0.1548769474029541\nEvidence optimization iteration: 3: precision (a) = 0.0051224104379705695\nEvidence optimization iteration: 3: norm of the residual gradient of the log-posterior (g_residual) = 8.031769693772617e-15\nEvidence optimization iteration: 3: MAP optimization converged\nEvidence optimization iteration: 3: approximate log-evidence (𝐸) = -24707.271175121816\nSPGLM.EvidenceOptimization{Vector{Float64}, Vector{Vector{Float64}}}\n  a: Array{Float64}((3,)) [0.5908446386657102, 0.006298120303099656, 0.0051224104379705695]\n  𝐸: Array{Float64}((3,)) [-25382.187862691735, -24707.60295639283, -24707.271175121816]\n  MAP_g_residual: Array{Float64}((3,)) [1.2256862191861728e-12, 2.9539305029802065e-14, 8.031769693772617e-15]\n  𝐰: Array{Vector{Float64}}((3,))\n\njulia\u003e characterization = SPGLM.Characterization(model)\njulia\u003e SPGLM.save(model)\njulia\u003e SPGLM.save(eo, model.options.outputpath)\njulia\u003e SPGLM.save(characterization, model.options.outputpath)\n```\nThis should take no more than a few mintues\n\n### MATLAB command window\nLet's check whether the model actually fit the data, using utilities in [+SPGLM](/src/+SPGLM/) and [+TZL](https://github.com/Brody-Lab/tzluo/tree/master/%2BTZL)\n```\n\u003e\u003e analysispath = 'V:\\Documents\\tzluo\\analyses\\analysis_2024_04_23a_test_SPGLM'\n\u003e\u003e models = readtable(fullfile(analysispath, \"models.csv\"), 'delimiter', ',');\n\u003e\u003e models.outputpath = TZL.cup2windows(string(models.outputpath));\n\u003e\u003e load(fullfile(models.outputpath{1}, 'model.mat'))\n\u003e\u003e load(fullfile(models.outputpath{1}, 'characterization.mat'))\n\u003e\u003e peths = SPGLM.tabulatepeths(peths);\n\u003e\u003e kernels = SPGLM.tabulatekernels(kernels);\n```\nPlotting the peri-event time histograms (PETH), aligned to the rat's movement onset, and conditioned on whether it made a left or a right choice:\n\n```\n\u003e\u003e reference_event = \"movement\";\n\u003e\u003e conditions = [\"leftchoice\", \"rightchoice\"];\n\u003e\u003e SPGLM.stylizeaxes\n\u003e\u003e colors = get(gca, 'colororder');\n\u003e\u003e h = [];\n\u003e\u003e k = 0;\n\u003e\u003e for i = 1:numel(conditions)\n    index = peths.condition == conditions(i) \u0026 peths.reference_event == reference_event;\n    k =k + 1;\n    h(k) = plot(peths.timesteps_s{index}, peths.observed{index}, '-', 'color', colors(i,:));\n    k =k + 1;\n    h(k) = plot(peths.timesteps_s{index}, peths.predicted{index}, '-', ...\n        'linewidth', 1, 'color', colors(i,:));\nend\n\u003e\u003e xlabel(['time from ' char(reference_event) ' (s)'])\n\u003e\u003e ylabel('spikes/s')\n\u003e\u003e ylim(ylim.*[0,1])\n\u003e\u003e legend(h, {'left choice, observed', 'left choice, predicted', ...\n    'right choice, observed', 'right choice, predicted'}, 'location', 'northwest')\n```\n\n\u003cimg src=\"/example/analysis_2024_04_22a_test_SPGLM/peth_perimovement.svg\" width = \"400\"\u003e\n\nThe model captures the choice-dependent peri-movement neuronal activity by treating the moment of movement onset as a transient impulse and fits convolution filters aligned to the impulse. Separate convolution kernels are fit for different choices. \n\n\u003cimg src=\"/example/analysis_2024_04_22a_test_SPGLM/kernel_perimovement.svg\" width = \"400\"\u003e\n\nUsing similar code as above to plot the PETH aligned to the onset of auditory click trains, which is always preceded by a left and right click being played simultaneously (i.e., a so-called \"stereoclick\"). The meaning of the color and the line thickness are as above\n\n```\n\u003e\u003e reference_event = \"stereoclick\";\n```\n\n\u003cimg src=\"/example/analysis_2024_04_22a_test_SPGLM/peth_stereoclick.svg\" width = \"400\"\u003e\n\nOr aligned to the onset of nose fixation\n\n```\n\u003e\u003e reference_event = \"cpoke_in\";\n```\n\n\u003cimg src=\"/example/analysis_2024_04_22a_test_SPGLM/peth_perifixation.svg\" width = \"400\"\u003e\n\nWe can also check whether we capture the response aligned to either a left or a right click (also called the \"click-triggered average\").\n\n\u003cimg src=\"/example/analysis_2024_04_22a_test_SPGLM/peth_click.svg\" width = \"400\"\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrody-lab%2Ftzl_spglm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrody-lab%2Ftzl_spglm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrody-lab%2Ftzl_spglm/lists"}