Repository: scipy/scipy

Issue:
BUG: Scalefactors for `signal.csd` with `average=='median'` are different depending on whether `x==y` or not
### Describe your issue.

Hi,

I found that `_median_bias` in `signal.csd` is differently applied depending on whether the result cross-spectrum is complex or not.

Around https://github.com/scipy/scipy/blob/v1.8.0/scipy/signal/_spectral_py.py#L594 :
When `Pxy` is complex, `_median_bias` is called with `shape[-1]` after computing median, i.e. the argument is the number of frequency-bins.
but in the else-block, `_median_bias` is directly called with `Pxy.shape[-1]` that is the number of time-frames.

I believe the latter behavior is correct, but not 100% sure.


### Reproducing Code Example

```python
Pxy = np.ones((3, 15), dtype=np.complex64)
if np.iscomplexobj(Pxy):
    Pxy = (np.median(np.real(Pxy), axis=-1)
            + 1j * np.median(np.imag(Pxy), axis=-1))
    Pxy /= _median_bias(Pxy.shape[-1])
else:
    Pxy = np.median(Pxy, axis=-1) / _median_bias(Pxy.shape[-1])
print(Pxy)
# This prints "array([1.2+0.j, 1.2+0.j, 1.2+0.j], dtype=complex64)"

Pxy = np.ones((3, 15), dtype=np.float32)
if np.iscomplexobj(Pxy):
    Pxy = (np.median(np.real(Pxy), axis=-1)
            + 1j * np.median(np.imag(Pxy), axis=-1))
    Pxy /= _median_bias(Pxy.shape[-1])
else:
    Pxy = np.median(Pxy, axis=-1) / _median_bias(Pxy.shape[-1])
print(Pxy)

# This prints "array([1.3786033, 1.3786033, 1.3786033], dtype=float32)"
# and this should be matched with the above output (except for dtype).
```

### Error message

```shell
N/A
```


### SciPy/NumPy/Python version information

1.8.0 1.22.2 sys.version_info(major=3, minor=9, micro=9, releaselevel='final', serial=0)



Discussion:
Ah, I'm sorry there was a mistake in version information.  So, corrected.

The following snippet is an example how the issue can affect in actual usage:
```python
>>> import numpy as np
>>> import scipy.signal
>>> x = np.random.randn(50)
>>> csd(x, x, nperseg=5, average='median', return_onesided=False)[1]
array([0.51791366, 0.97265675, 1.59664212, 1.59664212, 0.97265675])
>>> csd(x, x.copy(), nperseg=5, average='median', return_onesided=False)[1]
array([0.47959148+0.j, 0.90068658+0.j, 1.47850117+0.j, 1.47850117+0.j,
       0.90068658+0.j])
```
The two calls above should return the same results.



Resolving patch:
diff --git a/scipy/signal/_spectral_py.py b/scipy/signal/_spectral_py.py
--- a/scipy/signal/_spectral_py.py
+++ b/scipy/signal/_spectral_py.py
@@ -586,12 +586,13 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None,
         if Pxy.shape[-1] > 1:
             if average == 'median':
                 # np.median must be passed real arrays for the desired result
+                bias = _median_bias(Pxy.shape[-1])
                 if np.iscomplexobj(Pxy):
                     Pxy = (np.median(np.real(Pxy), axis=-1)
                            + 1j * np.median(np.imag(Pxy), axis=-1))
-                    Pxy /= _median_bias(Pxy.shape[-1])
                 else:
-                    Pxy = np.median(Pxy, axis=-1) / _median_bias(Pxy.shape[-1])
+                    Pxy = np.median(Pxy, axis=-1)
+                Pxy /= bias
             elif average == 'mean':
                 Pxy = Pxy.mean(axis=-1)
             else:


Test patch:
diff --git a/scipy/signal/tests/test_spectral.py b/scipy/signal/tests/test_spectral.py
--- a/scipy/signal/tests/test_spectral.py
+++ b/scipy/signal/tests/test_spectral.py
@@ -848,6 +848,23 @@ def test_padded_freqs(self):
         assert_allclose(f, fodd)
         assert_allclose(f, feven)
 
+    def test_copied_data(self):
+        x = np.random.randn(64)
+        y = x.copy()
+
+        _, p_same = csd(x, x, nperseg=8, average='mean',
+                        return_onesided=False)
+        _, p_copied = csd(x, y, nperseg=8, average='mean',
+                          return_onesided=False)
+        assert_allclose(p_same, p_copied)
+
+        _, p_same = csd(x, x, nperseg=8, average='median',
+                        return_onesided=False)
+        _, p_copied = csd(x, y, nperseg=8, average='median',
+                          return_onesided=False)
+        assert_allclose(p_same, p_copied)
+
+
 class TestCoherence:
     def test_identical_input(self):
         x = np.random.randn(20)