We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG-REPORT] Multiple selection filters broken in latest version
**Description**
I think I've found an issue with the new internal representation of arrays as arrow arrays.  This is in 4.0.0.dev0, so you might have a plan to fix it by the next proper release, but just flagging it anyway as it seems relatively major.

With a simple test table `data`:

```
import pandas as pd
import numpy as np
import vaex

data = pd.DataFrame({
  "A":np.array([1,2,3,4]),
  "B":["A","B","C","D"],
  "C":[np.datetime64('2019-10-07 09:00:00'),np.datetime64('2019-10-07 10:00:00'),np.datetime64('2019-10-07 11:00:00'),np.datetime64('2019-10-07 12:00:00')]
  })

vaex.from_pandas(data).export_arrow('arrow_sample.arrow')
data = vaex.open('arrow_sample.arrow')
```

Normal single clause filters work as you'd expect:

```
>>> data[(data['B'] == 'D')]
  #    A  B    C
  0    4  'D'  Timestamp('2019-10-07 12:00:00')
>>> data[(data['A'] > 2)]
  #    A  B    C
  0    3  'C'  Timestamp('2019-10-07 11:00:00')
  1    4  'D'  Timestamp('2019-10-07 12:00:00')
```

But when you combine multiple clauses the answer is wrong (this should return a single row):

```
>>> data[(data['B'] == 'D') & (data['A'] > 2)]
  #    A  B    C
  0    1  'A'  Timestamp('2019-10-07 09:00:00')
  1    2  'B'  Timestamp('2019-10-07 10:00:00')
  2    3  'C'  Timestamp('2019-10-07 11:00:00')
  3    4  'D'  Timestamp('2019-10-07 12:00:00')
```

