Repository: modin-project/modin

Issue:
`DataFrame.apply` is halting during interaction with scipy
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Ubuntu 20.04
- **Modin version**: 01a5de21c8fb3f6da0f9f887f6a659b7037136a0
- **Python version**: 3.8.12
- **Code we can use to reproduce**:
```
import modin.pandas as pd
import numpy as np
from scipy.io.wavfile import write, read

test_filename = "test.wav"
samplerate = 44100
fs = 100

# generate 100Hz sine wave
t = np.linspace(0., 1., samplerate)
amplitude = np.iinfo(np.int16).max
data = amplitude * np.sin(2. * np.pi * fs * t)
write(test_filename, samplerate, data.astype(np.int16))

data = pd.DataFrame({"filepath": [test_filename]})
data['audio'] = data['filepath'].apply(read)
```

<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
<!-- Describe the problem clearly here. -->
Modin `DataFrame.apply` is halting during applying `scipy` custom function, while pandas works fine.
`scipy` 1.7.1 from conda-forge

### Source code / logs
<!-- Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. Try to provide a reproducible test case that is the bare minimum necessary to generate the problem. -->



Discussion:
This is caused by `modin.Series.apply()` doing this:
https://github.com/modin-project/modin/blob/a256d33b919fc5deb86be0c4382dd017052053b9/modin/pandas/series.py#L682-L696

and `scipy.io.wavfile.read()` expecting a filename as the argument (with it coming from a Series element).

So when we call it to determine the return type, first one we got an exception (silently ignored): `FileNotFoundError: [Errno 2] No such file or directory: ''` (when `read` is called on a series with a single empty string as an item), and then `read` is told to read from integer with value `0` which is actually `stdin`, so it tries to read from the input which we never feed anything to, so it appears hang.

Yes, this code is trying to detect the API-layer output type (`Series`, `Dataframe`).
Simplified reproducer:
```python
import modin.pandas as pd
import pandas


def func(val):
    if not isinstance(val, tuple):
        raise BaseException('Urgh...')
    return val

df1 = pandas.DataFrame({'col': [(0, 1)]})
col1 = df1['col'].apply(func)

df2 = pd.DataFrame({'col': [(0, 1)]})
col2 = df2['col'].apply(func)

assert (col1 == col2._to_pandas()).all()
```
<details><summary>Current output</summary>

```
UserWarning: Distributing <class 'dict'> object. This may take some time.
Traceback (most recent call last):
  File "bug-3563.py", line 14, in <module>
    col2 = df2['col'].apply(func)
  File "...\modin\pandas\series.py", line 684, in apply
    getattr(pandas.Series("", index=self.index[:1]), apply_func)(
  File "C:\Miniconda3\envs\modin\lib\site-packages\pandas\core\series.py", line 4357, in apply
    return SeriesApply(self, func, convert_dtype, args, kwargs).apply()
  File "C:\Miniconda3\envs\modin\lib\site-packages\pandas\core\apply.py", line 1043, in apply
    return self.apply_standard()
  File "C:\Miniconda3\envs\modin\lib\site-packages\pandas\core\apply.py", line 1099, in apply_standard
    mapped = lib.map_infer(
  File "pandas\_libs\lib.pyx", line 2859, in pandas._libs.lib.map_infer
  File "bug-3563.py", line 7, in func
    raise BaseException('Urgh...')
BaseException: Urgh...
```
</details>

Here I'm trying to simulate a callable which does not accept neither strings nor integers in a peculiar enough way to overcome the `except Exception` block (to simulate the hang).
@devin-petersohn what was the reason to create the dummy `Series` in the first place? the most obvious fix here would be to create a 1-element `Series` like this: `pandas.Series(self.values[:1], index=self.index[:1])`, and the only drawback I see here compared to current solution is that it potentially would run slower (if the user-defined `func` takes more time on a real data piece than on simulated).

I don't really see the way around that... and the way to overcome the drawback would be to store the results of applying to a single element and then concatenate, but I believe it won't give any real speedup here.

Resolving patch:
diff --git a/modin/pandas/series.py b/modin/pandas/series.py
--- a/modin/pandas/series.py
+++ b/modin/pandas/series.py
@@ -681,19 +681,12 @@ def apply(
         # the return type.
         try:
             return_type = type(
-                getattr(pandas.Series("", index=self.index[:1]), apply_func)(
-                    func, *args, **kwargs
-                )
+                getattr(
+                    pandas.Series(self[:1].values, index=self.index[:1]), apply_func
+                )(func, *args, **kwargs)
             ).__name__
         except Exception:
-            try:
-                return_type = type(
-                    getattr(pandas.Series(0, index=self.index[:1]), apply_func)(
-                        func, *args, **kwargs
-                    )
-                ).__name__
-            except Exception:
-                return_type = type(self).__name__
+            return_type = type(self).__name__
         if (
             isinstance(func, str)
             or is_list_like(func)


Test patch:
diff --git a/modin/pandas/test/test_series.py b/modin/pandas/test/test_series.py
--- a/modin/pandas/test/test_series.py
+++ b/modin/pandas/test/test_series.py
@@ -4536,3 +4536,18 @@ def test_cat_as_unordered(data, inplace):
     modin_result = modin_series.cat.as_unordered(inplace=inplace)
     df_equals(modin_series, pandas_series)
     df_equals(modin_result, pandas_result)
+
+
+def test_peculiar_callback():
+    def func(val):
+        if not isinstance(val, tuple):
+            raise BaseException("Urgh...")
+        return val
+
+    pandas_df = pandas.DataFrame({"col": [(0, 1)]})
+    pandas_series = pandas_df["col"].apply(func)
+
+    modin_df = pd.DataFrame({"col": [(0, 1)]})
+    modin_series = modin_df["col"].apply(func)
+
+    df_equals(modin_series, pandas_series)