Repository: scikit-hep/awkward

Issue:
Unexpected results when using ak.from_arrow()
I'm getting a lot of mileage out of zero-copy (I think) conversions between awkward and Arrow. Thanks for all that functionality. However, when I introduce some slicing, I see some mismatches. Example below.

```python
import numpy as np # 1.19.0
import awkward as ak # 1.0.1
import pyarrow as pa # 2.0.0

np_arr = np.random.normal(0,1,10)
ak_arr = ak.Array(np_arr)
pa_arr = ak.to_arrow(ak_arr)
```

These are all identical, as expected.
```python

>>> print(np_arr)
[ 0.12213336  0.07918481 -0.02079451  0.52849207  0.56343541  0.08746807
  0.56490973 -0.47526351  0.295584    0.27810744]
>>> print(ak_arr)
[0.122, 0.0792, -0.0208, 0.528, 0.563, 0.0875, 0.565, -0.475, 0.296, 0.278]
>>> print(pa_arr)
[
  0.122133,
  0.0791848,
  -0.0207945,
  0.528492,
  0.563435,
  0.0874681,
  0.56491,
  -0.475264,
  0.295584,
  0.278107
]
```

I would expect to see the same values here, but it looks like awkward is going to the beginning of the array.
```python
>>> print(pa_arr[5:])
[
  0.0874681,
  0.56491,
  -0.475264,
  0.295584,
  0.278107
]
>>> print(ak.from_arrow(pa_arr[5:]))
[0.122, 0.0792, -0.0208, 0.528, 0.563]
```



Discussion:
I actually discovered this with jagged arrays, but put the minimal flattened example above.
```python
>>> a = ak.to_arrow(ak.Array([[1],[],[2,3,4],[5]]))
>>> a
<pyarrow.lib.ListArray object at 0x18fe05288>
[
  [
    1
  ],
  [],
  [
    2,
    3,
    4
  ],
  [
    5
  ]
]

>>> ak.from_arrow(a[2:])
<Array [[1], []] type='2 * option[var * ?int64]'>
```

My use case is putting jagged arrays into pandas dataframes without copying (or making list of lists) by converting awkward to Arrow and then using pyarrow's [fletcher](https://github.com/xhochy/fletcher) pandas ExtensionArray to get stuff like this:

|    | Jet_pt                                                            |   MET_pt |
|---:|:------------------------------------------------------------------|---------:|
|  0 | [39.65625  29.9375   23.09375  18.0625   17.6875   15.609375]     |  46.0749 |
|  1 | [76.75      61.84375   58.875     52.0625    22.171875  17.28125  |  29.7964 |
|    |  17.09375   15.453125  15.2578125]                                |          |
|  2 | [23.109375  16.453125  15.5703125]                                |  82.0033 |
|  3 | [59.8125    45.3125    35.21875   22.03125   19.046875  15.796875 |  77.7026 |
|    |  15.6328125]                                                      |          |
|  4 | [55.71875   47.84375   44.46875   42.875     25.578125  22.953125 |  29.9007 |
|    |  20.890625  19.5       15.8671875]                                |          |

And slicing on this gives me the expected Arrow arrays, but converting back to awkward in order to actually compute things, fails:
```python
>>> ak.from_arrow(df.iloc[2:]["Jet_pt"].values.data)
<Array [[39.7, 29.9, 23.1, ... 16.5, 15.6]] type='3 * option[var * ?float32]'>
```
From @trickarcher:

> Hey Jim, I was going through the Issue #624 and it was related to `ak.from_arrow`.  I was able to reproduce the issue. I tried forming sliced array from buffers in the pyarrow ecosystem, and got a similar failure.

```python
 >>> import awkward as ak
 >>> import pyarrow as pa
 >>> pa_arr=pa.array([1,2,3,4,5,6,7,8,9,10])
 >>> pa_arr
 <pyarrow.lib.Int64Array object at 0x7f3a0d009b20>
 [
   1,
   2,
   3,
   4,
   5,
   6,
   7,
   8,
   9,
   10
 ]
 >>> pa_arr.buffers()
 [None, <pyarrow.lib.Buffer object at 0x7f3a05b960f0>]
 >>> pa_arr[5:].buffers()
 [None, <pyarrow.lib.Buffer object at 0x7f3a05b8cd70>]
 >>> import numpy as np
 >>> pa.Array.from_buffers(pa.from_numpy_dtype(np.int64), 5, pa_arr[5:].buffers())
 <pyarrow.lib.Int64Array object at 0x7f3a05b95700>
 [
   1,
   2,
   3,
   4,
   5
 ]
```

> However, the sliced buffers are totally different from the original buffers. How can we debug further from here?

Actually, this example and @aminnj's are both showing the same thing: pyarrow's buffers are defined by a pointer, a length, and an offset (much like ours in Awkward, which we call `ptr`, `length`, and `offset` in C++). When you and I wrote `from_arrow`, we assumed that pyarrow's `buffers()` was giving offset-adjusted pointers (like our `data()`), but apparently it was giving non-adjusted pointers (like our `ptr`).

That's why `pa.array(np.arange(10))[6:]` is being turned into `ak.Array([0, 1, 2, 3])` instead of `ak.Array([6, 7, 8, 9])`. It has the right buffer and the right length (4), but it should be starting at 6, not starting at 0.

So the problem is that we're interpreting the output of pyarrow's `buffers()` incorrectly—we additionally need to get an offset number from somewhere. I don't know where that is, but the way to search for it is to open this up interactively and call `dir` on things. It's a number that would be 6 (items) or 48 (bytes) in the example above, possibly on the pyarrow object, possibly on the Python buffer returned by `buffers()`, possibly somewhere else, but accessible from the pyarrow object.