I first noticed this using a much larger table, and in that case it seemed to cause a memory leak (process memory continually rose after the query)  I haven't been able to reliably reproduce this case though, so I'm just reporting the simple case above.  It seems likely they're the same bug with two different symptoms.  With vaex==3.0.0 the query above returns a single row as you'd expect, so I suspect maybe [this change](https://github.com/vaexio/vaex/pull/984/files) has caused the issue, although I don't know enough to say how?

**Software information**
 - Vaex version (`import vaex; vaex.__version__)`: `{'vaex': '4.0.0.dev0', 'vaex-core': '4.0.0a5', 'vaex-viz': '0.5.0.dev0', 'vaex-hdf5': '0.7.0a2', 'vaex-server': '0.4.0.dev0', 'vaex-astro': '0.8.0.dev0', 'vaex-ui': '0.3.0', 'vaex-jupyter': '0.6.0.dev0', 'vaex-ml': '0.11.0a4', 'vaex-graphql': '0.2.0.dev0'}`
 - Vaex was installed from source
 - OS: Ubuntu 18.04.4 LTS


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `packages/vaex-core/vaex/arrow/numpy_dispatch.py`
Content:
```
1 import numpy as np
2 import pyarrow as pa
3 import pyarrow.compute as pc
4 import vaex
5 from ..expression import _binary_ops, _unary_ops, reversable
6 
7 
8 def combine_missing(a, b):
9     assert a.offset == 0
10     if a.null_count > 0 or b.null_count > 0:
11         # not optimal
12         nulls = pc.invert(pc.or_(a.is_null(), b.is_null()))
13         assert nulls.offset == 0
14         nulls_buffer = nulls.buffers()[1]
15         # this is not the case: no reason why it should be (TODO: open arrow issue)
16         # assert nulls.buffers()[0] is None
17     else:
18         nulls_buffer = None
19     buffers = a.buffers()
20     return pa.Array.from_buffers(a.type, len(a), [nulls_buffer, buffers[1]])
21 
22 class NumpyDispatch:
23     def __init__(self, ar):
24         self._array = ar
25         if isinstance(ar, vaex.column.ColumnStringArrow):
26             ar = pa.array(ar)
27         if isinstance(ar, np.ndarray):
28             self._numpy_array = ar
29             self._arrow_array = None
30         elif isinstance(ar, vaex.array_types.supported_arrow_array_types):
31             self._numpy_array = None
32             self._arrow_array = ar
33         else:
34             raise TypeError(f'Only support numpy and arrow, not {type(ar)}')
35 
36     def add_missing(self, ar):
37         if isinstance(ar, np.ndarray):
38             # if we are an arrow array, we upgrade ar to one
39             if isinstance(self._array, vaex.array_types.supported_arrow_array_types):
40                 ar = vaex.array_types.to_arrow(ar)
41                 ar = combine_missing(ar, self._array)
42             # else: both numpy, handled by numpy
43         else:
44             if isinstance(self._array, vaex.array_types.supported_arrow_array_types):
45                 ar = combine_missing(ar, self._array)
46             # else: was numpy, handled by numpy
47         return ar
48 
49 
50     @property
51     def numpy_array(self):
52         if self._numpy_array is None:
53             import vaex.arrow.convert
54             arrow_array = self._arrow_array
55             arrow_array = vaex.arrow.convert.ensure_not_chunked(arrow_array)
56             buffers = arrow_array.buffers()
57             # for math, we don't care about the nulls
58             if buffers[0] is not None:
59                 buffers[0] = None
60                 arrow_array = pa.Array.from_buffers(arrow_array.type, len(arrow_array), buffers, offset=arrow_array.offset)
61             self._numpy_array = vaex.array_types.to_numpy(arrow_array)
62         return self._numpy_array
63 
64     @property
65     def arrow_array(self):
66         if self._arrow_array is None:
67             # convert lazily, since not all arrow arrays (e.g. lists) can be converted
68             if self._arrow_array is None:
69                 self._arrow_array = vaex.array_types.to_arrow(self._numpy_array)
70         return self._arrow_array
71 
72     def __eq__(self, rhs):
73         if vaex.array_types.is_string(self.arrow_array):
74             # this does not support scalar input
75             # return pc.equal(self.arrow_array, rhs)
76             return NumpyDispatch(pa.array(vaex.functions.str_equals(self.arrow_array, rhs)))
77         else:
78             if isinstance(rhs, NumpyDispatch):
79                 rhs = rhs.numpy_array
80             return NumpyDispatch(pa.array(self.numpy_array == rhs))
81 
82 for op in _binary_ops:
83     def closure(op=op):
84         def operator(a, b):
85             a_data = a
86             b_data = b
87             if isinstance(a, NumpyDispatch):
88                 a_data = a.numpy_array
89             if isinstance(b, NumpyDispatch):
90                 b_data = b.numpy_array
91             result_data = op['op'](a_data, b_data)
92             if isinstance(a, NumpyDispatch):
93                 result_data = a.add_missing(result_data)
94             if isinstance(b, NumpyDispatch):
95                 result_data = b.add_missing(result_data)
96             return NumpyDispatch(result_data)
97         return operator
98     method_name = '__%s__' % op['name']
99     if op['name'] != "eq":
100         setattr(NumpyDispatch, method_name, closure())
101      # to support e.g. (1 + ...)    # to support e.g. (1 + ...)
102     if op['name'] in reversable:
103         def closure(op=op):
104             def operator(b, a):
105                 a_data = a
106                 b_data = b
107                 if isinstance(a, NumpyDispatch):
108                     a_data = a.numpy_array
109                 if isinstance(b, NumpyDispatch):
110                     b_data = b.numpy_array
111                 result_data = op['op'](a_data, b_data)
112                 if isinstance(a, NumpyDispatch):
113                     result_data = a.add_missing(result_data)
114                 if isinstance(b, NumpyDispatch):
115                     result_data = b.add_missing(result_data)
116                 return NumpyDispatch(result_data)
117             return operator
118         method_name = '__r%s__' % op['name']
119         setattr(NumpyDispatch, method_name, closure())
120 
121 
122 for op in _unary_ops:
123     def closure(op=op):
124         def operator(a):
125             a_data = a.numpy_array
126             result_data = op['op'](a_data)
127             if isinstance(a, NumpyDispatch):
128                 result_data = a.add_missing(result_data)
129             return NumpyDispatch(result_data)
130         return operator
131     method_name = '__%s__' % op['name']
132     setattr(NumpyDispatch, method_name, closure())
133 
134 
135 def wrap(value):
136     if not isinstance(value, NumpyDispatch): # and not isinstance(value, np.ndarray):
137         if isinstance(value, vaex.array_types.supported_array_types + (vaex.column.ColumnStringArrow,)):
138             return NumpyDispatch(value)
139     # for performance reasons we don't visit lists and dicts
140     return value
141 
142 
143 def unwrap(value):
144     if isinstance(value, NumpyDispatch):
145         return value._array
146     # for performance reasons we don't visit lists and dicts
147     return value
148 
149 
150 def autowrapper(f):
151     '''Takes a function f, and will unwrap all its arguments and wrap the return value'''
152     def wrapper(*args, **kwargs):
153         args_original = args
154         args = list(map(unwrap, args))
155         kwargs = {k: unwrap(v) for k, v, in kwargs.items()}
156         result = f(*args, **kwargs)
157         if isinstance(result, vaex.array_types.supported_arrow_array_types):
158             result = NumpyDispatch(result)
159         return result
160     return wrapper
161
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/packages/vaex-core/vaex/arrow/numpy_dispatch.py b/packages/vaex-core/vaex/arrow/numpy_dispatch.py
--- a/packages/vaex-core/vaex/arrow/numpy_dispatch.py
+++ b/packages/vaex-core/vaex/arrow/numpy_dispatch.py
@@ -69,15 +69,6 @@
                 self._arrow_array = vaex.array_types.to_arrow(self._numpy_array)
         return self._arrow_array
 
-    def __eq__(self, rhs):
-        if vaex.array_types.is_string(self.arrow_array):
-            # this does not support scalar input
-            # return pc.equal(self.arrow_array, rhs)
-            return NumpyDispatch(pa.array(vaex.functions.str_equals(self.arrow_array, rhs)))
-        else:
-            if isinstance(rhs, NumpyDispatch):
-                rhs = rhs.numpy_array
-            return NumpyDispatch(pa.array(self.numpy_array == rhs))
 
 for op in _binary_ops:
     def closure(op=op):
@@ -88,7 +79,10 @@
                 a_data = a.numpy_array
             if isinstance(b, NumpyDispatch):
                 b_data = b.numpy_array
-            result_data = op['op'](a_data, b_data)
+            if op['name'] == 'eq' and (vaex.array_types.is_string(a_data) or vaex.array_types.is_string(b_data)):
+                result_data = vaex.functions.str_equals(a_data, b_data)
+            else:
+                result_data = op['op'](a_data, b_data)
             if isinstance(a, NumpyDispatch):
                 result_data = a.add_missing(result_data)
             if isinstance(b, NumpyDispatch):
@@ -96,8 +90,7 @@
             return NumpyDispatch(result_data)
         return operator
     method_name = '__%s__' % op['name']
-    if op['name'] != "eq":
-        setattr(NumpyDispatch, method_name, closure())
+    setattr(NumpyDispatch, method_name, closure())
      # to support e.g. (1 + ...)    # to support e.g. (1 + ...)
     if op['name'] in reversable:
         def closure(op=op):
@@ -154,7 +147,5 @@
         args = list(map(unwrap, args))
         kwargs = {k: unwrap(v) for k, v, in kwargs.items()}
         result = f(*args, **kwargs)
-        if isinstance(result, vaex.array_types.supported_arrow_array_types):
-            result = NumpyDispatch(result)
-        return result
+        return wrap(result)
     return wrapper

{"golden_diff": "diff --git a/packages/vaex-core/vaex/arrow/numpy_dispatch.py b/packages/vaex-core/vaex/arrow/numpy_dispatch.py\n--- a/packages/vaex-core/vaex/arrow/numpy_dispatch.py\n+++ b/packages/vaex-core/vaex/arrow/numpy_dispatch.py\n@@ -69,15 +69,6 @@\n                 self._arrow_array = vaex.array_types.to_arrow(self._numpy_array)\n         return self._arrow_array\n \n-    def __eq__(self, rhs):\n-        if vaex.array_types.is_string(self.arrow_array):\n-            # this does not support scalar input\n-            # return pc.equal(self.arrow_array, rhs)\n-            return NumpyDispatch(pa.array(vaex.functions.str_equals(self.arrow_array, rhs)))\n-        else:\n-            if isinstance(rhs, NumpyDispatch):\n-                rhs = rhs.numpy_array\n-            return NumpyDispatch(pa.array(self.numpy_array == rhs))\n \n for op in _binary_ops:\n     def closure(op=op):\n@@ -88,7 +79,10 @@\n                 a_data = a.numpy_array\n             if isinstance(b, NumpyDispatch):\n                 b_data = b.numpy_array\n-            result_data = op['op'](a_data, b_data)\n+            if op['name'] == 'eq' and (vaex.array_types.is_string(a_data) or vaex.array_types.is_string(b_data)):\n+                result_data = vaex.functions.str_equals(a_data, b_data)\n+            else:\n+                result_data = op['op'](a_data, b_data)\n             if isinstance(a, NumpyDispatch):\n                 result_data = a.add_missing(result_data)\n             if isinstance(b, NumpyDispatch):\n@@ -96,8 +90,7 @@\n             return NumpyDispatch(result_data)\n         return operator\n     method_name = '__%s__' % op['name']\n-    if op['name'] != \"eq\":\n-        setattr(NumpyDispatch, method_name, closure())\n+    setattr(NumpyDispatch, method_name, closure())\n      # to support e.g. (1 + ...)    # to support e.g. (1 + ...)\n     if op['name'] in reversable:\n         def closure(op=op):\n@@ -154,7 +147,5 @@\n         args = list(map(unwrap, args))\n         kwargs = {k: unwrap(v) for k, v, in kwargs.items()}\n         result = f(*args, **kwargs)\n-        if isinstance(result, vaex.array_types.supported_arrow_array_types):\n-            result = NumpyDispatch(result)\n-        return result\n+        return wrap(result)\n     return wrapper\n", "issue": "[BUG-REPORT] Multiple selection filters broken in latest version\n**Description**\r\nI think I've found an issue with the new internal representation of arrays as arrow arrays.  This is in 4.0.0.dev0, so you might have a plan to fix it by the next proper release, but just flagging it anyway as it seems relatively major.\r\n\r\nWith a simple test table `data`:\r\n\r\n```\r\nimport pandas as pd\r\nimport numpy as np\r\nimport vaex\r\n\r\ndata = pd.DataFrame({\r\n  \"A\":np.array([1,2,3,4]),\r\n  \"B\":[\"A\",\"B\",\"C\",\"D\"],\r\n  \"C\":[np.datetime64('2019-10-07 09:00:00'),np.datetime64('2019-10-07 10:00:00'),np.datetime64('2019-10-07 11:00:00'),np.datetime64('2019-10-07 12:00:00')]\r\n  })\r\n\r\nvaex.from_pandas(data).export_arrow('arrow_sample.arrow')\r\ndata = vaex.open('arrow_sample.arrow')\r\n```\r\n\r\nNormal single clause filters work as you'd expect:\r\n\r\n```\r\n>>> data[(data['B'] == 'D')]\r\n  #    A  B    C\r\n  0    4  'D'  Timestamp('2019-10-07 12:00:00')\r\n>>> data[(data['A'] > 2)]\r\n  #    A  B    C\r\n  0    3  'C'  Timestamp('2019-10-07 11:00:00')\r\n  1    4  'D'  Timestamp('2019-10-07 12:00:00')\r\n```\r\n\r\nBut when you combine multiple clauses the answer is wrong (this should return a single row):\r\n\r\n```\r\n>>> data[(data['B'] == 'D') & (data['A'] > 2)]\r\n  #    A  B    C\r\n  0    1  'A'  Timestamp('2019-10-07 09:00:00')\r\n  1    2  'B'  Timestamp('2019-10-07 10:00:00')\r\n  2    3  'C'  Timestamp('2019-10-07 11:00:00')\r\n  3    4  'D'  Timestamp('2019-10-07 12:00:00')\r\n```\r\n\r\nI first noticed this using a much larger table, and in that case it seemed to cause a memory leak (process memory continually rose after the query)  I haven't been able to reliably reproduce this case though, so I'm just reporting the simple case above.  It seems likely they're the same bug with two different symptoms.  With vaex==3.0.0 the query above returns a single row as you'd expect, so I suspect maybe [this change](https://github.com/vaexio/vaex/pull/984/files) has caused the issue, although I don't know enough to say how?\r\n\r\n**Software information**\r\n - Vaex version (`import vaex; vaex.__version__)`: `{'vaex': '4.0.0.dev0', 'vaex-core': '4.0.0a5', 'vaex-viz': '0.5.0.dev0', 'vaex-hdf5': '0.7.0a2', 'vaex-server': '0.4.0.dev0', 'vaex-astro': '0.8.0.dev0', 'vaex-ui': '0.3.0', 'vaex-jupyter': '0.6.0.dev0', 'vaex-ml': '0.11.0a4', 'vaex-graphql': '0.2.0.dev0'}`\r\n - Vaex was installed from source\r\n - OS: Ubuntu 18.04.4 LTS\r\n\n", "before_files": [{"content": "import numpy as np\nimport pyarrow as pa\nimport pyarrow.compute as pc\nimport vaex\nfrom ..expression import _binary_ops, _unary_ops, reversable\n\n\ndef combine_missing(a, b):\n    assert a.offset == 0\n    if a.null_count > 0 or b.null_count > 0:\n        # not optimal\n        nulls = pc.invert(pc.or_(a.is_null(), b.is_null()))\n        assert nulls.offset == 0\n        nulls_buffer = nulls.buffers()[1]\n        # this is not the case: no reason why it should be (TODO: open arrow issue)\n        # assert nulls.buffers()[0] is None\n    else:\n        nulls_buffer = None\n    buffers = a.buffers()\n    return pa.Array.from_buffers(a.type, len(a), [nulls_buffer, buffers[1]])\n\nclass NumpyDispatch:\n    def __init__(self, ar):\n        self._array = ar\n        if isinstance(ar, vaex.column.ColumnStringArrow):\n            ar = pa.array(ar)\n        if isinstance(ar, np.ndarray):\n            self._numpy_array = ar\n            self._arrow_array = None\n        elif isinstance(ar, vaex.array_types.supported_arrow_array_types):\n            self._numpy_array = None\n            self._arrow_array = ar\n        else:\n            raise TypeError(f'Only support numpy and arrow, not {type(ar)}')\n\n    def add_missing(self, ar):\n        if isinstance(ar, np.ndarray):\n            # if we are an arrow array, we upgrade ar to one\n            if isinstance(self._array, vaex.array_types.supported_arrow_array_types):\n                ar = vaex.array_types.to_arrow(ar)\n                ar = combine_missing(ar, self._array)\n            # else: both numpy, handled by numpy\n        else:\n            if isinstance(self._array, vaex.array_types.supported_arrow_array_types):\n                ar = combine_missing(ar, self._array)\n            # else: was numpy, handled by numpy\n        return ar\n\n\n    @property\n    def numpy_array(self):\n        if self._numpy_array is None:\n            import vaex.arrow.convert\n            arrow_array = self._arrow_array\n            arrow_array = vaex.arrow.convert.ensure_not_chunked(arrow_array)\n            buffers = arrow_array.buffers()\n            # for math, we don't care about the nulls\n            if buffers[0] is not None:\n                buffers[0] = None\n                arrow_array = pa.Array.from_buffers(arrow_array.type, len(arrow_array), buffers, offset=arrow_array.offset)\n            self._numpy_array = vaex.array_types.to_numpy(arrow_array)\n        return self._numpy_array\n\n    @property\n    def arrow_array(self):\n        if self._arrow_array is None:\n            # convert lazily, since not all arrow arrays (e.g. lists) can be converted\n            if self._arrow_array is None:\n                self._arrow_array = vaex.array_types.to_arrow(self._numpy_array)\n        return self._arrow_array\n\n    def __eq__(self, rhs):\n        if vaex.array_types.is_string(self.arrow_array):\n            # this does not support scalar input\n            # return pc.equal(self.arrow_array, rhs)\n            return NumpyDispatch(pa.array(vaex.functions.str_equals(self.arrow_array, rhs)))\n        else:\n            if isinstance(rhs, NumpyDispatch):\n                rhs = rhs.numpy_array\n            return NumpyDispatch(pa.array(self.numpy_array == rhs))\n\nfor op in _binary_ops:\n    def closure(op=op):\n        def operator(a, b):\n            a_data = a\n            b_data = b\n            if isinstance(a, NumpyDispatch):\n                a_data = a.numpy_array\n            if isinstance(b, NumpyDispatch):\n                b_data = b.numpy_array\n            result_data = op['op'](a_data, b_data)\n            if isinstance(a, NumpyDispatch):\n                result_data = a.add_missing(result_data)\n            if isinstance(b, NumpyDispatch):\n                result_data = b.add_missing(result_data)\n            return NumpyDispatch(result_data)\n        return operator\n    method_name = '__%s__' % op['name']\n    if op['name'] != \"eq\":\n        setattr(NumpyDispatch, method_name, closure())\n     # to support e.g. (1 + ...)    # to support e.g. (1 + ...)\n    if op['name'] in reversable:\n        def closure(op=op):\n            def operator(b, a):\n                a_data = a\n                b_data = b\n                if isinstance(a, NumpyDispatch):\n                    a_data = a.numpy_array\n                if isinstance(b, NumpyDispatch):\n                    b_data = b.numpy_array\n                result_data = op['op'](a_data, b_data)\n                if isinstance(a, NumpyDispatch):\n                    result_data = a.add_missing(result_data)\n                if isinstance(b, NumpyDispatch):\n                    result_data = b.add_missing(result_data)\n                return NumpyDispatch(result_data)\n            return operator\n        method_name = '__r%s__' % op['name']\n        setattr(NumpyDispatch, method_name, closure())\n\n\nfor op in _unary_ops:\n    def closure(op=op):\n        def operator(a):\n            a_data = a.numpy_array\n            result_data = op['op'](a_data)\n            if isinstance(a, NumpyDispatch):\n                result_data = a.add_missing(result_data)\n            return NumpyDispatch(result_data)\n        return operator\n    method_name = '__%s__' % op['name']\n    setattr(NumpyDispatch, method_name, closure())\n\n\ndef wrap(value):\n    if not isinstance(value, NumpyDispatch): # and not isinstance(value, np.ndarray):\n        if isinstance(value, vaex.array_types.supported_array_types + (vaex.column.ColumnStringArrow,)):\n            return NumpyDispatch(value)\n    # for performance reasons we don't visit lists and dicts\n    return value\n\n\ndef unwrap(value):\n    if isinstance(value, NumpyDispatch):\n        return value._array\n    # for performance reasons we don't visit lists and dicts\n    return value\n\n\ndef autowrapper(f):\n    '''Takes a function f, and will unwrap all its arguments and wrap the return value'''\n    def wrapper(*args, **kwargs):\n        args_original = args\n        args = list(map(unwrap, args))\n        kwargs = {k: unwrap(v) for k, v, in kwargs.items()}\n        result = f(*args, **kwargs)\n        if isinstance(result, vaex.array_types.supported_arrow_array_types):\n            result = NumpyDispatch(result)\n        return result\n    return wrapper\n", "path": "packages/vaex-core/vaex/arrow/numpy_dispatch.py"}], "after_files": [{"content": "import numpy as np\nimport pyarrow as pa\nimport pyarrow.compute as pc\nimport vaex\nfrom ..expression import _binary_ops, _unary_ops, reversable\n\n\ndef combine_missing(a, b):\n    assert a.offset == 0\n    if a.null_count > 0 or b.null_count > 0:\n        # not optimal\n        nulls = pc.invert(pc.or_(a.is_null(), b.is_null()))\n        assert nulls.offset == 0\n        nulls_buffer = nulls.buffers()[1]\n        # this is not the case: no reason why it should be (TODO: open arrow issue)\n        # assert nulls.buffers()[0] is None\n    else:\n        nulls_buffer = None\n    buffers = a.buffers()\n    return pa.Array.from_buffers(a.type, len(a), [nulls_buffer, buffers[1]])\n\nclass NumpyDispatch:\n    def __init__(self, ar):\n        self._array = ar\n        if isinstance(ar, vaex.column.ColumnStringArrow):\n            ar = pa.array(ar)\n        if isinstance(ar, np.ndarray):\n            self._numpy_array = ar\n            self._arrow_array = None\n        elif isinstance(ar, vaex.array_types.supported_arrow_array_types):\n            self._numpy_array = None\n            self._arrow_array = ar\n        else:\n            raise TypeError(f'Only support numpy and arrow, not {type(ar)}')\n\n    def add_missing(self, ar):\n        if isinstance(ar, np.ndarray):\n            # if we are an arrow array, we upgrade ar to one\n            if isinstance(self._array, vaex.array_types.supported_arrow_array_types):\n                ar = vaex.array_types.to_arrow(ar)\n                ar = combine_missing(ar, self._array)\n            # else: both numpy, handled by numpy\n        else:\n            if isinstance(self._array, vaex.array_types.supported_arrow_array_types):\n                ar = combine_missing(ar, self._array)\n            # else: was numpy, handled by numpy\n        return ar\n\n\n    @property\n    def numpy_array(self):\n        if self._numpy_array is None:\n            import vaex.arrow.convert\n            arrow_array = self._arrow_array\n            arrow_array = vaex.arrow.convert.ensure_not_chunked(arrow_array)\n            buffers = arrow_array.buffers()\n            # for math, we don't care about the nulls\n            if buffers[0] is not None:\n                buffers[0] = None\n                arrow_array = pa.Array.from_buffers(arrow_array.type, len(arrow_array), buffers, offset=arrow_array.offset)\n            self._numpy_array = vaex.array_types.to_numpy(arrow_array)\n        return self._numpy_array\n\n    @property\n    def arrow_array(self):\n        if self._arrow_array is None:\n            # convert lazily, since not all arrow arrays (e.g. lists) can be converted\n            if self._arrow_array is None:\n                self._arrow_array = vaex.array_types.to_arrow(self._numpy_array)\n        return self._arrow_array\n\n\nfor op in _binary_ops:\n    def closure(op=op):\n        def operator(a, b):\n            a_data = a\n            b_data = b\n            if isinstance(a, NumpyDispatch):\n                a_data = a.numpy_array\n            if isinstance(b, NumpyDispatch):\n                b_data = b.numpy_array\n            if op['name'] == 'eq' and (vaex.array_types.is_string(a_data) or vaex.array_types.is_string(b_data)):\n                result_data = vaex.functions.str_equals(a_data, b_data)\n            else:\n                result_data = op['op'](a_data, b_data)\n            if isinstance(a, NumpyDispatch):\n                result_data = a.add_missing(result_data)\n            if isinstance(b, NumpyDispatch):\n                result_data = b.add_missing(result_data)\n            return NumpyDispatch(result_data)\n        return operator\n    method_name = '__%s__' % op['name']\n    setattr(NumpyDispatch, method_name, closure())\n     # to support e.g. (1 + ...)    # to support e.g. (1 + ...)\n    if op['name'] in reversable:\n        def closure(op=op):\n            def operator(b, a):\n                a_data = a\n                b_data = b\n                if isinstance(a, NumpyDispatch):\n                    a_data = a.numpy_array\n                if isinstance(b, NumpyDispatch):\n                    b_data = b.numpy_array\n                result_data = op['op'](a_data, b_data)\n                if isinstance(a, NumpyDispatch):\n                    result_data = a.add_missing(result_data)\n                if isinstance(b, NumpyDispatch):\n                    result_data = b.add_missing(result_data)\n                return NumpyDispatch(result_data)\n            return operator\n        method_name = '__r%s__' % op['name']\n        setattr(NumpyDispatch, method_name, closure())\n\n\nfor op in _unary_ops:\n    def closure(op=op):\n        def operator(a):\n            a_data = a.numpy_array\n            result_data = op['op'](a_data)\n            if isinstance(a, NumpyDispatch):\n                result_data = a.add_missing(result_data)\n            return NumpyDispatch(result_data)\n        return operator\n    method_name = '__%s__' % op['name']\n    setattr(NumpyDispatch, method_name, closure())\n\n\ndef wrap(value):\n    if not isinstance(value, NumpyDispatch): # and not isinstance(value, np.ndarray):\n        if isinstance(value, vaex.array_types.supported_array_types + (vaex.column.ColumnStringArrow,)):\n            return NumpyDispatch(value)\n    # for performance reasons we don't visit lists and dicts\n    return value\n\n\ndef unwrap(value):\n    if isinstance(value, NumpyDispatch):\n        return value._array\n    # for performance reasons we don't visit lists and dicts\n    return value\n\n\ndef autowrapper(f):\n    '''Takes a function f, and will unwrap all its arguments and wrap the return value'''\n    def wrapper(*args, **kwargs):\n        args_original = args\n        args = list(map(unwrap, args))\n        kwargs = {k: unwrap(v) for k, v, in kwargs.items()}\n        result = f(*args, **kwargs)\n        return wrap(result)\n    return wrapper\n", "path": "packages/vaex-core/vaex/arrow/numpy_dispatch.py"}]}