You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Python] How to Test Lists for Existence of an Element #47118
Describe the usage question you have. Please include as many useful details as possible.
I'm trying to use pyarrow to query arrow files that have a column named tags of type list<string>. The most common query is "does the list contain this string?" but I'm not able to figure out how to write this query. As an example:
From here, I would expect that I should be able to do something like table.filter(pc.list_has_element(pc.field('tags')), 'foo') but there is not a function list_has_element, and there doesn't seem to be anything like it. I'm wonderful if there is something that I'm missing, some part of the API that is used to lift other compute functions over lists.
Related Issues:
I see in #45167 that it's not currently possible to check that lists are equal, so maybe lists are a less developed part of the library.
Thanks. I had not come across #33295 in my searching. I'll close this issue since the question was answered by your response, and #33295 is the right place to track any work that gets done on this.
I spent an hour trying to make headway on this but didn't get anywhere. If anyone tries to do this in the future, here is one difficulty to be aware of. The arrow library is currently able to express type signatures where the output type refers to something from an input type. For example, list_element has a type like List a -> IntLike -> a, and this type can be expressed because there is a function ListValuesType that can be used as the OutputType, and its meaning is "take the first input type, expect it to be a list, and then get the element type from it". But there is no equivalent for an input type. So it is not currently possible to express a type like List a -> a -> Bool, which is needed for list_contains.
I worked on an alternative to what @amoeba mentioned in this comment. It is up to an order of magnitude faster, and needs next to no extra memory. A native kernel is still faster again: list_contains is implemented in #51611 (GH-33295).
importmathfromtypingimportAnyimportpyarrowaspaimportpyarrow.computeaspcBLOCK_VALUES=1<<21deflist_contains(array: pa.ChunkedArray, item: Any) ->pa.ChunkedArray:
"""Whether each list holds `item`, or a null element if `item` is None."""# Per chunk, as combining them can overflow 32-bit list offsets.returnpa.chunked_array([_list_contains(arr, item) forarrinarray.chunks], pa.bool_())
def_list_contains(arr: pa.ListArray, item: Any) ->pa.BooleanArray:
# Zero-copy slices of whole lists, of about 2M values each, keep the intermediates# small enough to stay in cache.n_lists=len(arr)
n_values=arr.offsets[-1].as_py() -arr.offsets[0].as_py()
step=max(1, n_lists*BLOCK_VALUES//max(1, n_values))
blocks= [_list_contains_block(arr.slice(i, step), item) foriinrange(0, n_lists, step)]
returnpa.concat_arrays(blocks) ifblockselsepa.array([], pa.bool_())
def_list_contains_block(arr: pa.ListArray, item: Any) ->pa.BooleanArray:
offsets=arr.offsetsfirst=offsets[0].as_py()
values=arr.values.slice(first, offsets[-1].as_py() -first)
iffirst:
# A plain `int` would widen int32 offsets to int64, which is slower to `take`.offsets=pc.subtract(offsets, pa.scalar(first, offsets.type))
ends, starts=offsets.slice(1), offsets.slice(0, len(arr))
# Like Polars, and unlike `pc.equal`, NaN matches NaN.ifitemisNone:
hits=pc.is_null(values)
elifisinstance(item, float) andmath.isnan(item):
hits=pc.is_nan(values)
else:
hits=pc.equal(values, pa.scalar(item))
# `running[i]` counts the matches before flattened position `i`, modulo 2^k. That is# exact within lists shorter than 2^k, so counting in the narrowest type fitting the# longest list keeps the only large intermediates small.longest=pc.max(pc.subtract(ends, starts)).as_py() or0count_type= (
pa.uint8() iflongest<1<<8elsepa.uint16() iflongest<1<<16elsepa.uint32() iflongest<1<<32elsepa.uint64()
)
hits=pa.concat_arrays([pa.array([False]), hits.fill_null(False)])
running=pc.cumulative_sum(hits.cast(count_type))
in_list=pc.subtract(running.take(ends), running.take(starts))
found=pc.not_equal(in_list, pa.scalar(0, count_type))
returnfoundifarr.null_count==0elsepc.if_else(arr.is_valid(), found, None)
Activity
[-]How to Test Lists for Existence of an Element[/-][+][Python] How to Test Lists for Existence of an Element[/+]amoeba commentedon Jul 15, 2025
Hi @andrewthad, thanks for the question.
I think #33295 and #32045 are related and that this operation isn't supported.
Until that's made to work (PRs welcome), you could work around it by flattening first, though it's not efficient,
andrewthad commentedon Jul 16, 2025
Thanks. I had not come across #33295 in my searching. I'll close this issue since the question was answered by your response, and #33295 is the right place to track any work that gets done on this.
andrewthad commentedon Jul 16, 2025
I spent an hour trying to make headway on this but didn't get anywhere. If anyone tries to do this in the future, here is one difficulty to be aware of. The arrow library is currently able to express type signatures where the output type refers to something from an input type. For example,
list_elementhas a type likeList a -> IntLike -> a, and this type can be expressed because there is a functionListValuesTypethat can be used as theOutputType, and its meaning is "take the first input type, expect it to be a list, and then get the element type from it". But there is no equivalent for an input type. So it is not currently possible to express a type likeList a -> a -> Bool, which is needed forlist_contains.jonasdedden commentedon Sep 28, 2026
I worked on an alternative to what @amoeba mentioned in this comment. It is up to an order of magnitude faster, and needs next to no extra memory. A native kernel is still faster again:
list_containsis implemented in #51611 (GH-33295).list_contains(#51611)Setup
int64values in onelist<int64>chunk; the item is anint64.is_inmatches back to lists. I usedlist_parent_indices→filter→unique→is_inover the row numbers.