We'll have to check this on pyarrow arrays with missing values, since they do this with a bitmask, which can't be sliced at non-byte boundaries. (In practice, we'd have to build the Awkward Array node with a `length + offset`, then slice the Awkward node by the `offset`. We'd only want to do that if the offset is non-zero, since that would force a conversion of the BitMaskArray into a ByteMaskArray to be able to cut it at arbitrary places, and that's pointless when the place is zero.)

We didn't notice it before because we weren't slicing pyarrow arrays before converting them. This is just another illustration of how inadequate even 100% test coverage is! (Remember how I added a bunch of tests to make sure that every line of code on the function is tested? That doesn't help if the thing we're missing is a value, like this offset not being zero!)

I'm not in a good position to investigate this now, but let me know here if you find something. Thanks!
I got a chance to look at it on my computer, and I can confirm:

```python
>>> array = pa.array(np.arange(10))[6:]
>>> array
<pyarrow.lib.Int64Array object at 0x7fcc999f4280>
[
  6,
  7,
  8,
  9
]
>>> mask, content = array.buffers()
>>> mask
>>> content
<pyarrow.lib.Buffer object at 0x7fcc99d053b0>
>>> np.frombuffer(content, dtype=np.int64)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
```

The Python buffer object returned by `array.buffers()` hides Arrow's equivalent of an "offset" parameter. It also hides the "length"; we must be getting that from `len(array)` (which is `4`).
Here it is: it's an attribute called `offset`, which is attached to the pyarrow array itself:

```python
>>> array.offset
6
>>> pa.array(np.arange(10)).offset
0
>>> pa.array(np.arange(10))[6:].offset
6
>>> pa.array(np.arange(10))[2:].offset
2
```
Hi Jim, thanks for such a fast response. I was playing a bit more yesterday and for flat arrays, found I could do
```python
>>> import pyarrow as pa
>>> import awkward1 as ak
>>> import numpy as np
>>> orig = pa.Array.from_pandas([1,2,3,4,5])
>>> a = orig[2:4]
>>> _, buffer = a.buffers()
>>> np.frombuffer(buffer, dtype=int)
array([1, 2, 3, 4, 5])
>>> np.frombuffer(buffer.slice(a.offset * a.type.bit_width//8), dtype=int)[:len(a)]
array([3, 4, 5])
```
but I can see how it's not trivial when there's a mask or jaggedness. 

Anyway, as a solution in my current code, I put
```python
        if array_arrow.offset != 0:
            array_arrow = array_arrow.take(np.arange(len(array_arrow)))
```
before I try to convert an arrow array into awkward.

Resolving patch:
diff --git a/src/awkward/operations/convert.py b/src/awkward/operations/convert.py
--- a/src/awkward/operations/convert.py
+++ b/src/awkward/operations/convert.py
@@ -2139,31 +2139,48 @@ def recurse(layout, mask, is_option):
             nulls = index < 0
             index[nulls] = 0
 
-            if len(nulls) % 8 == 0:
-                this_bytemask = (~nulls).view(np.uint8)
-            else:
-                length = int(numpy.ceil(len(nulls) / 8.0)) * 8
-                this_bytemask = numpy.empty(length, dtype=np.uint8)
-                this_bytemask[: len(nulls)] = ~nulls
-                this_bytemask[len(nulls) :] = 0
+            if layout.parameter("__array__") == "categorical":
+                dictionary = recurse(layout.content, None, False)
 
-            this_bitmask = numpy.packbits(
-                this_bytemask.reshape(-1, 8)[:, ::-1].reshape(-1)
-            )
+                if mask is None:
+                    bytemask = nulls
+                else:
+                    bytemask = (
+                        numpy.unpackbits(~mask)
+                        .reshape(-1, 8)[:, ::-1]
+                        .reshape(-1)
+                        .view(np.bool_)
+                    )[: len(index)]
+                    bytemask[nulls] = True
+
+                return pyarrow.DictionaryArray.from_arrays(index, dictionary, bytemask)
 
-            if isinstance(layout, ak.layout.IndexedOptionArray32):
-                next = ak.layout.IndexedArray32(
-                    ak.layout.Index32(index), layout.content
-                )
             else:
-                next = ak.layout.IndexedArray64(
-                    ak.layout.Index64(index), layout.content
+                if len(nulls) % 8 == 0:
+                    this_bytemask = (~nulls).view(np.uint8)
+                else:
+                    length = int(numpy.ceil(len(nulls) / 8.0)) * 8
+                    this_bytemask = numpy.empty(length, dtype=np.uint8)
+                    this_bytemask[: len(nulls)] = ~nulls
+                    this_bytemask[len(nulls) :] = 0
+
+                this_bitmask = numpy.packbits(
+                    this_bytemask.reshape(-1, 8)[:, ::-1].reshape(-1)
                 )
 
-            if mask is None:
-                return recurse(next, this_bitmask, True)
-            else:
-                return recurse(next, mask & this_bitmask, True)
+                if isinstance(layout, ak.layout.IndexedOptionArray32):
+                    next = ak.layout.IndexedArray32(
+                        ak.layout.Index32(index), layout.content
+                    )
+                else:
+                    next = ak.layout.IndexedArray64(
+                        ak.layout.Index64(index), layout.content
+                    )
+
+                if mask is None:
+                    return recurse(next, this_bitmask, True)
+                else:
+                    return recurse(next, mask & this_bitmask, True)
 
         elif isinstance(layout, ak.layout.BitMaskedArray):
             bitmask = numpy.asarray(layout.mask, dtype=np.uint8)
@@ -2299,42 +2316,24 @@ def from_arrow(array, highlevel=True, behavior=None):
 def _from_arrow(array, pass_empty_field, highlevel=True, behavior=None):
     pyarrow = _import_pyarrow("ak.from_arrow")
 
-    def popbuffers(array, tpe, buffers, length):
+    def popbuffers(array, tpe, buffers):
         if isinstance(tpe, pyarrow.lib.DictionaryType):
-            index = popbuffers(
-                None if array is None else array.indices,
-                tpe.index_type,
-                buffers,
-                length,
-            )
-            if array is not None:
-                content = handle_arrow(array.dictionary)
-            else:
-                raise NotImplementedError(
-                    "Arrow dictionary inside of UnionArray"
-                    + ak._util.exception_suffix(__file__)
-                )
+            index = popbuffers(array.indices, tpe.index_type, buffers)
+            content = handle_arrow(array.dictionary)
+
+            out = ak.layout.IndexedArray32(
+                ak.layout.Index32(index.content),
+                content,
+                parameters={"__array__": "categorical"},
+            ).simplify()
 
             if isinstance(index, ak.layout.BitMaskedArray):
-                if isinstance(content, ak.layout.UnmaskedArray):
-                    content = content.content
                 return ak.layout.BitMaskedArray(
-                    index.mask,
-                    ak.layout.IndexedArray32(
-                        ak.layout.Index32(index.content),
-                        content,
-                        parameters={"__array__": "categorical"},
-                    ),
-                    True,
-                    length,
-                    True,
-                )
+                    index.mask, out, True, len(index), True
+                ).simplify()
             else:
-                return ak.layout.IndexedArray32(
-                    ak.layout.Index32(index),
-                    content,
-                    parameters={"__array__": "categorical"},
-                )
+                return out
+            # RETURNED because 'index' has already been offset-corrected.
 
         elif isinstance(tpe, pyarrow.lib.StructType):
             assert tpe.num_buffers == 1
@@ -2342,293 +2341,162 @@ def popbuffers(array, tpe, buffers, length):
             child_arrays = []
             keys = []
             for i in range(tpe.num_fields):
-                content = popbuffers(
-                    None if array is None else array.field(tpe[i].name),
-                    tpe[i].type,
-                    buffers,
-                    length,
-                )
+                content = popbuffers(array.field(tpe[i].name), tpe[i].type, buffers)
                 if not tpe[i].nullable:
-                    # if isinstance(content, ak.layout.UnmaskedArray):
-                    assert isinstance(
-                        content, (ak.layout.UnmaskedArray, ak.layout.BitMaskedArray)
-                    )
                     content = content.content
-                    # else:
-                    #     raise ValueError(
-                    #         "Arrow struct field {0} is not nullable but content is\n\n{1}".format(
-                    #             repr(tpe[i].name), content
-                    #         )
-                    #         + ak._util.exception_suffix(__file__)
-                    #     )
                 child_arrays.append(content)
                 keys.append(tpe[i].name)
 
-            out = ak.layout.RecordArray(child_arrays, keys)
+            out = ak.layout.RecordArray(child_arrays, keys, length=len(array))
             if mask is not None:
                 mask = ak.layout.IndexU8(numpy.frombuffer(mask, dtype=np.uint8))
-                return ak.layout.BitMaskedArray(mask, out, True, length, True)
+                return ak.layout.BitMaskedArray(mask, out, True, len(out), True)
             else:
                 return ak.layout.UnmaskedArray(out)
+            # RETURNED because each field has already been offset-corrected.
 
         elif isinstance(tpe, pyarrow.lib.ListType):
             assert tpe.num_buffers == 2
             mask = buffers.pop(0)
             offsets = ak.layout.Index32(
-                numpy.frombuffer(buffers.pop(0), dtype=np.int32)[: length + 1]
-            )
-            content = popbuffers(
-                None if array is None else array.flatten(),
-                tpe.value_type,
-                buffers,
-                offsets[-1],
+                numpy.frombuffer(buffers.pop(0), dtype=np.int32)
             )
+            content = popbuffers(array.values, tpe.value_type, buffers)
             if not tpe.value_field.nullable:
-                # if isinstance(content, ak.layout.UnmaskedArray):
-                assert isinstance(
-                    content, (ak.layout.UnmaskedArray, ak.layout.BitMaskedArray)
-                )
                 content = content.content
-                # else:
-                #     raise ValueError(
-                #         "Arrow list item is not nullable but content is\n\n{0}".format(
-                #             content
-                #         )
-                #         + ak._util.exception_suffix(__file__)
-                #     )
 
             out = ak.layout.ListOffsetArray32(offsets, content)
-            if mask is not None:
-                mask = ak.layout.IndexU8(numpy.frombuffer(mask, dtype=np.uint8))
-                return ak.layout.BitMaskedArray(mask, out, True, length, True)
-            else:
-                return ak.layout.UnmaskedArray(out)
+            # No return yet!
 
         elif isinstance(tpe, pyarrow.lib.LargeListType):
             assert tpe.num_buffers == 2
             mask = buffers.pop(0)
             offsets = ak.layout.Index64(
-                numpy.frombuffer(buffers.pop(0), dtype=np.int64)[: length + 1]
-            )
-            content = popbuffers(
-                None if array is None else array.flatten(),
-                tpe.value_type,
-                buffers,
-                offsets[-1],
+                numpy.frombuffer(buffers.pop(0), dtype=np.int64)
             )
+            content = popbuffers(array.values, tpe.value_type, buffers)
             # https://issues.apache.org/jira/browse/ARROW-10930
             # if not tpe.value_field.nullable:
             if str(tpe).endswith(" not null>"):
-                # if isinstance(content, ak.layout.UnmaskedArray):
-                assert isinstance(
-                    content, (ak.layout.UnmaskedArray, ak.layout.BitMaskedArray)
-                )
                 content = content.content
-                # else:
-                #     raise ValueError(
-                #         "Arrow list item is not nullable but content is\n\n{0}".format(
-                #             content
-                #         )
-                #         + ak._util.exception_suffix(__file__)
-                #     )
 
             out = ak.layout.ListOffsetArray64(offsets, content)
-            if mask is not None:
-                mask = ak.layout.IndexU8(numpy.frombuffer(mask, dtype=np.uint8))
-                return ak.layout.BitMaskedArray(mask, out, True, length, True)
-            else:
-                return ak.layout.UnmaskedArray(out)
-
-        elif isinstance(tpe, pyarrow.lib.UnionType) and tpe.mode == "sparse":
-            assert tpe.num_buffers == 2
-            mask = buffers.pop(0)
-            tags = numpy.frombuffer(buffers.pop(0), dtype=np.int8)[:length]
-            index = numpy.arange(len(tags), dtype=np.int32)
+            # No return yet!
 
-            contents = []
-            for i in range(tpe.num_fields):
-                try:
-                    sublength = index[tags == i][-1] + 1
-                except IndexError:
-                    sublength = 0
-                contents.append(popbuffers(None, tpe[i].type, buffers, sublength))
-            for i in range(len(contents)):
-                these = index[tags == i]
-                if len(these) == 0:
-                    contents[i] = contents[i][0:0]
-                else:
-                    contents[i] = contents[i][: these[-1] + 1]
-
-            tags = ak.layout.Index8(tags)
-            index = ak.layout.Index32(index)
-            out = ak.layout.UnionArray8_32(tags, index, contents)
-
-            if mask is not None:
-                mask = ak.layout.IndexU8(numpy.frombuffer(mask, dtype=np.uint8))
-                return ak.layout.BitMaskedArray(mask, out, True, length, True)
+        elif isinstance(tpe, pyarrow.lib.UnionType):
+            if tpe.mode == "sparse":
+                assert tpe.num_buffers == 2
+            elif tpe.mode == "dense":
+                assert tpe.num_buffers == 3
             else:
-                return ak.layout.UnmaskedArray(out)
+                raise TypeError(
+                    "unrecognized Arrow union array mode: {0}".format(repr(tpe.mode))
+                    + ak._util.exception_suffix(__file__)
+                )
 
-        elif isinstance(tpe, pyarrow.lib.UnionType) and tpe.mode == "dense":
-            assert tpe.num_buffers == 3
             mask = buffers.pop(0)
-            tags = numpy.frombuffer(buffers.pop(0), dtype=np.int8)[:length]
-            index = numpy.frombuffer(buffers.pop(0), dtype=np.int32)[:length]
+            tags = numpy.frombuffer(buffers.pop(0), dtype=np.int8)
+            if tpe.mode == "sparse":
+                index = numpy.arange(len(tags), dtype=np.int32)
+            else:
+                index = numpy.frombuffer(buffers.pop(0), dtype=np.int32)
 
             contents = []
             for i in range(tpe.num_fields):
-                try:
-                    sublength = index[tags == i].max() + 1
-                except ValueError:
-                    sublength = 0
-                contents.append(popbuffers(None, tpe[i].type, buffers, sublength))
+                contents.append(popbuffers(array.field(i), tpe[i].type, buffers))
             for i in range(len(contents)):
                 these = index[tags == i]
                 if len(these) == 0:
                     contents[i] = contents[i][0:0]
+                elif tpe.mode == "sparse":
+                    contents[i] = contents[i][: these[-1] + 1]
                 else:
                     contents[i] = contents[i][: these.max() + 1]
             for i in range(len(contents)):
                 if not tpe[i].nullable:
-                    # if isinstance(contents[i], ak.layout.UnmaskedArray):
-                    assert isinstance(
-                        contents[i], (ak.layout.UnmaskedArray, ak.layout.BitMaskedArray)
-                    )
                     contents[i] = contents[i].content
-                    # else:
-                    #     raise ValueError(
-                    #         "Arrow union field {0} is not nullable but content is\n\n{1}".format(
-                    #             i, contents[i]
-                    #         )
-                    #         + ak._util.exception_suffix(__file__)
-                    #     )
 
             tags = ak.layout.Index8(tags)
             index = ak.layout.Index32(index)
             out = ak.layout.UnionArray8_32(tags, index, contents)
-            if mask is not None:
-                mask = ak.layout.IndexU8(numpy.frombuffer(mask, dtype=np.uint8))
-                return ak.layout.BitMaskedArray(mask, out, True, length, True)
-            else:
-                return ak.layout.UnmaskedArray(out)
+            # No return yet!
 
         elif tpe == pyarrow.string():
             assert tpe.num_buffers == 3
             mask = buffers.pop(0)
-
-            offsets = numpy.frombuffer(buffers.pop(0), dtype=np.int32)
-            contents = numpy.frombuffer(buffers.pop(0), dtype=np.uint8)
-
-            offsets = ak.layout.Index32(offsets)
-
-            contents = ak.layout.NumpyArray(contents)
+            offsets = ak.layout.Index32(
+                numpy.frombuffer(buffers.pop(0), dtype=np.int32)
+            )
+            contents = ak.layout.NumpyArray(
+                numpy.frombuffer(buffers.pop(0), dtype=np.uint8)
+            )
             contents.setparameter("__array__", "char")
-
-            awk_arr = ak.layout.ListOffsetArray32(offsets, contents)
-            awk_arr.setparameter("__array__", "string")
-
-            if mask is None:
-                return ak.layout.UnmaskedArray(awk_arr)
-            else:
-                awk_mask = ak.layout.IndexU8(numpy.frombuffer(mask, dtype=np.uint8))
-                return ak.layout.BitMaskedArray(
-                    awk_mask, awk_arr, True, len(offsets) - 1, True
-                )
+            out = ak.layout.ListOffsetArray32(offsets, contents)
+            out.setparameter("__array__", "string")
+            # No return yet!
 
         elif tpe == pyarrow.large_string():
             assert tpe.num_buffers == 3
             mask = buffers.pop(0)
-
-            offsets = numpy.frombuffer(buffers.pop(0), dtype=np.int64)
-            contents = numpy.frombuffer(buffers.pop(0), dtype=np.uint8)
-
-            offsets = ak.layout.Index64(offsets)
-
-            contents = ak.layout.NumpyArray(contents)
+            offsets = ak.layout.Index64(
+                numpy.frombuffer(buffers.pop(0), dtype=np.int64)
+            )
+            contents = ak.layout.NumpyArray(
+                numpy.frombuffer(buffers.pop(0), dtype=np.uint8)
+            )
             contents.setparameter("__array__", "char")
-
-            awk_arr = ak.layout.ListOffsetArray64(offsets, contents)
-            awk_arr.setparameter("__array__", "string")
-
-            if mask is None:
-                return ak.layout.UnmaskedArray(awk_arr)
-            else:
-                awk_mask = ak.layout.IndexU8(numpy.frombuffer(mask, dtype=np.uint8))
-                return ak.layout.BitMaskedArray(
-                    awk_mask, awk_arr, True, len(offsets) - 1, True
-                )
+            out = ak.layout.ListOffsetArray64(offsets, contents)
+            out.setparameter("__array__", "string")
+            # No return yet!
 
         elif tpe == pyarrow.binary():
             assert tpe.num_buffers == 3
             mask = buffers.pop(0)
-
-            offsets = numpy.frombuffer(buffers.pop(0), dtype=np.int32)
-            contents = numpy.frombuffer(buffers.pop(0), dtype=np.uint8)
-
-            offsets = ak.layout.Index32(offsets)
-
-            contents = ak.layout.NumpyArray(contents)
+            offsets = ak.layout.Index32(
+                numpy.frombuffer(buffers.pop(0), dtype=np.int32)
+            )
+            contents = ak.layout.NumpyArray(
+                numpy.frombuffer(buffers.pop(0), dtype=np.uint8)
+            )
             contents.setparameter("__array__", "byte")
-
-            awk_arr = ak.layout.ListOffsetArray32(offsets, contents)
-            awk_arr.setparameter("__array__", "bytestring")
-
-            if mask is None:
-                return ak.layout.UnmaskedArray(awk_arr)
-            else:
-                awk_mask = ak.layout.IndexU8(numpy.frombuffer(mask, dtype=np.uint8))
-                return ak.layout.BitMaskedArray(
-                    awk_mask, awk_arr, True, len(offsets) - 1, True
-                )
+            out = ak.layout.ListOffsetArray32(offsets, contents)
+            out.setparameter("__array__", "bytestring")
+            # No return yet!
 
         elif tpe == pyarrow.large_binary():
             assert tpe.num_buffers == 3
             mask = buffers.pop(0)
-
-            offsets = numpy.frombuffer(buffers.pop(0), dtype=np.int64)
-            contents = numpy.frombuffer(buffers.pop(0), dtype=np.uint8)
-
-            offsets = ak.layout.Index64(offsets)
-
-            contents = ak.layout.NumpyArray(contents)
+            offsets = ak.layout.Index64(
+                numpy.frombuffer(buffers.pop(0), dtype=np.int64)
+            )
+            contents = ak.layout.NumpyArray(
+                numpy.frombuffer(buffers.pop(0), dtype=np.uint8)
+            )
             contents.setparameter("__array__", "byte")
-
-            awk_arr = ak.layout.ListOffsetArray64(offsets, contents)
-            awk_arr.setparameter("__array__", "bytestring")
-
-            if mask is None:
-                return ak.layout.UnmaskedArray(awk_arr)
-            else:
-                awk_mask = ak.layout.IndexU8(numpy.frombuffer(mask, dtype=np.uint8))
-                return ak.layout.BitMaskedArray(
-                    awk_mask, awk_arr, True, len(offsets) - 1, True
-                )
+            out = ak.layout.ListOffsetArray64(offsets, contents)
+            out.setparameter("__array__", "bytestring")
+            # No return yet!
 
         elif tpe == pyarrow.bool_():
             assert tpe.num_buffers == 2
             mask = buffers.pop(0)
             data = buffers.pop(0)
-            out = numpy.frombuffer(data, dtype=np.uint8)
-            out = numpy.unpackbits(out).reshape(-1, 8)[:, ::-1].reshape(-1)
-            out = ak.layout.NumpyArray(out[:length].view(np.bool_))
-            if mask is not None:
-                awk_mask = ak.layout.IndexU8(numpy.frombuffer(mask, dtype=np.uint8))
-                mask = numpy.frombuffer(mask, dtype=np.uint8)
-                return ak.layout.BitMaskedArray(awk_mask, out, True, length, True)
-            else:
-                return ak.layout.UnmaskedArray(out)
+            as_bytes = (
+                numpy.unpackbits(numpy.frombuffer(data, dtype=np.uint8))
+                .reshape(-1, 8)[:, ::-1]
+                .reshape(-1)
+            )
+            out = ak.layout.NumpyArray(as_bytes.view(np.bool_))
+            # No return yet!
 
         elif isinstance(tpe, pyarrow.lib.DataType):
             assert tpe.num_buffers == 2
             mask = buffers.pop(0)
+            data = buffers.pop(0)
             out = ak.layout.NumpyArray(
-                numpy.frombuffer(buffers.pop(0), dtype=tpe.to_pandas_dtype())[:length]
+                numpy.frombuffer(data, dtype=tpe.to_pandas_dtype())
             )
-            if mask is not None:
-                mask = ak.layout.IndexU8(numpy.frombuffer(mask, dtype=np.uint8))
-                return ak.layout.BitMaskedArray(mask, out, True, length, True)
-            else:
-                return ak.layout.UnmaskedArray(out)
+            # No return yet!
 
         else:
             raise TypeError(
@@ -2636,10 +2504,24 @@ def popbuffers(array, tpe, buffers, length):
                 + ak._util.exception_suffix(__file__)
             )
 
+        # All 'no return yet' cases need to become option-type (even if the UnmaskedArray
+        # is just going to get stripped off in the recursive step that calls this one).
+        if mask is not None:
+            mask = ak.layout.IndexU8(numpy.frombuffer(mask, dtype=np.uint8))
+            out = ak.layout.BitMaskedArray(mask, out, True, len(out), True)
+        else:
+            out = ak.layout.UnmaskedArray(out)
+
+        # All 'no return yet' cases need to be corrected for pyarrow's 'offset'.
+        if array.offset == 0 and len(array) == len(out):
+            return out
+        else:
+            return out[array.offset : array.offset + len(array)]
+
     def handle_arrow(obj):
         if isinstance(obj, pyarrow.lib.Array):
             buffers = obj.buffers()
-            out = popbuffers(obj, obj.type, buffers, len(obj))
+            out = popbuffers(obj, obj.type, buffers)
             assert len(buffers) == 0
             if isinstance(out, ak.layout.UnmaskedArray):
                 return out.content


Test patch:
diff --git a/tests/test_0224-arrow-to-awkward.py b/tests/test_0224-arrow-to-awkward.py
--- a/tests/test_0224-arrow-to-awkward.py
+++ b/tests/test_0224-arrow-to-awkward.py
@@ -1774,3 +1774,508 @@ def test_arrow_coverage100_broken_unions():
 
     a = pyarrow.chunked_array([pyarrow.array([1.1, 2.2, 3.3, 4.4, 5.5])])
     assert ak.to_list(ak.from_arrow(a, highlevel=False)) == [1.1, 2.2, 3.3, 4.4, 5.5]
+
+
+def test_arrow_dictionary_from_indexedoptionarray():
+    assert ak.to_arrow(
+        ak.to_categorical(
+            ak.Array(["one", "one", None, "two", "one", None, "three", "one"])
+        )
+    ).to_pylist() == ["one", "one", None, "two", "one", None, "three", "one"]
+
+# NumpyArray in Awkward Arrays translate to their corresponding DataType Arrays in Arrow
+def test_nonzero_offset_fromarrow_NumpyArray_1():
+    boolarray = ak.layout.NumpyArray(
+        np.array([True, True, True, False, False, True, False, True, False, True])
+    )
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(boolarray)[5:], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(boolarray)[5:])
+
+
+def test_nonzero_offset_fromarrow_NumpyArray_2():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(content)[2:], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(content)[2:])
+
+def test_nonzero_offset_fromarrow_NumpyArray_3():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(content)[2:5], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(content)[2:5])
+
+def test_nonzero_offset_fromarrow_NumpyArray_4():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(content)[0:9:2], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(content)[0:9:2])
+
+def test_nonzero_offset_fromarrow_NumpyArray_5():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(content)[-2:10], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(content)[-2:10])
+
+def test_nonzero_offset_fromarrow_NumpyArray_6():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(content)[-3:3:-1], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(content)[-3:3:-1])
+
+# ListOffsetArrays in Awkward Arrays translate to ListArrays in Arrow
+def test_nonzero_offset_fromarrow_ListOffsetArray_1():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(listoffsetarray)[2:], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(listoffsetarray)[2:])
+
+def test_nonzero_offset_fromarrow_ListOffsetArray_2():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(listoffsetarray)[2:5], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(listoffsetarray)[2:5])
+
+def test_nonzero_offset_fromarrow_ListOffsetArray_3():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(listoffsetarray)[0:5:2], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(listoffsetarray)[0:5:2])
+
+def test_nonzero_offset_fromarrow_ListOffsetArray_4():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(listoffsetarray)[-3:3:-1], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(listoffsetarray)[-3:3:-1])
+
+# RegularArrays in Awkward Arrays translate to ListArrays in Arrow
+def test_nonzero_offset_fromarrow_RegularArray_1():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+
+    regulararray = ak.layout.RegularArray(listoffsetarray, 2, zeros_length=0)
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(regulararray)[2:], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(regulararray)[2:])
+
+def test_nonzero_offset_fromarrow_RegularArray_2():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+
+    regulararray = ak.layout.RegularArray(listoffsetarray, 2, zeros_length=0)
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(regulararray)[2:5], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(regulararray)[2:5])
+
+def test_nonzero_offset_fromarrow_RegularArray_3():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+
+    regulararray = ak.layout.RegularArray(listoffsetarray, 2, zeros_length=0)
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(regulararray)[0:5:2], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(regulararray)[0:5:2])
+
+def test_nonzero_offset_fromarrow_RegularArray_4():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+
+    regulararray = ak.layout.RegularArray(listoffsetarray, 2, zeros_length=0)
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(regulararray)[-3:3:-1], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(regulararray)[-3:3:-1])
+
+# RecordArrays in Awkward Arrays translate to Struct Arrays in Arrow
+def test_nonzero_offset_fromarrow_RecordArray_1():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+
+    content1 = ak.layout.NumpyArray(np.array([1, 2, 3, 4, 5]))
+    content2 = ak.layout.NumpyArray(
+        np.array([1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9])
+    )
+    offsets = ak.layout.Index32(np.array([0, 3, 3, 5, 6, 9]))
+    recordarray = ak.layout.RecordArray(
+        [content1, listoffsetarray, content2, content1],
+        keys=["one", "chonks", "2", "wonky"],
+    )
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(recordarray)[2:], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(recordarray)[2:])
+
+def test_nonzero_offset_fromarrow_RecordArray_2():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+
+    content1 = ak.layout.NumpyArray(np.array([1, 2, 3, 4, 5]))
+    content2 = ak.layout.NumpyArray(
+        np.array([1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9])
+    )
+    offsets = ak.layout.Index32(np.array([0, 3, 3, 5, 6, 9]))
+    recordarray = ak.layout.RecordArray(
+        [content1, listoffsetarray, content2, content1],
+        keys=["one", "chonks", "2", "wonky"],
+    )
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(recordarray)[2:5], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(recordarray)[2:5])
+
+def test_nonzero_offset_fromarrow_RecordArray_3():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+
+    content1 = ak.layout.NumpyArray(np.array([1, 2, 3, 4, 5]))
+    content2 = ak.layout.NumpyArray(
+        np.array([1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9])
+    )
+    offsets = ak.layout.Index32(np.array([0, 3, 3, 5, 6, 9]))
+    recordarray = ak.layout.RecordArray(
+        [content1, listoffsetarray, content2, content1],
+        keys=["one", "chonks", "2", "wonky"],
+    )
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(recordarray)[0:5:2], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(recordarray)[0:5:2])
+
+def test_nonzero_offset_fromarrow_RecordArray_4():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+
+    content1 = ak.layout.NumpyArray(np.array([1, 2, 3, 4, 5]))
+    content2 = ak.layout.NumpyArray(
+        np.array([1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9])
+    )
+    offsets = ak.layout.Index32(np.array([0, 3, 3, 5, 6, 9]))
+    recordarray = ak.layout.RecordArray(
+        [content1, listoffsetarray, content2, content1],
+        keys=["one", "chonks", "2", "wonky"],
+    )
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(recordarray)[-3:3:-1], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(recordarray)[-3:3:-1])
+
+def test_nonzero_offset_fromarrow_RecordArray_4():
+    content = ak.layout.NumpyArray(
+        np.array([0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10])
+    )
+    offsets = ak.layout.Index64(np.array([0, 3, 3, 5, 6, 10, 10]))
+    listoffsetarray = ak.layout.ListOffsetArray64(offsets, content)
+
+    content1 = ak.layout.NumpyArray(np.array([1, 2, 3, 4, 5]))
+    content2 = ak.layout.NumpyArray(
+        np.array([1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9])
+    )
+    offsets = ak.layout.Index32(np.array([0, 3, 3, 5, 6, 9]))
+    recordarray = ak.layout.RecordArray(
+        [content1, listoffsetarray, content2, content1],
+        keys=["one", "chonks", "2", "wonky"],
+    )
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(recordarray)[-3:3:-1], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(recordarray)[-3:3:-1])
+
+def test_nonzero_offset_fromarrow_UnionArray_1():
+    content0 = ak.Array([[1.1, 2.2, 3.3], [], [4.4, 5.5]]).layout
+    content = ak.Array(
+        ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
+    ).layout
+    tags = ak.layout.Index8(np.array([1, 1, 0, 0, 1, 0, 1, 1], dtype=np.int8))
+    index = ak.layout.Index32(np.array([0, 1, 0, 1, 2, 2, 4, 3], dtype=np.int32))
+    array = ak.layout.UnionArray8_32(tags, index, [content0, content])
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(array)[2:], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(array)[2:])
+
+def test_nonzero_offset_fromarrow_UnionArray_2():
+    content0 = ak.Array([[1.1, 2.2, 3.3], [], [4.4, 5.5]]).layout
+    content = ak.Array(
+        ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
+    ).layout
+    tags = ak.layout.Index8(np.array([1, 1, 0, 0, 1, 0, 1, 1], dtype=np.int8))
+    index = ak.layout.Index32(np.array([0, 1, 0, 1, 2, 2, 4, 3], dtype=np.int32))
+    array = ak.layout.UnionArray8_32(tags, index, [content0, content])
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(array)[2:5], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(array)[2:5])
+
+def test_nonzero_offset_fromarrow_UnionArray_3():
+    content0 = ak.Array([[1.1, 2.2, 3.3], [], [4.4, 5.5]]).layout
+    content = ak.Array(
+        ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
+    ).layout
+    tags = ak.layout.Index8(np.array([1, 1, 0, 0, 1, 0, 1, 1], dtype=np.int8))
+    index = ak.layout.Index32(np.array([0, 1, 0, 1, 2, 2, 4, 3], dtype=np.int32))
+    array = ak.layout.UnionArray8_32(tags, index, [content0, content])
+    assert ak.to_list(
+        ak.from_arrow(ak.to_arrow(array)[0:5:1], highlevel=False)
+    ) == pyarrow.Array.to_pylist(ak.to_arrow(array)[0:5:1])
+
+def test_nonzero_offset_fromarrow_ArrowDictionaryArray_1():
+    a = pyarrow.DictionaryArray.from_arrays(
+        pyarrow.array([0, 0, 2, 2, 1, 0, 2, 1, 1]),
+        pyarrow.array(["one", None, "three"]),
+    )
+    assert ak.to_list(ak.from_arrow(a[2:], highlevel=False)) == [
+        "three",
+        "three",
+        None,
+        "one",
+        "three",
+        None,
+        None,
+    ]
+
+def test_nonzero_offset_fromarrow_ArrowDictionaryArray_2():
+    a = pyarrow.DictionaryArray.from_arrays(
+        pyarrow.array([0, 0, 2, 2, 1, 0, 2, 1, 1]),
+        pyarrow.array(["one", None, "three"]),
+    )
+    assert ak.to_list(ak.from_arrow(a[2:5], highlevel=False)) == [
+        "three",
+        "three",
+        None
+    ]
+
+def test_nonzero_offset_fromarrow_ArrowDictionaryArray_3():
+    a = pyarrow.DictionaryArray.from_arrays(
+        pyarrow.array([0, 0, 2, 2, 1, 0, 2, 1, 1]),
+        pyarrow.array(["one", None, "three"]),
+    )
+    assert ak.to_list(ak.from_arrow(a[0:8:2], highlevel=False)) == [
+        "one",
+        "three",
+        None,
+        "three",
+    ]
+
+def test_nonzero_offset_fromarrow_ArrowDictionaryArray_4():
+    a = pyarrow.DictionaryArray.from_arrays(
+        pyarrow.array([0, 0, 2, 2, 1, 0, 2, 1, 1]),
+        pyarrow.array(["one", None, "three"]),
+    )
+    assert ak.to_list(ak.from_arrow(a[-3:3:-1], highlevel=False)) == [
+        "three",
+        "one",
+        None
+    ]
+
+def test_nonzero_offset_fromarrow_ArrowRecordBatch_1():
+    a = pyarrow.RecordBatch.from_arrays(
+        [
+            pyarrow.array([1.1, 2.2, 3.3, 4.4, 5.5]),
+            pyarrow.array([[1, 2, 3], [], [], [4, 5], [6]]),
+        ],
+        ["a", "b"],
+    )
+    assert ak.to_list(ak.from_arrow(a[0], highlevel=False)) == a[0].to_pylist()
+
+def test_nonzero_offset_fromarrow_ArrowRecordBatch_2():
+    a = pyarrow.RecordBatch.from_arrays(
+        [
+            pyarrow.array([1.1, 2.2, 3.3, 4.4, 5.5]),
+            pyarrow.array([[1, 2, 3], [], [], [4, 5], [6]]),
+        ],
+        ["a", "b"],
+    )
+    assert ak.to_list(ak.from_arrow(a[2:], highlevel=False)) == [
+        {'a': 3.3, 'b': []}, 
+        {'a': 4.4, 'b': [4, 5]}, 
+        {'a': 5.5, 'b': [6]}
+    ]
+
+def test_nonzero_offset_fromarrow_ArrowRecordBatch_3():
+    a = pyarrow.RecordBatch.from_arrays(
+        [
+            pyarrow.array([1.1, 2.2, 3.3, 4.4, 5.5]),
+            pyarrow.array([[1, 2, 3], [], [], [4, 5], [6]]),
+        ],
+        ["a", "b"],
+    )
+    assert ak.to_list(ak.from_arrow(a[2:5], highlevel=False)) == [
+        {'a': 3.3, 'b': []}, 
+        {'a': 4.4, 'b': [4, 5]}, 
+        {'a': 5.5, 'b': [6]}
+    ]
+
+def test_nonzero_offset_fromarrow_ArrowRecordBatch_4():
+    a = pyarrow.RecordBatch.from_arrays(
+        [
+            pyarrow.array([1.1, 2.2, 3.3, 4.4, 5.5]),
+            pyarrow.array([[1, 2, 3], [], [], [4, 5], [6]]),
+        ],
+        ["a", "b"],
+    )
+    assert ak.to_list(ak.from_arrow(a[0:5:2], highlevel=False)) == [
+        {'a': 1.1, 'b': [1, 2, 3]}, 
+        {'a': 3.3, 'b': []}, 
+        {'a': 5.5, 'b': [6]}
+    ]
+
+def test_nonzero_offset_fromarrow_ArrowRecordBatch_4():
+    a = pyarrow.RecordBatch.from_arrays(
+        [
+            pyarrow.array([1.1, 2.2, 3.3, 4.4, 5.5]),
+            pyarrow.array([[1, 2, 3], [], [], [4, 5], [6]]),
+        ],
+        ["a", "b"],
+    )
+    assert ak.to_list(ak.from_arrow(a[-2:0:-1], highlevel=False)) == [
+        {'a': 4.4, 'b': [4, 5]}, 
+        {'a': 3.3, 'b': []}, 
+        {'a': 2.2, 'b': []}
+    ]
+
+def test_nonzero_offset_fromarrow_ArrowTable_1():
+    a = pyarrow.Table.from_batches(
+        [
+            pyarrow.RecordBatch.from_arrays(
+                [
+                    pyarrow.array([1.1, 2.2, 3.3, None, 5.5]),
+                    pyarrow.array([[1, 2, 3], [], [4, 5], [None], [6]]),
+                    pyarrow.array(
+                        [
+                            {"x": 1, "y": 1.1},
+                            {"x": 2, "y": 2.2},
+                            {"x": 3, "y": 3.3},
+                            {"x": 4, "y": None},
+                            {"x": 5, "y": 5.5},
+                        ]
+                    ),
+                    pyarrow.array(
+                        [
+                            {"x": 1, "y": 1.1},
+                            None,
+                            None,
+                            {"x": 4, "y": None},
+                            {"x": 5, "y": 5.5},
+                        ]
+                    ),
+                    pyarrow.array(
+                        [
+                            [
+                                {"x": 1, "y": 1.1},
+                                {"x": 2, "y": 2.2},
+                                {"x": 3, "y": 3.3},
+                            ],
+                            [],
+                            [{"x": 4, "y": None}, {"x": 5, "y": 5.5}],
+                            [None],
+                            [{"x": 6, "y": 6.6}],
+                        ]
+                    ),
+                ],
+                ["a", "b", "c", "d", "e"],
+            ),
+            pyarrow.RecordBatch.from_arrays(
+                [
+                    pyarrow.array([1.1, 2.2, 3.3, None, 5.5]),
+                    pyarrow.array([[1, 2, 3], [], [4, 5], [None], [6]]),
+                    pyarrow.array(
+                        [
+                            {"x": 1, "y": 1.1},
+                            {"x": 2, "y": 2.2},
+                            {"x": 3, "y": 3.3},
+                            {"x": 4, "y": None},
+                            {"x": 5, "y": 5.5},
+                        ]
+                    ),
+                    pyarrow.array(
+                        [
+                            {"x": 1, "y": 1.1},
+                            None,
+                            None,
+                            {"x": 4, "y": None},
+                            {"x": 5, "y": 5.5},
+                        ]
+                    ),
+                    pyarrow.array(
+                        [
+                            [
+                                {"x": 1, "y": 1.1},
+                                {"x": 2, "y": 2.2},
+                                {"x": 3, "y": 3.3},
+                            ],
+                            [],
+                            [{"x": 4, "y": None}, {"x": 5, "y": 5.5}],
+                            [None],
+                            [{"x": 6, "y": 6.6}],
+                        ]
+                    ),
+                ],
+                ["a", "b", "c", "d", "e"],
+            ),
+        ]
+    )
+    assert ak.to_list(ak.from_arrow(a[0:5:2], highlevel=False)) == [
+        {
+            "a": 1.1,
+            "b": [1, 2, 3],
+            "c": {"x": 1, "y": 1.1},
+            "d": {"x": 1, "y": 1.1},
+            "e": [{"x": 1, "y": 1.1}, {"x": 2, "y": 2.2}, {"x": 3, "y": 3.3}],
+        },
+        {
+            "a": 3.3,
+            "b": [4, 5],
+            "c": {"x": 3, "y": 3.3},
+            "d": None,
+            "e": [{"x": 4, "y": None}, {"x": 5, "y": 5.5}],
+        },
+        {
+            "a": 5.5,
+            "b": [6],
+            "c": {"x": 5, "y": 5.5},
+            "d": {"x": 5, "y": 5.5},
+            "e": [{"x": 6, "y": 6.6}],
+        },
+    ]