@@ -171,7 +171,7 @@ def __init__(
171
171
# a consistent NaN value (and we can use `dtype.na_value is np.nan`)
172
172
na_value = np .nan
173
173
elif na_value is not libmissing .NA :
174
- raise ValueError ("'na_value' must be np.nan or pd.NA, got {na_value}" )
174
+ raise ValueError (f "'na_value' must be np.nan or pd.NA, got { na_value } " )
175
175
176
176
self .storage = storage
177
177
self ._na_value = na_value
@@ -284,6 +284,35 @@ def construct_array_type( # type: ignore[override]
284
284
else :
285
285
return ArrowStringArrayNumpySemantics
286
286
287
+ def _get_common_dtype (self , dtypes : list [DtypeObj ]) -> DtypeObj | None :
288
+ allowed_numpy_kinds = {"S" , "U" }
289
+
290
+ storages = set ()
291
+ na_values = set ()
292
+
293
+ for dtype in dtypes :
294
+ if isinstance (dtype , StringDtype ):
295
+ storages .add (dtype .storage )
296
+ na_values .add (dtype .na_value )
297
+ elif isinstance (dtype , np .dtype ) and dtype .kind in allowed_numpy_kinds :
298
+ continue
299
+ else :
300
+ return None
301
+
302
+ if len (storages ) == 2 :
303
+ # if both python and pyarrow storage -> priority to pyarrow
304
+ storage = "pyarrow"
305
+ else :
306
+ storage = next (iter (storages ))
307
+
308
+ if len (na_values ) == 2 :
309
+ # if both NaN and NA -> priority to NA
310
+ na_value = libmissing .NA
311
+ else :
312
+ na_value = next (iter (na_values ))
313
+
314
+ return StringDtype (storage = storage , na_value = na_value )
315
+
287
316
def __from_arrow__ (
288
317
self , array : pyarrow .Array | pyarrow .ChunkedArray
289
318
) -> BaseStringArray :
0 commit comments