Skip to content

Commit 55dd845

Browse files
authored
Merge pull request #781 from nkeim/doc-gapped-msd
DOC: Gapped MSD
2 parents 5a73ec8 + b2b93d1 commit 55dd845

File tree

3 files changed

+59
-2
lines changed

3 files changed

+59
-2
lines changed

doc/releases/v0.7.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
v0.7
2+
------
3+
4+
trackpy v0.7 includes an enhancement/bug fix to how MSD calculations handle gaps
5+
in trajectories. The msd() and imsd() functions now output NaN values (instead
6+
of zeros) for portions of their output that could not be measured. The emsd()
7+
function now properly de-weights missing data, and so its output may be
8+
significantly different (and more accurate) when gaps are numerous and large.
9+
10+
Bug fixes
11+
~~~~~~~~~
12+
- MSD properly handles gaps in trajectories (@vivarose, #773)

trackpy/motion.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,15 @@ def msd(traj, mpp, fps, max_lagtime=100, detail=False, pos_columns=None):
2626
2727
If detail is True, the DataFrame also contains a column N,
2828
the estimated number of statistically independent measurements
29-
that comprise the result at each lagtime.
29+
that comprise the result at each lagtime. Where gaps make the
30+
MSD undefined, N is set to 0. All N values are then rescaled to
31+
make the total unchanged, so that the particle will be weighted
32+
correctly in an ensemble calculation.
3033
3134
Notes
3235
-----
3336
Input units are pixels and frames. Output units are microns and seconds.
37+
Outputs NaN for lag times that could not be measured, due to gaps.
3438
3539
See also
3640
--------
@@ -81,7 +85,13 @@ def _msd_iter(pos, lagtimes):
8185

8286
def _msd_gaps(traj, mpp, fps, max_lagtime=100, detail=False, pos_columns=None):
8387
"""Compute the mean displacement and mean squared displacement of one
84-
trajectory over a range of time intervals."""
88+
trajectory over a range of time intervals.
89+
90+
If 'detail' is True, N is set to 0 wherever gaps make the MSD undefined. All
91+
N values are then rescaled to make the total unchanged, so that the particle
92+
will be weighted correctly in an ensemble calculation. For details, see
93+
the discussion in PR #773: https://github.com/soft-matter/trackpy/pull/773
94+
"""
8595
if pos_columns is None:
8696
pos_columns = ['x', 'y']
8797
result_columns = ['<{}>'.format(p) for p in pos_columns] + \
@@ -196,6 +206,7 @@ def imsd(traj, mpp, fps, max_lagtime=100, statistic='msd', pos_columns=None):
196206
Notes
197207
-----
198208
Input units are pixels and frames. Output units are microns and seconds.
209+
Outputs NaN for lag times that could not be measured, due to gaps.
199210
"""
200211
ids = []
201212
msds = []

trackpy/tests/test_motion.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,13 @@ def setUp(self):
161161
'frame': np.arange(1, N), 'particle': np.ones(N - 1)})
162162
self.steppers = conformity(pandas_concat([a, b]))
163163

164+
self.badly_gapped_walks = conformity(DataFrame({
165+
'frame': [1, 2, 3, 4, 5, 6, 1, 2, 6], # create an example trajectory where particle 2 disappears for a few frames and returns.
166+
'x': [4, 5, 4, 5, 3, 2, -3, -1, -2],
167+
'y': [6, 7, 8, 6, 7, 6, 10, 11, 10],
168+
'particle': [1, 1, 1, 1, 1, 1, 2, 2, 2]
169+
}))
170+
164171
def test_zero_emsd(self):
165172
N = 10
166173
actual = tp.emsd(self.dead_still, 1, 1)
@@ -207,6 +214,33 @@ def test_linear_emsd_gaps(self):
207214
actual.index = expected.index
208215
assert_series_equal(np.round(actual), expected)
209216

217+
def test_major_gap_imsd(self):
218+
"""Large gap that should strongly affect emsd.
219+
220+
Test data and fix (PR #773) by @vivarose
221+
"""
222+
imsd = tp.imsd(self.badly_gapped_walks, mpp=1, fps=1)
223+
nans = imsd.isna()
224+
225+
assert nans[2][2.0]
226+
assert nans[2][3.0]
227+
assert sum(nans.values.flatten()) == 2
228+
229+
def test_major_gap_emsd(self):
230+
"""Large gap that should strongly affect emsd.
231+
232+
Test data and fix (PR #773) by @vivarose
233+
"""
234+
actual = tp.emsd(self.badly_gapped_walks, mpp=1, fps=1)
235+
expected = Series({
236+
1.0: 4.012847555129437,
237+
2.0: 4.000000000000007,
238+
3.0: 4.333333333333333,
239+
4.0: 4.193672099712366,
240+
5.0: 2.645254074784259
241+
})
242+
assert_almost_equal(expected.values, actual.values)
243+
210244
def test_direction_corr(self):
211245
# just a smoke test
212246
f1, f2 = 2, 6

0 commit comments

Comments
 (0)