From 6c0e8d4aec81c83b13e1838d117a095f152f757b Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:10:00 +0100 Subject: [PATCH 01/11] remove copy statement --- elephant/statistics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elephant/statistics.py b/elephant/statistics.py index 45d9cd283..942790688 100644 --- a/elephant/statistics.py +++ b/elephant/statistics.py @@ -1178,12 +1178,12 @@ def time_histogram(spiketrains, bin_size, t_start=None, t_stop=None, def _counts() -> pq.Quantity: # 'counts': spike counts at each bin (as integer numbers). - return pq.Quantity(bin_hist, units=pq.dimensionless, copy=False) + return pq.Quantity(bin_hist, units=pq.dimensionless) def _mean() -> pq.Quantity: # 'mean': mean spike counts per spike train. return pq.Quantity(bin_hist / len(spiketrains), - units=pq.dimensionless, copy=False) + units=pq.dimensionless) def _rate() -> pq.Quantity: # 'rate': mean spike rate per spike train. Like 'mean', but the From a3fdbaac14c311673534f8c7afb1fe188086ae73 Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:13:12 +0100 Subject: [PATCH 02/11] add regression test for issue #648 --- elephant/test/test_statistics.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/elephant/test/test_statistics.py b/elephant/test/test_statistics.py index 426111810..96ad7e7ad 100644 --- a/elephant/test/test_statistics.py +++ b/elephant/test/test_statistics.py @@ -1089,6 +1089,18 @@ def test_annotations(self): self.assertIn('normalization', histogram.annotations) self.assertEqual(histogram.annotations['normalization'], output) + def test_time_histogram_regression_648_single_spiketrain(self): + # Create a single spike train + spiketrain = neo.SpikeTrain([0.1, 0.5, 1.0, 1.5, 2.0] * pq.s, t_stop=3.0 * pq.s) + + # Run time_histogram with spiketrain directly and observe the incorrect result + histogram_direct = statistics.time_histogram(spiketrain, output='rate', bin_size=0.5 * pq.s) + print("Histogram (direct):", histogram_direct) + + # Wrap spiketrain in a list and run time_histogram + histogram_wrapped = statistics.time_histogram([spiketrain], output='rate', bin_size=0.5 * pq.s) + np.testing.assert_array_equal(histogram_direct.magnitude, histogram_wrapped.magnitude) + class ComplexityTestCase(unittest.TestCase): def test_complexity_pdf_deprecated(self): From 06358788d2c559c1e2efa7d8d1b4be82134d894f Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:13:48 +0100 Subject: [PATCH 03/11] fix time_histogram for single spike_train --- elephant/statistics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elephant/statistics.py b/elephant/statistics.py index 942790688..930e5c81c 100644 --- a/elephant/statistics.py +++ b/elephant/statistics.py @@ -1182,13 +1182,13 @@ def _counts() -> pq.Quantity: def _mean() -> pq.Quantity: # 'mean': mean spike counts per spike train. - return pq.Quantity(bin_hist / len(spiketrains), + return pq.Quantity(bin_hist / binned_spiketrain.shape[0], units=pq.dimensionless) def _rate() -> pq.Quantity: # 'rate': mean spike rate per spike train. Like 'mean', but the # counts are additionally normalized by the bin width. - return bin_hist / (len(spiketrains) * bin_size) + return bin_hist / (binned_spiketrain.shape[0] * bin_size) output_mapping = {"counts": _counts, "mean": _mean, "rate": _rate} try: From 710a993e30ac5ddedae6e81c48f079ed08184af4 Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:19:40 +0100 Subject: [PATCH 04/11] update docstring --- elephant/statistics.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/elephant/statistics.py b/elephant/statistics.py index 930e5c81c..c5078deee 100644 --- a/elephant/statistics.py +++ b/elephant/statistics.py @@ -74,7 +74,7 @@ import scipy.signal from numpy import ndarray from scipy.special import erf -from typing import Union +from typing import List, Optional, Union import elephant.conversion as conv import elephant.kernels as kernels @@ -1062,17 +1062,21 @@ def optimal_kernel(st): @deprecated_alias(binsize='bin_size') -def time_histogram(spiketrains, bin_size, t_start=None, t_stop=None, - output='counts', binary=False): +def time_histogram(spiketrains: Union[List[neo.SpikeTrain], neo.SpikeTrain], + bin_size: pq.Quantity, + t_start: Optional[pq.Quantity] = None, + t_stop: Optional[pq.Quantity] = None, + output: str = 'counts', + binary: bool = False) -> neo.AnalogSignal: """ - Time Histogram of a list of `neo.SpikeTrain` objects. + Time Histogram of a list of :class:`neo.core.SpikeTrain` objects. Visualization of this function is covered in Viziphant: :func:`viziphant.statistics.plot_time_histogram`. Parameters ---------- - spiketrains : list of neo.SpikeTrain + spiketrains : list of :class:`neo.core.SpikeTrain` or :class:`neo.core.SpikeTrain` `neo.SpikeTrain`s with a common time axis (same `t_start` and `t_stop`) bin_size : pq.Quantity Width of the histogram's time bins. @@ -1080,14 +1084,14 @@ def time_histogram(spiketrains, bin_size, t_start=None, t_stop=None, Start time of the histogram. Only events in `spiketrains` falling between `t_start` and `t_stop` (both included) are considered in the histogram. - If None, the maximum `t_start` of all `neo.SpikeTrain`s is used as + If None, the maximum `t_start` of all :class:`neo.core.SpikeTrain`s is used as `t_start`. Default: None t_stop : pq.Quantity, optional Stop time of the histogram. Only events in `spiketrains` falling between `t_start` and `t_stop` (both included) are considered in the histogram. - If None, the minimum `t_stop` of all `neo.SpikeTrain`s is used as + If None, the minimum `t_stop` of all :class:`neo.core.SpikeTrain` s is used as `t_stop`. Default: None output : {'counts', 'mean', 'rate'}, optional @@ -1099,9 +1103,9 @@ def time_histogram(spiketrains, bin_size, t_start=None, t_stop=None, Default: 'counts' binary : bool, optional - If True, indicates whether all `neo.SpikeTrain` objects should first + If True, indicates whether all :class:`neo.core.SpikeTrain` objects should first be binned to a binary representation (using the - `conversion.BinnedSpikeTrain` class) and the calculation of the + [:class:`elephant.conversion.BinnedSpikeTrain` class] and the calculation of the histogram is based on this representation. Note that the output is not binary, but a histogram of the converted, binary representation. @@ -1110,8 +1114,8 @@ def time_histogram(spiketrains, bin_size, t_start=None, t_stop=None, Returns ------- neo.AnalogSignal - A `neo.AnalogSignal` object containing the histogram values. - `neo.AnalogSignal[j]` is the histogram computed between + A :class:`neo.core.SpikeTrain` object containing the histogram values. + :class:`neo.core.SpikeTrain `[j]` is the histogram computed between `t_start + j * bin_size` and `t_start + (j + 1) * bin_size`. Raises @@ -1129,7 +1133,7 @@ def time_histogram(spiketrains, bin_size, t_start=None, t_stop=None, See also -------- - elephant.conversion.BinnedSpikeTrain + :func:`elephant.conversion.BinnedSpikeTrain` Examples -------- From 64b50cba4b72ce0802addbc3b7695f8e78e75a86 Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:09:40 +0100 Subject: [PATCH 05/11] fix neo. spike trains to neo.spiketrain objects --- elephant/statistics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elephant/statistics.py b/elephant/statistics.py index c5078deee..3027c6f48 100644 --- a/elephant/statistics.py +++ b/elephant/statistics.py @@ -1077,7 +1077,7 @@ def time_histogram(spiketrains: Union[List[neo.SpikeTrain], neo.SpikeTrain], Parameters ---------- spiketrains : list of :class:`neo.core.SpikeTrain` or :class:`neo.core.SpikeTrain` - `neo.SpikeTrain`s with a common time axis (same `t_start` and `t_stop`) + `neo.SpikeTrain` objects with a common time axis (same `t_start` and `t_stop`) bin_size : pq.Quantity Width of the histogram's time bins. t_start : pq.Quantity, optional From a965d3025ceafd5a86b91a3ee57d5aeea5b50543 Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:10:31 +0100 Subject: [PATCH 06/11] fix bullet point list --- elephant/statistics.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/elephant/statistics.py b/elephant/statistics.py index 3027c6f48..9272bb01b 100644 --- a/elephant/statistics.py +++ b/elephant/statistics.py @@ -1096,10 +1096,9 @@ def time_histogram(spiketrains: Union[List[neo.SpikeTrain], neo.SpikeTrain], Default: None output : {'counts', 'mean', 'rate'}, optional Normalization of the histogram. Can be one of: - * 'counts': spike counts at each bin (as integer numbers). - * 'mean': mean spike counts per spike train. - * 'rate': mean spike rate per spike train. Like 'mean', but the - counts are additionally normalized by the bin width. + - 'counts': spike counts at each bin (as integer numbers). + - 'mean': mean spike counts per spike train. + - 'rate': mean spike rate per spike train. Like 'mean', but the counts are additionally normalized by the bin width. Default: 'counts' binary : bool, optional From 9c06d0d02010e3f559fefcd87648f0a7401b28a9 Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:49:30 +0100 Subject: [PATCH 07/11] fix bullet points --- elephant/statistics.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/elephant/statistics.py b/elephant/statistics.py index 9272bb01b..6a8fdcb10 100644 --- a/elephant/statistics.py +++ b/elephant/statistics.py @@ -1096,9 +1096,11 @@ def time_histogram(spiketrains: Union[List[neo.SpikeTrain], neo.SpikeTrain], Default: None output : {'counts', 'mean', 'rate'}, optional Normalization of the histogram. Can be one of: + - 'counts': spike counts at each bin (as integer numbers). - 'mean': mean spike counts per spike train. - - 'rate': mean spike rate per spike train. Like 'mean', but the counts are additionally normalized by the bin width. + - 'rate': mean spike rate per spike train. Like 'mean', but the counts are additionally normalized + by the bin width. Default: 'counts' binary : bool, optional From 7fee5adcaa5daf7754256cfd39d6b5dad2976348 Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Wed, 8 Jan 2025 15:08:28 +0100 Subject: [PATCH 08/11] Update elephant/test/test_statistics.py --- elephant/test/test_statistics.py | 1 - 1 file changed, 1 deletion(-) diff --git a/elephant/test/test_statistics.py b/elephant/test/test_statistics.py index 96ad7e7ad..0db9829cd 100644 --- a/elephant/test/test_statistics.py +++ b/elephant/test/test_statistics.py @@ -1095,7 +1095,6 @@ def test_time_histogram_regression_648_single_spiketrain(self): # Run time_histogram with spiketrain directly and observe the incorrect result histogram_direct = statistics.time_histogram(spiketrain, output='rate', bin_size=0.5 * pq.s) - print("Histogram (direct):", histogram_direct) # Wrap spiketrain in a list and run time_histogram histogram_wrapped = statistics.time_histogram([spiketrain], output='rate', bin_size=0.5 * pq.s) From f6580e08513fc0753f1784185a944343ca1181d3 Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Wed, 8 Jan 2025 15:17:36 +0100 Subject: [PATCH 09/11] check if result is correct --- elephant/test/test_statistics.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/elephant/test/test_statistics.py b/elephant/test/test_statistics.py index 0db9829cd..ededadff0 100644 --- a/elephant/test/test_statistics.py +++ b/elephant/test/test_statistics.py @@ -1099,10 +1099,11 @@ def test_time_histogram_regression_648_single_spiketrain(self): # Wrap spiketrain in a list and run time_histogram histogram_wrapped = statistics.time_histogram([spiketrain], output='rate', bin_size=0.5 * pq.s) np.testing.assert_array_equal(histogram_direct.magnitude, histogram_wrapped.magnitude) + np.testing.assert_array_equal(histogram_wrapped.magnitude.flatten(), [2., 2., 2., 2., 2., 0.]*pq.Hz) class ComplexityTestCase(unittest.TestCase): - def test_complexity_pdf_deprecated(self): + def test_complexiy_pdf_deprecated(self): spiketrain_a = neo.SpikeTrain( [0.5, 0.7, 1.2, 2.3, 4.3, 5.5, 6.7] * pq.s, t_stop=10.0 * pq.s) spiketrain_b = neo.SpikeTrain( From a406a79d42abfa16ec04ce28ce879cdf2693c60d Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Wed, 8 Jan 2025 15:20:36 +0100 Subject: [PATCH 10/11] add comments --- elephant/test/test_statistics.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/elephant/test/test_statistics.py b/elephant/test/test_statistics.py index ededadff0..47f453623 100644 --- a/elephant/test/test_statistics.py +++ b/elephant/test/test_statistics.py @@ -1098,8 +1098,10 @@ def test_time_histogram_regression_648_single_spiketrain(self): # Wrap spiketrain in a list and run time_histogram histogram_wrapped = statistics.time_histogram([spiketrain], output='rate', bin_size=0.5 * pq.s) + # Check if passing a single spiketrain directly vs in a list gives same result np.testing.assert_array_equal(histogram_direct.magnitude, histogram_wrapped.magnitude) - np.testing.assert_array_equal(histogram_wrapped.magnitude.flatten(), [2., 2., 2., 2., 2., 0.]*pq.Hz) + # Check if the spike rate calculation is correct for a single spike train + np.testing.assert_array_equal(histogram_direct.magnitude.flatten(), [2., 2., 2., 2., 2., 0.]*pq.Hz) class ComplexityTestCase(unittest.TestCase): From aeb36039b1a7c1bc7b4b16b994362021750a82ea Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Fri, 24 Jan 2025 13:30:36 +0100 Subject: [PATCH 11/11] Update elephant/test/test_statistics.py --- elephant/test/test_statistics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elephant/test/test_statistics.py b/elephant/test/test_statistics.py index 47f453623..1e6a8fec3 100644 --- a/elephant/test/test_statistics.py +++ b/elephant/test/test_statistics.py @@ -1105,7 +1105,7 @@ def test_time_histogram_regression_648_single_spiketrain(self): class ComplexityTestCase(unittest.TestCase): - def test_complexiy_pdf_deprecated(self): + def test_complexity_pdf_deprecated(self): spiketrain_a = neo.SpikeTrain( [0.5, 0.7, 1.2, 2.3, 4.3, 5.5, 6.7] * pq.s, t_stop=10.0 * pq.s) spiketrain_b = neo.SpikeTrain(