Repository: vaexio/vaex

Issue:
[BUG-REPORT] DataFrame.apply crashes when the column name has spaces in it

**Description**
When trying to execute the following code with data

```
import vaex

vaex_df = vaex.from_dict(
    {
        "g1 space": ["a", "a", "b", "b", "a"],
        "g2": ["c", "d", "c", "d", "c"],
        "g3 space": ["c", None, "c", "d", "c"],
    }
)


def unique_or_other(g1, g2, g3):
    if g3 == "c":
        return "OTHER"
    else:
        return g1


vaex_df["g4"] = vaex_df.apply(unique_or_other, arguments=["g1 space", "g2", "g3 space"])


print(vaex_df)
```

Results in the following error


```
During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/faizank/workspace/plotly/dashboard-engine/sample-runnable-app/venv/lib/python3.6/site-packages/vaex/scopes.py", line 106, in evaluate
    result = self[expression]
  File "/Users/faizank/workspace/plotly/dashboard-engine/sample-runnable-app/venv/lib/python3.6/site-packages/vaex/scopes.py", line 166, in __getitem__
    raise KeyError("Unknown variables or column: %r" % (variable,))
KeyError: "Unknown variables or column: 'lambda_function(g1 space, g2, g3 space)'"

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/faizank/workspace/plotly/dashboard-engine/sample-runnable-app/venv/lib/python3.6/site-packages/vaex/dataframe.py", line 3820, in table_part
    values[name] = df.evaluate(name)
  File "/Users/faizank/workspace/plotly/dashboard-engine/sample-runnable-app/venv/lib/python3.6/site-packages/vaex/dataframe.py", line 2850, in evaluate
    return self._evaluate_implementation(expression, i1=i1, i2=i2, out=out, selection=selection, filtered=filtered, array_type=array_type, parallel=parallel, chunk_size=chunk_size)
  File "/Users/faizank/workspace/plotly/dashboard-engine/sample-runnable-app/venv/lib/python3.6/site-packages/vaex/dataframe.py", line 6022, in _evaluate_implementation
    dtypes[expression] = dtype = df.data_type(expression).internal
  File "/Users/faizank/workspace/plotly/dashboard-engine/sample-runnable-app/venv/lib/python3.6/site-packages/vaex/dataframe.py", line 2004, in data_type
    data = self.evaluate(expression, 0, 1, filtered=True, array_type=array_type, parallel=False)
  File "/Users/faizank/workspace/plotly/dashboard-engine/sample-runnable-app/venv/lib/python3.6/site-packages/vaex/dataframe.py", line 2850, in evaluate
    return self._evaluate_implementation(expression, i1=i1, i2=i2, out=out, selection=selection, filtered=filtered, array_type=array_type, parallel=parallel, chunk_size=chunk_size)
  File "/Users/faizank/workspace/plotly/dashboard-engine/sample-runnable-app/venv/lib/python3.6/site-packages/vaex/dataframe.py", line 6110, in _evaluate_implementation
    value = scope.evaluate(expression)
  File "/Users/faizank/workspace/plotly/dashboard-engine/sample-runnable-app/venv/lib/python3.6/site-packages/vaex/scopes.py", line 106, in evaluate
    result = self[expression]
  File "/Users/faizank/workspace/plotly/dashboard-engine/sample-runnable-app/venv/lib/python3.6/site-packages/vaex/scopes.py", line 156, in __getitem__
    values = self.evaluate(expression)
  File "/Users/faizank/workspace/plotly/dashboard-engine/sample-runnable-app/venv/lib/python3.6/site-packages/vaex/scopes.py", line 112, in evaluate
    result = eval(expression, expression_namespace, self)
  File "<string>", line 1
    lambda_function(g1 space, g2, g3 space)
```


**Software information**
 - Vaex version {'vaex': '4.5.0', 'vaex-core': '4.5.1', 'vaex-viz': '0.5.0', 'vaex-hdf5': '0.10.0', 'vaex-server': '0.6.1', 'vaex-astro': '0.9.0', 'vaex-jupyter': '0.6.0', 'vaex-ml': '0.14.0'}
 - Vaex was installed via: pip
 - OS: BigSure 11.2.2




Discussion:
What about 
```
df.apply(unique_or_other, arguments=[df["g1 space"], "g2", df["g3 space"]])
```
Does that work?
It worked :D
Wait, it works for this data test but for some reason it does not work with a different dataset. Trying to reproduce that error. 
Ok, sorry I was using a different environment for the different dataset. 

So the above solution works with Vaex==4.4.0, python==3.6.8. 

Produces the same error with Vaex==4.5.1, python==3.8

Resolving patch:
diff --git a/packages/vaex-core/vaex/dataframe.py b/packages/vaex-core/vaex/dataframe.py
--- a/packages/vaex-core/vaex/dataframe.py
+++ b/packages/vaex-core/vaex/dataframe.py
@@ -2025,6 +2025,8 @@ def data_type(self, expression, array_type=None, internal=False, axis=0):
         :param str array_type: 'numpy', 'arrow' or None, to indicate if the data type should be converted
         :param int axis: If a nested type (like list), it will return the value_type of the nested type, axis levels deep.
         """
+        if isinstance(expression, vaex.expression.Expression):
+            expression = expression._label
         expression = _ensure_string_from_expression(expression)
         data_type = None
         if expression in self.variables:
diff --git a/packages/vaex-core/vaex/utils.py b/packages/vaex-core/vaex/utils.py
--- a/packages/vaex-core/vaex/utils.py
+++ b/packages/vaex-core/vaex/utils.py
@@ -770,7 +770,7 @@ def _ensure_string_from_expression(expression):
     elif isinstance(expression, six.string_types):
         return expression
     elif isinstance(expression, vaex.expression.Expression):
-        return expression._label
+        return expression.expression
     else:
         raise ValueError('%r is not of string or Expression type, but %r' % (expression, type(expression)))
 


Test patch:
diff --git a/tests/apply_test.py b/tests/apply_test.py
--- a/tests/apply_test.py
+++ b/tests/apply_test.py
@@ -18,3 +18,9 @@ def test_apply_select():
     df.state_get()
     df.select('x_ind')
     assert 2 == df.selected_length()
+
+def test_apply_with_invalid_identifier():
+    df = vaex.from_dict({"#": [1], "with space": [2]})
+    def add(a, b):
+        return a + b
+    assert df.apply(add, arguments=[df["#"], df["with space"]]).tolist() == [3]
diff --git a/tests/dtypes_test.py b/tests/dtypes_test.py
--- a/tests/dtypes_test.py
+++ b/tests/dtypes_test.py
@@ -1,5 +1,7 @@
 from common import *
 from vaex.datatype import DataType
+from unittest.mock import MagicMock
+
 
 def test_dtype_basics(df):
     df['new_virtual_column'] = df.x + 1
@@ -81,3 +83,10 @@ def test_dtype_nested():
     assert df.s.data_type(axis=-3) == pa.list_(pa.list_(pa.string()))
     assert df.s.data_type(axis=-2) == pa.list_(pa.string())
     assert df.s.data_type(axis=-1) == pa.string()
+
+
+def test_dtype_no_eval():
+    df = vaex.from_dict({"#": [1.1], "with space": ['should work']})
+    df._evaluate_implementation = MagicMock()
+    assert df.data_type(df['#']) == float
+    assert df.data_type(df['with space']) == str