From 07cb714ef7cee01d5e55f678c12e386c864b2980 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Sun, 10 Dec 2017 21:14:23 -0800 Subject: [PATCH 1/6] CLN: ASV index object benchmark --- asv_bench/benchmarks/ctors.py | 25 ++- asv_bench/benchmarks/index_object.py | 243 +++++++++++++-------------- asv_bench/benchmarks/indexing.py | 44 +++++ 3 files changed, 176 insertions(+), 136 deletions(-) diff --git a/asv_bench/benchmarks/ctors.py b/asv_bench/benchmarks/ctors.py index 65af7b077d80f..af9c133e4968b 100644 --- a/asv_bench/benchmarks/ctors.py +++ b/asv_bench/benchmarks/ctors.py @@ -1,5 +1,7 @@ import numpy as np -from pandas import Series, Index, DatetimeIndex, Timestamp +import pandas.util.testing as tm +from pandas import (DataFrame, Series, Index, DatetimeIndex, Timestamp, + MultiIndex) from .pandas_vb_common import setup # noqa @@ -35,21 +37,32 @@ class SeriesDtypesConstructors(object): goal_time = 0.2 def setup(self): - N = 10**2 + N = 10**4 self.arr = np.random.randn(N, N) self.arr_str = np.array(['foo', 'bar', 'baz'], dtype=object) - - self.data = np.random.randn(N) - self.index = Index(np.arange(N)) - self.s = Series([Timestamp('20110101'), Timestamp('20120101'), Timestamp('20130101')] * N * 10) def time_index_from_array_string(self): Index(self.arr_str) + def time_index_from_array_floats(self): + Index(self.arr) + def time_dtindex_from_series(self): DatetimeIndex(self.s) def time_dtindex_from_index_with_series(self): Index(self.s) + + +class MultiIndexConstructor(object): + + goal_time = 0.2 + + def setup(self): + N = 10**4 + self.iterables = [tm.makeStringIndex(N), range(20)] + + def time_multiindex_from_iterables(self): + MultiIndex.from_product(self.iterables) diff --git a/asv_bench/benchmarks/index_object.py b/asv_bench/benchmarks/index_object.py index a607168ea0457..f649b8d935966 100644 --- a/asv_bench/benchmarks/index_object.py +++ b/asv_bench/benchmarks/index_object.py @@ -1,207 +1,178 @@ -from .pandas_vb_common import * +import numpy as np +import pandas.util.testing as tm +from pandas import date_range, DatetimeIndex, Index, MultiIndex, RangeIndex + +from .pandas_vb_common import setup # noqa class SetOperations(object): + goal_time = 0.2 def setup(self): - self.rng = date_range('1/1/2000', periods=10000, freq='T') - self.rng2 = self.rng[:(-1)] + self.dates_left = date_range('1/1/2000', periods=10000, freq='T') + self.dates_right = self.dates_left[:(-1)] - # object index with datetime values - if (self.rng.dtype == object): - self.idx_rng = self.rng.view(Index) - else: - self.idx_rng = self.rng.astype(object) - self.idx_rng2 = self.idx_rng[:(-1)] + fmt = '%Y-%m-%d %H:%M:%S' + self.date_str_left = Index(self.dates_left.strftime(fmt)) + self.date_str_right = self.date_str_left[:-1] # other datetime N = 100000 A = N - 20000 B = N + 20000 - self.dtidx1 = DatetimeIndex(range(N)) - self.dtidx2 = DatetimeIndex(range(A, B)) - self.dtidx3 = DatetimeIndex(range(N, B)) - - # integer - self.N = 1000000 - self.options = np.arange(self.N) - self.left = Index( - self.options.take(np.random.permutation(self.N)[:(self.N // 2)])) - self.right = Index( - self.options.take(np.random.permutation(self.N)[:(self.N // 2)])) - - # strings - N = 10000 - strs = tm.rands_array(10, N) - self.leftstr = Index(strs[:N * 2 // 3]) - self.rightstr = Index(strs[N // 3:]) + self.datetime_left = DatetimeIndex(range(N)) + self.datetime_right = DatetimeIndex(range(A, B)) + self.datetime_right2 = DatetimeIndex(range(N, B)) + + options = np.arange(N) + self.int_left = Index(options.take(np.random.permutation(N)[:N // 2])) + self.int_right = Index(options.take(np.random.permutation(N)[:N // 2])) + + strs = tm.rands_array(10, N / 10) + self.str_left = Index(strs[:N / 10 * 2 // 3]) + self.str_right = Index(strs[N / 10 // 3:]) def time_datetime_intersection(self): - self.rng.intersection(self.rng2) + self.dates_left.intersection(self.dates_right) def time_datetime_union(self): - self.rng.union(self.rng2) + self.dates_left.union(self.dates_right) def time_datetime_difference(self): - self.dtidx1.difference(self.dtidx2) + self.datetime_left.difference(self.datetime_right) def time_datetime_difference_disjoint(self): - self.dtidx1.difference(self.dtidx3) + self.datetime_left.difference(self.datetime_right2) def time_datetime_symmetric_difference(self): - self.dtidx1.symmetric_difference(self.dtidx2) + self.datetime_left.symmetric_difference(self.datetime_right) def time_index_datetime_intersection(self): - self.idx_rng.intersection(self.idx_rng2) + self.date_str_left.intersection(self.date_str_right) def time_index_datetime_union(self): - self.idx_rng.union(self.idx_rng2) + self.date_str_left.union(self.date_str_right) def time_int64_intersection(self): - self.left.intersection(self.right) + self.int_left.intersection(self.int_right) def time_int64_union(self): - self.left.union(self.right) + self.int_left.union(self.int_right) def time_int64_difference(self): - self.left.difference(self.right) + self.int_left.difference(self.int_right) def time_int64_symmetric_difference(self): - self.left.symmetric_difference(self.right) + self.int_left.symmetric_difference(self.int_right) def time_str_difference(self): - self.leftstr.difference(self.rightstr) + self.str_left.difference(self.str_right) def time_str_symmetric_difference(self): - self.leftstr.symmetric_difference(self.rightstr) + self.str_left.symmetric_difference(self.str_right) class Datetime(object): + goal_time = 0.2 def setup(self): - self.dr = pd.date_range('20000101', freq='D', periods=10000) + self.dr = date_range('20000101', freq='D', periods=10000) def time_is_dates_only(self): self.dr._is_dates_only -class Float64(object): - goal_time = 0.2 +class Ops(object): - def setup(self): - self.idx = tm.makeFloatIndex(1000000) - self.mask = ((np.arange(self.idx.size) % 3) == 0) - self.series_mask = Series(self.mask) + sample_time = 0.2 + params = ['float', 'int'] + param_names = ['dtype'] - self.baseidx = np.arange(1000000.0) + def setup(self, dtype): + N = 10**6 + indexes = {'int': 'makeIntIndex', 'float': 'makeFloatIndex'} + self.index = getattr(tm, indexes[dtype])(N) - def time_boolean_indexer(self): - self.idx[self.mask] + def time_add(self, dtype): + self.index + 2 - def time_boolean_series_indexer(self): - self.idx[self.series_mask] + def time_subtract(self, dtype): + self.index - 2 - def time_construct(self): - Index(self.baseidx) + def time_multiply(self, dtype): + self.index * 2 - def time_div(self): - (self.idx / 2) + def time_divide(self, dtype): + self.index / 2 - def time_get(self): - self.idx[1] + def time_modulo(self, dtype): + self.index % 2 - def time_mul(self): - (self.idx * 2) - def time_slice_indexer_basic(self): - self.idx[:(-1)] +class Duplicated(object): - def time_slice_indexer_even(self): - self.idx[::2] - - -class StringIndex(object): goal_time = 0.2 def setup(self): - self.idx = tm.makeStringIndex(1000000) - self.mask = ((np.arange(1000000) % 3) == 0) - self.series_mask = Series(self.mask) - - def time_boolean_indexer(self): - self.idx[self.mask] - - def time_boolean_series_indexer(self): - self.idx[self.series_mask] - - def time_slice_indexer_basic(self): - self.idx[:(-1)] - - def time_slice_indexer_even(self): - self.idx[::2] - - -class Multi1(object): - goal_time = 0.2 - - def setup(self): - (n, k) = (200, 5000) - self.levels = [np.arange(n), tm.makeStringIndex(n).values, (1000 + np.arange(n))] - self.labels = [np.random.choice(n, (k * n)) for lev in self.levels] - self.mi = MultiIndex(levels=self.levels, labels=self.labels) - - self.iterables = [tm.makeStringIndex(10000), range(20)] + n, k = 200, 5000 + levels = [np.arange(n), + tm.makeStringIndex(n).values, + 1000 + np.arange(n)] + labels = [np.random.choice(n, (k * n)) for lev in levels] + self.mi = MultiIndex(levels=levels, labels=labels) def time_duplicated(self): self.mi.duplicated() - def time_from_product(self): - MultiIndex.from_product(self.iterables) +class Sortlevel(object): -class Multi2(object): goal_time = 0.2 def setup(self): - self.n = ((((3 * 5) * 7) * 11) * (1 << 10)) - (low, high) = (((-1) << 12), (1 << 12)) - self.f = (lambda k: np.repeat(np.random.randint(low, high, (self.n // k)), k)) - self.i = np.random.permutation(self.n) - self.mi = MultiIndex.from_arrays([self.f(11), self.f(7), self.f(5), self.f(3), self.f(1)])[self.i] + n = 10**6 + low, high = -5000, 5000 + arrs = [np.repeat(np.random.randint(low, high, (n // k)), k) + for k in [11, 7, 5, 3, 1]] + self.mi_int = MultiIndex.from_arrays(arrs)[np.random.permutation(n)] - self.a = np.repeat(np.arange(100), 1000) - self.b = np.tile(np.arange(1000), 100) - self.midx2 = MultiIndex.from_arrays([self.a, self.b]) - self.midx2 = self.midx2.take(np.random.permutation(np.arange(100000))) + a = np.repeat(np.arange(100), 1000) + b = np.tile(np.arange(1000), 100) + self.mi = MultiIndex.from_arrays([a, b]) + self.mi = self.mi.take(np.random.permutation(np.arange(n / 10))) def time_sortlevel_int64(self): - self.mi.sortlevel() + self.mi_int.sortlevel() def time_sortlevel_zero(self): - self.midx2.sortlevel(0) + self.mi.sortlevel(0) def time_sortlevel_one(self): - self.midx2.sortlevel(1) + self.mi.sortlevel(1) + +class MultiIndexValues(object): -class Multi3(object): goal_time = 0.2 - def setup(self): - self.level1 = range(1000) - self.level2 = date_range(start='1/1/2012', periods=100) - self.mi = MultiIndex.from_product([self.level1, self.level2]) + def setup_cache(self): - def time_datetime_level_values_full(self): - self.mi.copy().values + level1 = range(1000) + level2 = date_range(start='1/1/2012', periods=100) + mi = MultiIndex.from_product([level1, level2]) + return mi - def time_datetime_level_values_sliced(self): - self.mi[:10].values + def time_datetime_level_values_copy(self, mi): + mi.copy().values + + def time_datetime_level_values_sliced(self, mi): + mi[:10].values class Range(object): + goal_time = 0.2 def setup(self): @@ -221,20 +192,32 @@ def time_min_trivial(self): self.idx_inc.min() -class IndexOps(object): +class IndexAppend(object): + goal_time = 0.2 def setup(self): - N = 10000 - self.ridx = [RangeIndex(i * 100, (i + 1) * 100) for i in range(N)] - self.iidx = [idx.astype(int) for idx in self.ridx] - self.oidx = [idx.astype(str) for idx in self.iidx] - def time_concat_range(self): - self.ridx[0].append(self.ridx[1:]) - - def time_concat_int(self): - self.iidx[0].append(self.iidx[1:]) - - def time_concat_obj(self): - self.oidx[0].append(self.oidx[1:]) + N = 10000 + self.range_idx = RangeIndex(0, 100) + self.int_idx = self.range_idx.astype(int) + self.obj_idx = self.int_idx.astype(str) + self.range_idxs = [] + self.int_idxs = [] + self.object_idxs = [] + for i in range(1, N): + r_idx = RangeIndex(i * 100, (i + 1) * 100) + self.range_idxs.append(r_idx) + i_idx = r_idx.astype(int) + self.int_idxs.append(i_idx) + o_idx = i_idx.astype(str) + self.object_idxs.append(o_idx) + + def time_append_range_list(self): + self.range_idx.append(self.range_idxs) + + def time_append_int_list(self): + self.int_idx.append(self.int_idxs) + + def time_append_obj_list(self): + self.obj_idx.append(self.object_idxs) diff --git a/asv_bench/benchmarks/indexing.py b/asv_bench/benchmarks/indexing.py index f271b82c758ee..91abef55bcc52 100644 --- a/asv_bench/benchmarks/indexing.py +++ b/asv_bench/benchmarks/indexing.py @@ -369,3 +369,47 @@ def time_assign_with_setitem(self): self.df[i] = np.random.randn(self.N) +class Float64(object): + + goal_time = 0.2 + + def setup(self): + self.idx = tm.makeFloatIndex(1000000) + self.mask = ((np.arange(self.idx.size) % 3) == 0) + self.series_mask = Series(self.mask) + + def time_boolean_indexer(self): + self.idx[self.mask] + + def time_boolean_series_indexer(self): + self.idx[self.series_mask] + + def time_get(self): + self.idx[1] + + def time_slice_indexer_basic(self): + self.idx[:(-1)] + + def time_slice_indexer_even(self): + self.idx[::2] + + +class StringIndex(object): + goal_time = 0.2 + + def setup(self): + self.idx = tm.makeStringIndex(1000000) + self.mask = ((np.arange(1000000) % 3) == 0) + self.series_mask = Series(self.mask) + + def time_boolean_indexer(self): + self.idx[self.mask] + + def time_boolean_series_indexer(self): + self.idx[self.series_mask] + + def time_slice_indexer_basic(self): + self.idx[:(-1)] + + def time_slice_indexer_even(self): + self.idx[::2] From 61af06e67712f5f86e3179a807ad2f511bb04714 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Tue, 12 Dec 2017 21:00:59 -0800 Subject: [PATCH 2/6] Fix failing benchmark --- asv_bench/benchmarks/index_object.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/asv_bench/benchmarks/index_object.py b/asv_bench/benchmarks/index_object.py index f649b8d935966..f045cf8c7de4f 100644 --- a/asv_bench/benchmarks/index_object.py +++ b/asv_bench/benchmarks/index_object.py @@ -132,8 +132,8 @@ class Sortlevel(object): goal_time = 0.2 def setup(self): - n = 10**6 - low, high = -5000, 5000 + n = 1182720 + low, high = -4096, 4096 arrs = [np.repeat(np.random.randint(low, high, (n // k)), k) for k in [11, 7, 5, 3, 1]] self.mi_int = MultiIndex.from_arrays(arrs)[np.random.permutation(n)] @@ -141,7 +141,7 @@ def setup(self): a = np.repeat(np.arange(100), 1000) b = np.tile(np.arange(1000), 100) self.mi = MultiIndex.from_arrays([a, b]) - self.mi = self.mi.take(np.random.permutation(np.arange(n / 10))) + self.mi = self.mi.take(np.random.permutation(np.arange(100000))) def time_sortlevel_int64(self): self.mi_int.sortlevel() From a8f143c095c78c1c8965a2e2a8749fb69633c31d Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Tue, 12 Dec 2017 21:09:30 -0800 Subject: [PATCH 3/6] pep8speaks --- asv_bench/benchmarks/index_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asv_bench/benchmarks/index_object.py b/asv_bench/benchmarks/index_object.py index f045cf8c7de4f..a8c9b63464c43 100644 --- a/asv_bench/benchmarks/index_object.py +++ b/asv_bench/benchmarks/index_object.py @@ -2,7 +2,7 @@ import pandas.util.testing as tm from pandas import date_range, DatetimeIndex, Index, MultiIndex, RangeIndex -from .pandas_vb_common import setup # noqa +from .pandas_vb_common import setup # noqa class SetOperations(object): From f2e73026ea3d8f4c3b6dc1e7b37afa63a410f11e Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Thu, 14 Dec 2017 21:39:46 -0800 Subject: [PATCH 4/6] Parameterize set benchmark --- asv_bench/benchmarks/index_object.py | 80 +++++++++------------------- 1 file changed, 26 insertions(+), 54 deletions(-) diff --git a/asv_bench/benchmarks/index_object.py b/asv_bench/benchmarks/index_object.py index a8c9b63464c43..3c8ae01cc81a9 100644 --- a/asv_bench/benchmarks/index_object.py +++ b/asv_bench/benchmarks/index_object.py @@ -8,69 +8,41 @@ class SetOperations(object): goal_time = 0.2 + params = (['datetime', 'date_string', 'int', 'strings'], + ['intersection', 'union', 'symmetric_difference']) + param_names = ['dtype', 'method'] - def setup(self): - self.dates_left = date_range('1/1/2000', periods=10000, freq='T') - self.dates_right = self.dates_left[:(-1)] - + def setup(self, dtype, method): + N = 10**5 + dates_left = date_range('1/1/2000', periods=N, freq='T') fmt = '%Y-%m-%d %H:%M:%S' - self.date_str_left = Index(self.dates_left.strftime(fmt)) - self.date_str_right = self.date_str_left[:-1] - - # other datetime - N = 100000 - A = N - 20000 - B = N + 20000 - self.datetime_left = DatetimeIndex(range(N)) - self.datetime_right = DatetimeIndex(range(A, B)) - self.datetime_right2 = DatetimeIndex(range(N, B)) + date_str_left = Index(dates_left.strftime(fmt)) + int_left = Index(np.arange(N)) + str_left = tm.makeStringIndex(N) + data = {'datetime': {'left': dates_left, 'right': dates_left[:-1]}, + 'date_string': {'left': date_str_left, + 'right': date_str_left[:-1]}, + 'int': {'left': int_left, 'right': int_left[:-1]}, + 'strings': {'left': str_left, 'right': str_left[:-1]}} + self.left = data[dtype]['left'] + self.right = data[dtype]['right'] - options = np.arange(N) - self.int_left = Index(options.take(np.random.permutation(N)[:N // 2])) - self.int_right = Index(options.take(np.random.permutation(N)[:N // 2])) + def time_operation(self, dtype, method): + getattr(self.left, method)(self.right) - strs = tm.rands_array(10, N / 10) - self.str_left = Index(strs[:N / 10 * 2 // 3]) - self.str_right = Index(strs[N / 10 // 3:]) - def time_datetime_intersection(self): - self.dates_left.intersection(self.dates_right) +class SetDisjoint(object): - def time_datetime_union(self): - self.dates_left.union(self.dates_right) + goal_time = 0.2 - def time_datetime_difference(self): - self.datetime_left.difference(self.datetime_right) + def setup(self): + N = 10**5 + B = N + 20000 + self.datetime_left = DatetimeIndex(range(N)) + self.datetime_right = DatetimeIndex(range(N, B)) def time_datetime_difference_disjoint(self): - self.datetime_left.difference(self.datetime_right2) - - def time_datetime_symmetric_difference(self): - self.datetime_left.symmetric_difference(self.datetime_right) - - def time_index_datetime_intersection(self): - self.date_str_left.intersection(self.date_str_right) - - def time_index_datetime_union(self): - self.date_str_left.union(self.date_str_right) - - def time_int64_intersection(self): - self.int_left.intersection(self.int_right) - - def time_int64_union(self): - self.int_left.union(self.int_right) - - def time_int64_difference(self): - self.int_left.difference(self.int_right) - - def time_int64_symmetric_difference(self): - self.int_left.symmetric_difference(self.int_right) - - def time_str_difference(self): - self.str_left.difference(self.str_right) - - def time_str_symmetric_difference(self): - self.str_left.symmetric_difference(self.str_right) + self.datetime_left.difference(self.datetime_right) class Datetime(object): From 7afcc7fa799c05be30b8e6bd0a74d16c7bc06651 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Sat, 16 Dec 2017 11:18:21 -0800 Subject: [PATCH 5/6] Move indexing back to index_object --- asv_bench/benchmarks/index_object.py | 31 ++++++++++++++++++- asv_bench/benchmarks/indexing.py | 46 ---------------------------- 2 files changed, 30 insertions(+), 47 deletions(-) diff --git a/asv_bench/benchmarks/index_object.py b/asv_bench/benchmarks/index_object.py index 3c8ae01cc81a9..d73b216478ad5 100644 --- a/asv_bench/benchmarks/index_object.py +++ b/asv_bench/benchmarks/index_object.py @@ -1,6 +1,7 @@ import numpy as np import pandas.util.testing as tm -from pandas import date_range, DatetimeIndex, Index, MultiIndex, RangeIndex +from pandas import (Series, date_range, DatetimeIndex, Index, MultiIndex, + RangeIndex) from .pandas_vb_common import setup # noqa @@ -193,3 +194,31 @@ def time_append_int_list(self): def time_append_obj_list(self): self.obj_idx.append(self.object_idxs) + + +class Indexing(object): + + goal_time = 0.2 + params = ['String', 'Float', 'Int'] + param_names = ['dtype'] + + def setup(self, dtype): + N = 10**6 + self.idx = getattr(tm, 'make{}Index'.format(dtype))(N) + self.array_mask = (np.arange(N) % 3) == 0 + self.series_mask = Series(self.array_mask) + + def time_boolean_array(self, dtype): + self.idx[self.array_mask] + + def time_boolean_series(self, dtype): + self.idx[self.series_mask] + + def time_get(self, dtype): + self.idx[1] + + def time_slice(self, dtype): + self.idx[:-1] + + def time_slice_step(self, dtype): + self.idx[::2] diff --git a/asv_bench/benchmarks/indexing.py b/asv_bench/benchmarks/indexing.py index 91abef55bcc52..5b12f6ea89614 100644 --- a/asv_bench/benchmarks/indexing.py +++ b/asv_bench/benchmarks/indexing.py @@ -367,49 +367,3 @@ def time_assign_with_setitem(self): np.random.seed(1234) for i in range(100): self.df[i] = np.random.randn(self.N) - - -class Float64(object): - - goal_time = 0.2 - - def setup(self): - self.idx = tm.makeFloatIndex(1000000) - self.mask = ((np.arange(self.idx.size) % 3) == 0) - self.series_mask = Series(self.mask) - - def time_boolean_indexer(self): - self.idx[self.mask] - - def time_boolean_series_indexer(self): - self.idx[self.series_mask] - - def time_get(self): - self.idx[1] - - def time_slice_indexer_basic(self): - self.idx[:(-1)] - - def time_slice_indexer_even(self): - self.idx[::2] - - -class StringIndex(object): - goal_time = 0.2 - - def setup(self): - self.idx = tm.makeStringIndex(1000000) - self.mask = ((np.arange(1000000) % 3) == 0) - self.series_mask = Series(self.mask) - - def time_boolean_indexer(self): - self.idx[self.mask] - - def time_boolean_series_indexer(self): - self.idx[self.series_mask] - - def time_slice_indexer_basic(self): - self.idx[:(-1)] - - def time_slice_indexer_even(self): - self.idx[::2] From add0d71bfdbf6e40c75b129ad4c2e806d21ade65 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Sat, 23 Dec 2017 22:27:12 -0800 Subject: [PATCH 6/6] Fix merge conflict --- asv_bench/benchmarks/ctors.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/asv_bench/benchmarks/ctors.py b/asv_bench/benchmarks/ctors.py index af9c133e4968b..3f9016787aab4 100644 --- a/asv_bench/benchmarks/ctors.py +++ b/asv_bench/benchmarks/ctors.py @@ -1,9 +1,8 @@ import numpy as np import pandas.util.testing as tm -from pandas import (DataFrame, Series, Index, DatetimeIndex, Timestamp, - MultiIndex) +from pandas import Series, Index, DatetimeIndex, Timestamp, MultiIndex -from .pandas_vb_common import setup # noqa +from .pandas_vb_common import setup # noqa class SeriesConstructors(object): @@ -23,7 +22,6 @@ class SeriesConstructors(object): def setup(self, data_fmt, with_index): N = 10**4 - np.random.seed(1234) arr = np.random.randn(N) self.data = data_fmt(arr) self.index = np.arange(N) if with_index else None