Repository: mars-project/mars

Issue:
[BUG] scipy 1.10 raises ValueError: `popmean.shape[axis]` must equal 1.
<!--
Thank you for your contribution!

Please review https://github.com/mars-project/mars/blob/master/CONTRIBUTING.rst before opening an issue.
-->

**Describe the bug**
A clear and concise description of what the bug is.

```python
import numpy as np
from scipy.stats import ttest_1samp

fa_raw = np.array([16, 18, 16, 14, 12, 12])
fb_raw = np.array([16, 16, 16, 16, 16, 8])

print(ttest_1samp(fa_raw, fb_raw, alternative="less"))
```
works on scipy  1.9.3, fails on scipy 1.10.0

mars/tensor/stats/tests/test_stats_execution.py::test_t_test_execution

```python
mars/tensor/stats/tests/test_stats_execution.py:139 (test_t_test_execution)
a = array([16, 18, 16, 14, 12, 12]), popmean = array([16, 16, 16, 16, 16,  8])
axis = 0, nan_policy = 'propagate', alternative = 'less'

    @_axis_nan_policy_factory(pack_TtestResult, default_axis=0, n_samples=2,
                              result_to_tuple=unpack_TtestResult, n_outputs=6)
    def ttest_1samp(a, popmean, axis=0, nan_policy='propagate',
                    alternative="two-sided"):
        """Calculate the T-test for the mean of ONE group of scores.
    
        This is a test for the null hypothesis that the expected value
        (mean) of a sample of independent observations `a` is equal to the given
        population mean, `popmean`.
    
        Parameters
        ----------
        a : array_like
            Sample observation.
        popmean : float or array_like
            Expected value in null hypothesis. If array_like, then its length along
            `axis` must equal 1, and it must otherwise be broadcastable with `a`.
        axis : int or None, optional
            Axis along which to compute test; default is 0. If None, compute over
            the whole array `a`.
        nan_policy : {'propagate', 'raise', 'omit'}, optional
            Defines how to handle when input contains nan.
            The following options are available (default is 'propagate'):
    
              * 'propagate': returns nan
              * 'raise': throws an error
              * 'omit': performs the calculations ignoring nan values
    
        alternative : {'two-sided', 'less', 'greater'}, optional
            Defines the alternative hypothesis.
            The following options are available (default is 'two-sided'):
    
            * 'two-sided': the mean of the underlying distribution of the sample
              is different than the given population mean (`popmean`)
            * 'less': the mean of the underlying distribution of the sample is
              less than the given population mean (`popmean`)
            * 'greater': the mean of the underlying distribution of the sample is
              greater than the given population mean (`popmean`)
    
        Returns
        -------
        result : `~scipy.stats._result_classes.TtestResult`
            An object with the following attributes:
    
            statistic : float or array
                The t-statistic.
            pvalue : float or array
                The p-value associated with the given alternative.
            df : float or array
                The number of degrees of freedom used in calculation of the
                t-statistic; this is one less than the size of the sample
                (``a.shape[axis]``).
    
                .. versionadded:: 1.10.0
    
            The object also has the following method:
    
            confidence_interval(confidence_level=0.95)
                Computes a confidence interval around the population
                mean for the given confidence level.
                The confidence interval is returned in a ``namedtuple`` with
                fields `low` and `high`.
    
                .. versionadded:: 1.10.0
    
        Notes
        -----
        The statistic is calculated as ``(np.mean(a) - popmean)/se``, where
        ``se`` is the standard error. Therefore, the statistic will be positive
        when the sample mean is greater than the population mean and negative when
        the sample mean is less than the population mean.
    
        Examples
        --------
        Suppose we wish to test the null hypothesis that the mean of a population
        is equal to 0.5. We choose a confidence level of 99%; that is, we will
        reject the null hypothesis in favor of the alternative if the p-value is
        less than 0.01.
    
        When testing random variates from the standard uniform distribution, which
        has a mean of 0.5, we expect the data to be consistent with the null
        hypothesis most of the time.
    
        >>> import numpy as np
        >>> from scipy import stats
        >>> rng = np.random.default_rng()
        >>> rvs = stats.uniform.rvs(size=50, random_state=rng)
        >>> stats.ttest_1samp(rvs, popmean=0.5)
        TtestResult(statistic=2.456308468440, pvalue=0.017628209047638, df=49)
    
        As expected, the p-value of 0.017 is not below our threshold of 0.01, so
        we cannot reject the null hypothesis.
    
        When testing data from the standard *normal* distribution, which has a mean
        of 0, we would expect the null hypothesis to be rejected.
    
        >>> rvs = stats.norm.rvs(size=50, random_state=rng)
        >>> stats.ttest_1samp(rvs, popmean=0.5)
        TtestResult(statistic=-7.433605518875, pvalue=1.416760157221e-09, df=49)
    
        Indeed, the p-value is lower than our threshold of 0.01, so we reject the
        null hypothesis in favor of the default "two-sided" alternative: the mean
        of the population is *not* equal to 0.5.
    
        However, suppose we were to test the null hypothesis against the
        one-sided alternative that the mean of the population is *greater* than
        0.5. Since the mean of the standard normal is less than 0.5, we would not
        expect the null hypothesis to be rejected.
    
        >>> stats.ttest_1samp(rvs, popmean=0.5, alternative='greater')
        TtestResult(statistic=-7.433605518875, pvalue=0.99999999929, df=49)
    
        Unsurprisingly, with a p-value greater than our threshold, we would not
        reject the null hypothesis.
    
        Note that when working with a confidence level of 99%, a true null
        hypothesis will be rejected approximately 1% of the time.
    
        >>> rvs = stats.uniform.rvs(size=(100, 50), random_state=rng)
        >>> res = stats.ttest_1samp(rvs, popmean=0.5, axis=1)
        >>> np.sum(res.pvalue < 0.01)
        1
    
        Indeed, even though all 100 samples above were drawn from the standard
        uniform distribution, which *does* have a population mean of 0.5, we would
        mistakenly reject the null hypothesis for one of them.
    
        `ttest_1samp` can also compute a confidence interval around the population
        mean.
    
        >>> rvs = stats.norm.rvs(size=50, random_state=rng)
        >>> res = stats.ttest_1samp(rvs, popmean=0)
        >>> ci = res.confidence_interval(confidence_level=0.95)
        >>> ci
        ConfidenceInterval(low=-0.3193887540880017, high=0.2898583388980972)
    
        The bounds of the 95% confidence interval are the
        minimum and maximum values of the parameter `popmean` for which the
        p-value of the test would be 0.05.
    
        >>> res = stats.ttest_1samp(rvs, popmean=ci.low)
        >>> np.testing.assert_allclose(res.pvalue, 0.05)
        >>> res = stats.ttest_1samp(rvs, popmean=ci.high)
        >>> np.testing.assert_allclose(res.pvalue, 0.05)
    
        Under certain assumptions about the population from which a sample
        is drawn, the confidence interval with confidence level 95% is expected
        to contain the true population mean in 95% of sample replications.
    
        >>> rvs = stats.norm.rvs(size=(50, 1000), loc=1, random_state=rng)
        >>> res = stats.ttest_1samp(rvs, popmean=0)
        >>> ci = res.confidence_interval()
        >>> contains_pop_mean = (ci.low < 1) & (ci.high > 1)
        >>> contains_pop_mean.sum()
        953
    
        """
        a, axis = _chk_asarray(a, axis)
    
        n = a.shape[axis]
        df = n - 1
    
        mean = np.mean(a, axis)
        try:
>           popmean = np.squeeze(popmean, axis=axis)

../../.pyenv/versions/3.8.13/lib/python3.8/site-packages/scipy/stats/_stats_py.py:6270: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (array([16, 16, 16, 16, 16,  8]),), kwargs = {'axis': 0}
relevant_args = (array([16, 16, 16, 16, 16,  8]),)

>   ???

<__array_function__ internals>:200: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

a = array([16, 16, 16, 16, 16,  8]), axis = 0

    @array_function_dispatch(_squeeze_dispatcher)
    def squeeze(a, axis=None):
        """
        Remove axes of length one from `a`.
    
        Parameters
        ----------
        a : array_like
            Input data.
        axis : None or int or tuple of ints, optional
            .. versionadded:: 1.7.0
    
            Selects a subset of the entries of length one in the
            shape. If an axis is selected with shape entry greater than
            one, an error is raised.
    
        Returns
        -------
        squeezed : ndarray
            The input array, but with all or a subset of the
            dimensions of length 1 removed. This is always `a` itself
            or a view into `a`. Note that if all axes are squeezed,
            the result is a 0d array and not a scalar.
    
        Raises
        ------
        ValueError
            If `axis` is not None, and an axis being squeezed is not of length 1
    
        See Also
        --------
        expand_dims : The inverse operation, adding entries of length one
        reshape : Insert, remove, and combine dimensions, and resize existing ones
    
        Examples
        --------
        >>> x = np.array([[[0], [1], [2]]])
        >>> x.shape
        (1, 3, 1)
        >>> np.squeeze(x).shape
        (3,)
        >>> np.squeeze(x, axis=0).shape
        (3, 1)
        >>> np.squeeze(x, axis=1).shape
        Traceback (most recent call last):
        ...
        ValueError: cannot select an axis to squeeze out which has size not equal to one
        >>> np.squeeze(x, axis=2).shape
        (1, 3)
        >>> x = np.array([[1234]])
        >>> x.shape
        (1, 1)
        >>> np.squeeze(x)
        array(1234)  # 0d array
        >>> np.squeeze(x).shape
        ()
        >>> np.squeeze(x)[()]
        1234
    
        """
        try:
            squeeze = a.squeeze
        except AttributeError:
            return _wrapit(a, 'squeeze', axis=axis)
        if axis is None:
            return squeeze()
        else:
>           return squeeze(axis=axis)
E           ValueError: cannot select an axis to squeeze out which has size not equal to one

../../.pyenv/versions/3.8.13/lib/python3.8/site-packages/numpy/core/fromnumeric.py:1571: ValueError

The above exception was the direct cause of the following exception:

setup = <mars.deploy.oscar.session.SyncSession object at 0x29b9e0a90>

    def test_t_test_execution(setup):
        if parse_version(scipy.__version__) >= parse_version("1.6.0"):
            alternatives = ["less", "greater", "two-sided"]
    
            mt_from_stats = (
                lambda a, b, alternative=None, equal_var=True: ttest_ind_from_stats(
                    a.mean(),
                    a.std(),
                    a.shape[0],
                    b.mean(),
                    b.std(),
                    b.shape[0],
                    alternative=alternative,
                    equal_var=equal_var,
                )
            )
            sp_from_stats = (
                lambda a, b, alternative=None, equal_var=True: sp_ttest_ind_from_stats(
                    a.mean(),
                    a.std(),
                    a.shape[0],
                    b.mean(),
                    b.std(),
                    b.shape[0],
                    alternative=alternative,
                    equal_var=equal_var,
                )
            )
        else:
            alternatives = ["two-sided"]
    
            mt_from_stats = lambda a, b, equal_var=True: ttest_ind_from_stats(
                a.mean(),
                a.std(),
                a.shape[0],
                b.mean(),
                b.std(),
                b.shape[0],
                equal_var=equal_var,
            )
            sp_from_stats = lambda a, b, equal_var=True: sp_ttest_ind_from_stats(
                a.mean(),
                a.std(),
                a.shape[0],
                b.mean(),
                b.std(),
                b.shape[0],
                equal_var=equal_var,
            )
    
        funcs = [
            (ttest_rel, sp_ttest_rel),
            (
                functools.partial(ttest_ind, equal_var=True),
                functools.partial(sp_ttest_ind, equal_var=True),
            ),
            (
                functools.partial(ttest_ind, equal_var=False),
                functools.partial(sp_ttest_ind, equal_var=False),
            ),
            (
                functools.partial(mt_from_stats, equal_var=True),
                functools.partial(sp_from_stats, equal_var=True),
            ),
            (
                functools.partial(mt_from_stats, equal_var=False),
                functools.partial(sp_from_stats, equal_var=False),
            ),
            (ttest_1samp, sp_ttest_1samp),
        ]
    
        fa_raw = np.array([16, 18, 16, 14, 12, 12])
        fb_raw = np.array([16, 16, 16, 16, 16, 8])
    
        fa = tensor(fa_raw, chunk_size=4)
        fb = tensor(fb_raw, chunk_size=4)
    
        for mt_func, sp_func in funcs:
            if parse_version(scipy.__version__) >= parse_version("1.6.0"):
                with pytest.raises(ValueError):
                    mt_func(fa, fb, alternative="illegal-alternative")
    
            for alt in alternatives:
                if parse_version(scipy.__version__) >= parse_version("1.6.0"):
                    r = mt_func(fa, fb, alternative=alt)
                else:
                    r = mt_func(fa, fb)
                result = r.execute().fetch()
    
                if parse_version(scipy.__version__) >= parse_version("1.6.0"):
>                   expected = sp_func(fa_raw, fb_raw, alternative=alt)

mars/tensor/stats/tests/test_stats_execution.py:230: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../.pyenv/versions/3.8.13/lib/python3.8/site-packages/scipy/stats/_axis_nan_policy.py:502: in axis_nan_policy_wrapper
    res = hypotest_fun_out(*samples, **kwds)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

a = array([16, 18, 16, 14, 12, 12]), popmean = array([16, 16, 16, 16, 16,  8])
axis = 0, nan_policy = 'propagate', alternative = 'less'

    @_axis_nan_policy_factory(pack_TtestResult, default_axis=0, n_samples=2,
                              result_to_tuple=unpack_TtestResult, n_outputs=6)
    def ttest_1samp(a, popmean, axis=0, nan_policy='propagate',
                    alternative="two-sided"):
        """Calculate the T-test for the mean of ONE group of scores.
    
        This is a test for the null hypothesis that the expected value
        (mean) of a sample of independent observations `a` is equal to the given
        population mean, `popmean`.
    
        Parameters
        ----------
        a : array_like
            Sample observation.
        popmean : float or array_like
            Expected value in null hypothesis. If array_like, then its length along
            `axis` must equal 1, and it must otherwise be broadcastable with `a`.
        axis : int or None, optional
            Axis along which to compute test; default is 0. If None, compute over
            the whole array `a`.
        nan_policy : {'propagate', 'raise', 'omit'}, optional
            Defines how to handle when input contains nan.
            The following options are available (default is 'propagate'):
    
              * 'propagate': returns nan
              * 'raise': throws an error
              * 'omit': performs the calculations ignoring nan values
    
        alternative : {'two-sided', 'less', 'greater'}, optional
            Defines the alternative hypothesis.
            The following options are available (default is 'two-sided'):
    
            * 'two-sided': the mean of the underlying distribution of the sample
              is different than the given population mean (`popmean`)
            * 'less': the mean of the underlying distribution of the sample is
              less than the given population mean (`popmean`)
            * 'greater': the mean of the underlying distribution of the sample is
              greater than the given population mean (`popmean`)
    
        Returns
        -------
        result : `~scipy.stats._result_classes.TtestResult`
            An object with the following attributes:
    
            statistic : float or array
                The t-statistic.
            pvalue : float or array
                The p-value associated with the given alternative.
            df : float or array
                The number of degrees of freedom used in calculation of the
                t-statistic; this is one less than the size of the sample
                (``a.shape[axis]``).
    
                .. versionadded:: 1.10.0
    
            The object also has the following method:
    
            confidence_interval(confidence_level=0.95)
                Computes a confidence interval around the population
                mean for the given confidence level.
                The confidence interval is returned in a ``namedtuple`` with
                fields `low` and `high`.
    
                .. versionadded:: 1.10.0
    
        Notes
        -----
        The statistic is calculated as ``(np.mean(a) - popmean)/se``, where
        ``se`` is the standard error. Therefore, the statistic will be positive
        when the sample mean is greater than the population mean and negative when
        the sample mean is less than the population mean.
    
        Examples
        --------
        Suppose we wish to test the null hypothesis that the mean of a population
        is equal to 0.5. We choose a confidence level of 99%; that is, we will
        reject the null hypothesis in favor of the alternative if the p-value is
        less than 0.01.
    
        When testing random variates from the standard uniform distribution, which
        has a mean of 0.5, we expect the data to be consistent with the null
        hypothesis most of the time.
    
        >>> import numpy as np
        >>> from scipy import stats
        >>> rng = np.random.default_rng()
        >>> rvs = stats.uniform.rvs(size=50, random_state=rng)
        >>> stats.ttest_1samp(rvs, popmean=0.5)
        TtestResult(statistic=2.456308468440, pvalue=0.017628209047638, df=49)
    
        As expected, the p-value of 0.017 is not below our threshold of 0.01, so
        we cannot reject the null hypothesis.
    
        When testing data from the standard *normal* distribution, which has a mean
        of 0, we would expect the null hypothesis to be rejected.
    
        >>> rvs = stats.norm.rvs(size=50, random_state=rng)
        >>> stats.ttest_1samp(rvs, popmean=0.5)
        TtestResult(statistic=-7.433605518875, pvalue=1.416760157221e-09, df=49)
    
        Indeed, the p-value is lower than our threshold of 0.01, so we reject the
        null hypothesis in favor of the default "two-sided" alternative: the mean
        of the population is *not* equal to 0.5.
    
        However, suppose we were to test the null hypothesis against the
        one-sided alternative that the mean of the population is *greater* than
        0.5. Since the mean of the standard normal is less than 0.5, we would not
        expect the null hypothesis to be rejected.
    
        >>> stats.ttest_1samp(rvs, popmean=0.5, alternative='greater')
        TtestResult(statistic=-7.433605518875, pvalue=0.99999999929, df=49)
    
        Unsurprisingly, with a p-value greater than our threshold, we would not
        reject the null hypothesis.
    
        Note that when working with a confidence level of 99%, a true null
        hypothesis will be rejected approximately 1% of the time.
    
        >>> rvs = stats.uniform.rvs(size=(100, 50), random_state=rng)
        >>> res = stats.ttest_1samp(rvs, popmean=0.5, axis=1)
        >>> np.sum(res.pvalue < 0.01)
        1
    
        Indeed, even though all 100 samples above were drawn from the standard
        uniform distribution, which *does* have a population mean of 0.5, we would
        mistakenly reject the null hypothesis for one of them.
    
        `ttest_1samp` can also compute a confidence interval around the population
        mean.
    
        >>> rvs = stats.norm.rvs(size=50, random_state=rng)
        >>> res = stats.ttest_1samp(rvs, popmean=0)
        >>> ci = res.confidence_interval(confidence_level=0.95)
        >>> ci
        ConfidenceInterval(low=-0.3193887540880017, high=0.2898583388980972)
    
        The bounds of the 95% confidence interval are the
        minimum and maximum values of the parameter `popmean` for which the
        p-value of the test would be 0.05.
    
        >>> res = stats.ttest_1samp(rvs, popmean=ci.low)
        >>> np.testing.assert_allclose(res.pvalue, 0.05)
        >>> res = stats.ttest_1samp(rvs, popmean=ci.high)
        >>> np.testing.assert_allclose(res.pvalue, 0.05)
    
        Under certain assumptions about the population from which a sample
        is drawn, the confidence interval with confidence level 95% is expected
        to contain the true population mean in 95% of sample replications.
    
        >>> rvs = stats.norm.rvs(size=(50, 1000), loc=1, random_state=rng)
        >>> res = stats.ttest_1samp(rvs, popmean=0)
        >>> ci = res.confidence_interval()
        >>> contains_pop_mean = (ci.low < 1) & (ci.high > 1)
        >>> contains_pop_mean.sum()
        953
    
        """
        a, axis = _chk_asarray(a, axis)
    
        n = a.shape[axis]
        df = n - 1
    
        mean = np.mean(a, axis)
        try:
            popmean = np.squeeze(popmean, axis=axis)
        except ValueError as e:
>           raise ValueError("`popmean.shape[axis]` must equal 1.") from e
E           ValueError: `popmean.shape[axis]` must equal 1.

../../.pyenv/versions/3.8.13/lib/python3.8/site-packages/scipy/stats/_stats_py.py:6272: ValueError
```

**To Reproduce**
To help us reproducing this bug, please provide information below:
1. Your Python version
2. The version of Mars you use
3. Versions of crucial packages, such as numpy, scipy and pandas
4. Full stack of the error.
5. Minimized code to reproduce the error.

**Expected behavior**
A clear and concise description of what you expected to happen.

**Additional context**
Add any other context about the problem here.



Discussion:

Resolving patch:
diff --git a/mars/dataframe/datasource/date_range.py b/mars/dataframe/datasource/date_range.py
--- a/mars/dataframe/datasource/date_range.py
+++ b/mars/dataframe/datasource/date_range.py
@@ -17,6 +17,7 @@
 
 import numpy as np
 import pandas as pd
+from pandas import Timestamp, NaT
 from pandas.tseries.frequencies import to_offset
 from pandas.tseries.offsets import Tick
 from pandas._libs.tslibs import timezones
@@ -51,6 +52,72 @@ def normalize_date(dt):  # from pandas/_libs/tslibs/conversion.pyx
 _date_range_use_inclusive = pd_release_version[:2] >= (1, 4)
 
 
+# adapted from pandas.core.arrays.datetimes.generate_range
+def generate_range_count(
+    start=None, end=None, periods=None, offset=None
+):  # pragma: no cover
+    offset = to_offset(offset)
+
+    start = Timestamp(start)
+    start = start if start is not NaT else None
+    end = Timestamp(end)
+    end = end if end is not NaT else None
+
+    if start and not offset.is_on_offset(start):
+        start = offset.rollforward(start)
+
+    elif end and not offset.is_on_offset(end):
+        end = offset.rollback(end)
+
+    if periods is None and end < start and offset.n >= 0:
+        end = None
+        periods = 0
+
+    if end is None:
+        end = start + (periods - 1) * offset
+
+    if start is None:
+        start = end - (periods - 1) * offset
+
+    cur = start
+    count = 0
+    if offset.n >= 0:
+        while cur <= end:
+            count += 1
+
+            if cur == end:
+                # GH#24252 avoid overflows by not performing the addition
+                # in offset.apply unless we have to
+                break
+
+            # faster than cur + offset
+            try:
+                next_date = offset._apply(cur)
+            except AttributeError:
+                next_date = cur + offset
+            if next_date <= cur:
+                raise ValueError(f"Offset {offset} did not increment date")
+            cur = next_date
+    else:
+        while cur >= end:
+            count += 1
+
+            if cur == end:
+                # GH#24252 avoid overflows by not performing the addition
+                # in offset.apply unless we have to
+                break
+
+            # faster than cur + offset
+            try:
+                next_date = offset._apply(cur)
+            except AttributeError:
+                next_date = cur + offset
+            if next_date >= cur:
+                raise ValueError(f"Offset {offset} did not decrement date")
+            cur = next_date
+    return count
+
+
 class DataFrameDateRange(DataFrameOperand, DataFrameOperandMixin):
     _op_type_ = OperandDef.DATE_RANGE
 
@@ -511,7 +578,7 @@ def date_range(
             inclusive = "both"
     else:
         if periods is None:
-            periods = size = int((end - start) / freq + 1)
+            periods = size = generate_range_count(start, end, periods, freq)
         else:
             size = periods
         if inclusive in ("left", "right"):


Test patch:
diff --git a/mars/dataframe/datasource/tests/test_datasource_execution.py b/mars/dataframe/datasource/tests/test_datasource_execution.py
--- a/mars/dataframe/datasource/tests/test_datasource_execution.py
+++ b/mars/dataframe/datasource/tests/test_datasource_execution.py
@@ -1007,6 +1007,11 @@ def test_date_range_execution(setup):
     expected = pd.date_range(start="1/1/2018", periods=5, freq="M")
     pd.testing.assert_index_equal(result, expected)
 
+    dr = md.date_range(start="2018/01/01", end="2018/07/01", freq="M")
+    result = dr.execute().fetch()
+    expected = pd.date_range(start="2018/01/01", end="2018/07/01", freq="M")
+    pd.testing.assert_index_equal(result, expected)
+
 
 parquet_engines = ["auto"]
 if pa is not None:
diff --git a/mars/tensor/stats/tests/test_stats_execution.py b/mars/tensor/stats/tests/test_stats_execution.py
--- a/mars/tensor/stats/tests/test_stats_execution.py
+++ b/mars/tensor/stats/tests/test_stats_execution.py
@@ -205,7 +205,6 @@ def test_t_test_execution(setup):
             functools.partial(mt_from_stats, equal_var=False),
             functools.partial(sp_from_stats, equal_var=False),
         ),
-        (ttest_1samp, sp_ttest_1samp),
     ]
 
     fa_raw = np.array([16, 18, 16, 14, 12, 12])
@@ -233,6 +232,23 @@ def test_t_test_execution(setup):
             np.testing.assert_almost_equal(expected[0], result[0])
             np.testing.assert_almost_equal(expected[1], result[1])
 
+    # second param size must be 1 for ttest_1samp
+    fb_raw = np.array([16])
+    fb = tensor(fb_raw)
+    for alt in alternatives:
+        if parse_version(scipy.__version__) >= parse_version("1.6.0"):
+            r = ttest_1samp(fa, fb, alternative=alt)
+        else:
+            r = ttest_1samp(fa, fb)
+        result = r.execute().fetch()
+
+        if parse_version(scipy.__version__) >= parse_version("1.6.0"):
+            expected = sp_ttest_1samp(fa_raw, fb_raw, alternative=alt)
+        else:
+            expected = sp_ttest_1samp(fa_raw, fb_raw)
+        np.testing.assert_almost_equal(expected[0], result[0])
+        np.testing.assert_almost_equal(expected[1], result[1])
+
 
 @pytest.mark.parametrize("chunk_size", [5, 15])
 @pytest.mark.parametrize(