[ArrowStringArray] API: StringDtype parameterized by storage (python or pyarrow)
State: closed

continuation of #36142

todo:

- [x] during the merge I removed the import gymnastics from `pandas/core/arrays/string_arrow.py`. We added since `ArrowStringDtype` should always be available. I think this is no longer the case and we want to move the import checks to `StringDtype`. I expect envs without pyarrow to fail for now.
- [x] `test_arrow_roundtrip` is failing. `StringDtype.__from_arrow__` needs updating.
- [x] address @jorisvandenbossche comments https://github.com/pandas-dev/pandas/pull/36142#discussion_r484213504 and https://github.com/pandas-dev/pandas/pull/36142#discussion_r484213103
- [x] some other test failures `AssertionError: assert 'unknown-array' == 'string'`

Labels:
name: API Design
name: Strings
description: String extension data type and string data

Comments:
@simonjayhawkins do you have time to further update this PR?
> @simonjayhawkins do you have time to further update this PR?

I really need to walk away from typing activity for a few weeks, it's a time sink (and depressing.) we should discuss in the dev-meeting later today.

@simonjayhawkins a friendly ping here
> @simonjayhawkins a friendly ping here

Thanks @jorisvandenbossche 

merge master and pushed changes. not ready for review.

these failures to fix...

```
=================================================== short test summary info ===================================================
FAILED pandas/tests/arrays/string_/test_string.py::test_arrow_roundtrip[pyarrow] - AttributeError: 'StringArray' object has ...
FAILED pandas/tests/arrays/string_/test_string.py::test_arrow_roundtrip[python] - AttributeError: 'StringArray' object has n...
FAILED pandas/tests/dtypes/test_inference.py::TestTypeInference::test_string_dtype[data0-False-Series] - AssertionError: ass...
FAILED pandas/tests/dtypes/test_inference.py::TestTypeInference::test_string_dtype[data1-False-Series] - AssertionError: ass...
FAILED pandas/tests/dtypes/test_inference.py::TestTypeInference::test_string_dtype[data0-True-array] - AssertionError: asser...
FAILED pandas/tests/dtypes/test_inference.py::TestTypeInference::test_string_dtype[data0-False-array] - AssertionError: asse...
FAILED pandas/tests/dtypes/test_inference.py::TestTypeInference::test_string_dtype[data1-True-array] - AssertionError: asser...
FAILED pandas/tests/dtypes/test_inference.py::TestTypeInference::test_string_dtype[data1-False-array] - AssertionError: asse...
FAILED pandas/tests/dtypes/test_inference.py::TestTypeInference::test_string_dtype[data1-True-Series] - AssertionError: asse...
FAILED pandas/tests/dtypes/test_inference.py::TestTypeInference::test_string_dtype[data0-True-Series] - AssertionError: asse...
FAILED pandas/tests/io/test_parquet.py::TestParquetPyArrow::test_additional_extension_arrays - AttributeError: 'StringArray'...
FAILED pandas/tests/io/test_parquet.py::TestParquetPyArrow::test_use_nullable_dtypes - AttributeError: 'StringArray' object ...
============== 13 failed, 157195 passed, 3567 skipped, 992 xfailed, 6 xpassed, 219 warnings in 465.64s (0:07:45) ==============
```


Looking at the error messages, you need to update the `__from_arrow__` method for StringDtype. It hardcodes the use of `StringArray._concat_same_type`, but the class should depend on the dtype's backend
@jorisvandenbossche The fails in https://github.com/pandas-dev/pandas/pull/39908#issuecomment-808155116 are fixed.

we now have these failures...

```
=================================================== short test summary info ===================================================
FAILED pandas/tests/arrays/string_/test_string.py::test_string_methods[string-input0-method0] - AssertionError: assert 'stri...
FAILED pandas/tests/arrays/string_/test_string.py::test_string_methods[string-input1-method1] - AssertionError: assert 'stri...
FAILED pandas/tests/arrays/string_/test_string.py::test_string_methods[string-input2-method2] - AssertionError: assert 'stri...
FAILED pandas/tests/extension/test_floating.py::TestCasting::test_astype_string[string[pyarrow]-Float32Dtype] - AssertionErr...
FAILED pandas/tests/extension/test_string.py::TestCasting::test_astype_string[string[pyarrow]-python-False] - TypeError: Can...
================================= 6 failed, 25 passed, 29311 deselected, 2 warnings in 6.82s ==================================
```

i need to make a start on the str accessor (separate PR). so will do that before investigating the first three errors

the last 2 will be either xfailed or disappear for now if we remove `string[pyarrow]` from the fixture and add in a follow-on instead. https://github.com/pandas-dev/pandas/pull/39908#discussion_r602883047

EDIT: removed fail that I always get locally that is not related.
> the last 2 will be either xfailed or disappear for now if we remove `string[pyarrow]` from the fixture and add in a follow-on instead. [#39908 (comment)](https://github.com/pandas-dev/pandas/pull/39908#discussion_r602883047)

have opened #40679 as a pre-cursor to remove these changes from this PR
@simonjayhawkins there are a few import related comments in my previous review round (https://github.com/pandas-dev/pandas/pull/39908#pullrequestreview-601825543) that would be good to look at.
> @simonjayhawkins there are a few import related comments in my previous review round ([#39908 (review)](https://github.com/pandas-dev/pandas/pull/39908#pullrequestreview-601825543)) that would be good to look at.

sure. trying to get the diff down before marking as ready for review.

there is still some more test PRs in the pipeline before this
@simonjayhawkins what do you still want to do as a precursor (except for the open PRs) before this PR is ready to review again?
This is probably the last PR since it makes the dtype public (which hopefully we can do for 1.3)

There's the todo lists in https://github.com/pandas-dev/pandas/issues/35169#issuecomment-712883859 and https://github.com/pandas-dev/pandas/issues/35169#issuecomment-727217462

and also

Str.split - open PR #41085 (with perf issues to be resolved and rsplit to be added)
override isin in ArrowStringArray - wip. will open PR hopefully today/tomorrow
Benchmarking/Testing - include StringArray/ArrowStringArray in all string benchmarks/tests - ongoing
PyArrow 4.0 - String transforms - replace_substring/replace_substring_regex- will open PR after #41217 is merged (probably similar code structure)
PyArrow 4.0 - String extraction - extract_regex
Other - Can regex be used for other methods?

update df.convert_dtypes() in this PR to account to global options
add documentation to this PR - https://github.com/pandas-dev/pandas/pull/40747#issue-607997917

Maybe open issue for removal of hasattr/or add coments
Memory benchmark?
Use ascii versions of functions where appropriate
Allow array to define additional methods - wip - isprintable, isascii, index_in
Fast conversions using cast and strptime

may close #41018 and do to_numpy/as_type seperately. the changes to to_numpy are tested by the path taken through \_\_from__arrow__.




@jorisvandenbossche I'm not the original author of this PR, but as I'm working on this, i've not commented as a reviewer.

as we make progress on the ArrowStringArray implementation, and see some performance benefits, I think @jreback comment https://github.com/pandas-dev/pandas/issues/35169#issuecomment-655151869 should perhaps be considered as an alternative.
here are some timings (for master) if alternative is to be considered https://github.com/pandas-dev/pandas/pull/39908#issuecomment-830625427

```
  0.00%] ·· Benchmarking existing-py_home_simon_miniconda3_envs_pandas-dev_bin_python
[  1.39%] ··· strings.Methods.time_center                                                                                                                 ok
[  1.39%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        26.4±0ms 
                  string      23.8±0ms 
               arrow_string   33.5±0ms 
              ============== ==========

[  2.78%] ··· strings.Methods.time_count                                                                                                                  ok
[  2.78%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        32.9±0ms 
                  string      20.2±0ms 
               arrow_string   22.9±0ms 
              ============== ==========

[  4.17%] ··· strings.Methods.time_endswith                                                                                                               ok
[  4.17%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        20.3±0ms 
                  string      15.1±0ms 
               arrow_string   6.82±0ms 
              ============== ==========

[  5.56%] ··· strings.Methods.time_extract                                                                                                                ok
[  5.56%] ··· ============== =========
                  dtype               
              -------------- ---------
                   str        108±0ms 
                  string      133±0ms 
               arrow_string   348±0ms 
              ============== =========

[  6.94%] ··· strings.Methods.time_find                                                                                                                   ok
[  6.94%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        32.3±0ms 
                  string      20.9±0ms 
               arrow_string   24.3±0ms 
              ============== ==========

[  8.33%] ··· strings.Methods.time_findall                                                                                                                ok
[  8.33%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        67.0±0ms 
                  string      64.2±0ms 
               arrow_string   64.7±0ms 
              ============== ==========

[  9.72%] ··· strings.Methods.time_get                                                                                                                    ok
[  9.72%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        25.6±0ms 
                  string      23.0±0ms 
               arrow_string   26.4±0ms 
              ============== ==========

[ 11.11%] ··· strings.Methods.time_isalnum                                                                                                                ok
[ 11.11%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        18.3±0ms 
                  string      12.4±0ms 
               arrow_string   2.30±0ms 
              ============== ==========

[ 12.50%] ··· strings.Methods.time_isalpha                                                                                                                ok
[ 12.50%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        16.9±0ms 
                  string      10.3±0ms 
               arrow_string   2.53±0ms 
              ============== ==========

[ 13.89%] ··· strings.Methods.time_isdecimal                                                                                                              ok
[ 13.89%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        16.6±0ms 
                  string      8.83±0ms 
               arrow_string   1.83±0ms 
              ============== ==========

[ 15.28%] ··· strings.Methods.time_isdigit                                                                                                                ok
[ 15.28%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        15.7±0ms 
                  string      9.06±0ms 
               arrow_string   1.80±0ms 
              ============== ==========

[ 16.67%] ··· strings.Methods.time_islower                                                                                                                ok
[ 16.67%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        17.2±0ms 
                  string      10.8±0ms 
               arrow_string   3.57±0ms 
              ============== ==========

[ 18.06%] ··· strings.Methods.time_isnumeric                                                                                                              ok
[ 18.06%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        15.0±0ms 
                  string      9.15±0ms 
               arrow_string   1.71±0ms 
              ============== ==========

[ 19.44%] ··· strings.Methods.time_isspace                                                                                                                ok
[ 19.44%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        14.3±0ms 
                  string      9.00±0ms 
               arrow_string   1.67±0ms 
              ============== ==========

[ 20.83%] ··· strings.Methods.time_istitle                                                                                                                ok
[ 20.83%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        16.0±0ms 
                  string      10.9±0ms 
               arrow_string   3.37±0ms 
              ============== ==========

[ 22.22%] ··· strings.Methods.time_isupper                                                                                                                ok
[ 22.22%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        17.2±0ms 
                  string      10.0±0ms 
               arrow_string   3.65±0ms 
              ============== ==========

[ 23.61%] ··· strings.Methods.time_join                                                                                                                   ok
[ 23.61%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        30.4±0ms 
                  string      25.9±0ms 
               arrow_string   29.3±0ms 
              ============== ==========

[ 25.00%] ··· strings.Methods.time_len                                                                                                                    ok
[ 25.00%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        20.3±0ms 
                  string      7.10±0ms 
               arrow_string   1.99±0ms 
              ============== ==========

[ 26.39%] ··· strings.Methods.time_lower                                                                                                                  ok
[ 26.39%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        18.4±0ms 
                  string      11.9±0ms 
               arrow_string   1.91±0ms 
              ============== ==========

[ 27.78%] ··· strings.Methods.time_lstrip                                                                                                                 ok
[ 27.78%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        17.5±0ms 
                  string      13.0±0ms 
               arrow_string   1.45±0ms 
              ============== ==========

[ 29.17%] ··· strings.Methods.time_match                                                                                                                  ok
[ 29.17%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        28.1±0ms 
                  string      23.5±0ms 
               arrow_string   2.37±0ms 
              ============== ==========

[ 30.56%] ··· strings.Methods.time_normalize                                                                                                              ok
[ 30.56%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        33.3±0ms 
                  string      16.2±0ms 
               arrow_string   20.6±0ms 
              ============== ==========

[ 31.94%] ··· strings.Methods.time_pad                                                                                                                    ok
[ 31.94%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        28.4±0ms 
                  string      22.7±0ms 
               arrow_string   30.2±0ms 
              ============== ==========

[ 33.33%] ··· strings.Methods.time_partition                                                                                                              ok
[ 33.33%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        63.8±0ms 
                  string      46.4±0ms 
               arrow_string   54.5±0ms 
              ============== ==========

[ 34.72%] ··· strings.Methods.time_replace                                                                                                                ok
[ 34.72%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        21.2±0ms 
                  string      16.3±0ms 
               arrow_string   22.5±0ms 
              ============== ==========

[ 36.11%] ··· strings.Methods.time_rfind                                                                                                                  ok
[ 36.11%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        32.8±0ms 
                  string      21.1±0ms 
               arrow_string   23.3±0ms 
              ============== ==========

[ 37.50%] ··· strings.Methods.time_rpartition                                                                                                             ok
[ 37.50%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        54.5±0ms 
                  string      46.3±0ms 
               arrow_string   53.9±0ms 
              ============== ==========

[ 38.89%] ··· strings.Methods.time_rstrip                                                                                                                 ok
[ 38.89%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        17.9±0ms 
                  string      13.6±0ms 
               arrow_string   1.46±0ms 
              ============== ==========

[ 40.28%] ··· strings.Methods.time_slice                                                                                                                  ok
[ 40.28%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        21.6±0ms 
                  string      16.4±0ms 
               arrow_string   20.8±0ms 
              ============== ==========

[ 41.67%] ··· strings.Methods.time_startswith                                                                                                             ok
[ 41.67%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        22.1±0ms 
                  string      15.2±0ms 
               arrow_string   2.71±0ms 
              ============== ==========

[ 43.06%] ··· strings.Methods.time_strip                                                                                                                  ok
[ 43.06%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        19.4±0ms 
                  string      13.2±0ms 
               arrow_string   1.68±0ms 
              ============== ==========

[ 44.44%] ··· strings.Methods.time_title                                                                                                                  ok
[ 44.44%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        24.5±0ms 
                  string      17.2±0ms 
               arrow_string   22.1±0ms 
              ============== ==========

[ 45.83%] ··· strings.Methods.time_translate                                                                                                              ok
[ 45.83%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        54.8±0ms 
                  string      46.4±0ms 
               arrow_string   50.6±0ms 
              ============== ==========

[ 47.22%] ··· strings.Methods.time_upper                                                                                                                  ok
[ 47.22%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        20.5±0ms 
                  string      16.4±0ms 
               arrow_string   1.83±0ms 
              ============== ==========

[ 48.61%] ··· strings.Methods.time_wrap                                                                                                                   ok
[ 48.61%] ··· ============== =========
                  dtype               
              -------------- ---------
                   str        280±0ms 
                  string      272±0ms 
               arrow_string   277±0ms 
              ============== =========

[ 50.00%] ··· strings.Methods.time_zfill                                                                                                                  ok
[ 50.00%] ··· ============== ==========
                  dtype                
              -------------- ----------
                   str        19.1±0ms 
                  string      13.7±0ms 
               arrow_string   18.3±0ms 
              ============== ==========
```

so extract seems the worst in perf terms. pyarrow 4.0.0 has `extract_regex` but no looked at that yet.

other things to consider

- Categorical backed by ArrowStringArray
- pyarrow does not support Python 3.9 (according to docs https://arrow.apache.org/docs/python/install.html#python-compatibility)
- we could keep StringArray around for backwards compatibility and have it behind a global flag with a deprecation warning.
- pyarrow arrays are immutable. do we have benchmarks where this could matter.
@jorisvandenbossche i've marked as ready for review since ci is green. There are however, some outstanding comments from your previous review.
@simonjayhawkins can you rebase
xref https://github.com/pandas-dev/pandas/pull/41590#issuecomment-848395594

![image](https://user-images.githubusercontent.com/13159005/120106993-15fab700-c157-11eb-9311-ebb5670499ff.png)

can you rebase
Will take a look tomorrow

Op ma 31 mei 2021 18:21 schreef Jeff Reback ***@***.***>:

> can you rebase
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/pandas-dev/pandas/pull/39908#issuecomment-851584783>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAHZEUFFKAN33VDJHUE47WTTQOZSLANCNFSM4X4NQGBQ>
> .
>

I've update to @jorisvandenbossche suggestion. can now dicsuss https://github.com/pandas-dev/pandas/pull/39908#discussion_r643948210 in the context of these changes.

not yet tested with global option changed and not yet fixed pyarrow round trip failures (I assume we can do this in pandas)

will circle back to that after addressing other comments and any comments arising from these changes.
@jorisvandenbossche brings up a good point in https://github.com/pandas-dev/pandas/pull/39908#discussion_r643835726 that the storage is an implementation detail.

We could be on the wrong track with the parametrization of the dtype by storage.

The data that can be stored should be the same whether it is using python objects or backed by pyarrow. The dtype holds metadata about the data. maybe storage should not be an attribute of StringDtype at all.

Originally (when StringArray was implemented) is was planned that the future would be swapping out the memory storage model for pyarrow.

Changing the name and str() representation to not include the storage is a step in that direction.

I think we should maybe consider use composition in StringArray using ObjectStringArray and ArrowStringArray so that the user facing array type is always StringArray. The implementation detail could perhaps be hidden from the user completely. will look to update #40962 as a POC this weekend.

This would be a major 11th hour change so we may need to defer from 1.3
i disagree the storage is certainly part of the type 

#this is like saying i want float32 vs float64 it is a conscious decision 

further we can always make the string dtype work but you cannot go back 

eg if we don't expose these then it's always a global which is a bad idea

-1 on changing and -1000 on delaying 1.3
> -1000 on delaying 1.3

I'm not proposing delaying 1.3. we could go ahead without this.

> i disagree the storage is certainly part of the type
> 

its an implementation detail. should be able to convert between StringArray and ArrowSringArray and vice-versa without data loss.

> eg if we don't expose these then it's always a global which is a bad idea

the storage could be attribute of StringArray and storage an argument of the constructor. The array constructors is the only method where the StringArray and ArrowStringArray differ, these could be unified with this argument.



i would merge this as is

we close options if it's changed to be a single type

it's multiple types - the storage is not an implementation detail unless they r exactly the same and they r not

maybe in the future but not now 
> it's multiple types - the storage is not an implementation detail unless they r exactly the same and they r not

@jorisvandenbossche questioned whether `string[python] == string[pyarrow]` https://github.com/pandas-dev/pandas/pull/39908#discussion_r585576078  

Maybe a DataFrame/Series holding string data in pyarrow format should compare equal to another holding exactly the same data using the python memory model.

where we may want to consider mutliple types for storage is ascii/unicode as conversions between these would not be lossless.

> i would merge this as is

yes. this seems to be a working implementation. @jorisvandenbossche does seem to have some outstanding concerns which I think the proposed alternative could address.

> maybe in the future but not now

i'll mock up a POC in the next few days anyway. You have a good point about needing to use the global option to create pyarrow backed string arrays and can use the POC to look into this further (i.e. maybe we could pass kwargs from pd.array onto the array constructor and add a kwarg to _from_sequence)


> i would merge this as is

a couple of outstanding points https://github.com/pandas-dev/pandas/pull/39908#discussion_r642569108 and https://github.com/pandas-dev/pandas/pull/39908#discussion_r644023148

these could maybe a follow-up
> I think we should maybe consider use composition in StringArray using ObjectStringArray and ArrowStringArray so that the user facing array type is always StringArray. The implementation detail could perhaps be hidden from the user completely. ... This would be a major 11th hour change so we may need to defer from 1.3

I don't have a good idea whether composition would be nice, but just to point out: `ArrowStringArray` is explicitly labeled as "experimental", so I don't think we need to worry about this right now for 1.3. We can still change it afterwards *if* that turns out to be a better option. 
(all the methods on ArrowStringArray are consistent with StringArray, so that wouldn't change anything. The only "public" API entrypoint is the `ArrowStringArray(..)` constructor, and in principle no user should be using that (and that would also be easy to keep as backwards-compatible shim)).

All to say: I don't think we need to consider leaving this out for 1.3 release because of this open question. The exact array class is mostly an implementation detail we can still change later.

> We could be on the wrong track with the parametrization of the dtype by storage.

Even if we would have a single public StringArray through composition, wouldn't we keep the parametrization of the dtype? We still need to store the information about which storage to use somewhere, and we still need to give the user some control over it if they want to specify it explicitly. A parameter on the dtype seems fine for this. 
Yes, it would also be a property of the StringArray, but that makes it a lot harder to give a nice way to power users to control it.

> @jorisvandenbossche does seem to have some outstanding concerns which I think the proposed alternative could address.

I think my main open comment is about the potential "deferred" storage mode for `StringDtype(..)`. While I would like to see that discussed, I don't think that's worth it to block this PR for (it's mostly an implementation detail on eg how to deal with things like ``astype("string")``)


> > We could be on the wrong track with the parametrization of the dtype by storage.
> 
> Even if we would have a single public StringArray through composition, wouldn't we keep the parametrization of the dtype? We still need to store the information about which storage to use somewhere, and we still need to give the user some control over it if they want to specify it explicitly. A parameter on the dtype seems fine for this.
> Yes, it would also be a property of the StringArray, but that makes it a lot harder to give a nice way to power users to control it.

I think user control over the storage used is the sticking point if it's not on the dtype. I have updated  #40962 and it works well apart from the user control. tbh I prefer that implementation as basically the storage is controlled by the global option. I think it better represents the original intent of the StringArray.

Maybe a 3rd storage option, "auto" to use pyarrow backed storage if pyarrow > 1.0.0 is installed.

> Maybe a 3rd storage option, "auto" to use pyarrow backed storage if pyarrow > 1.0.0 is installed.

That's something that we can still add later? So no need to consider that now?
> > Maybe a 3rd storage option, "auto" to use pyarrow backed storage if pyarrow > 1.0.0 is installed.
> 
> That's something that we can still add later? So no need to consider that now?

sure.
Bringing the "deferred" storage mode lookup for `StringDtype` discussion (originally here: https://github.com/pandas-dev/pandas/pull/39908#discussion_r585573328) in the main thread, and trying to recap.

Currently, doing `pd.StringDtype()` (without specifying the storage), will already look up the option. In the default case, you get:

```python
>>> pd.StringDtype().storage
'python'
```

which also means that `pandas_dtype()` already "fully initializes" the string dtype:

```python
>>> pd.api.types.pandas_dtype("string")
string[python]
```

As a consequence, doing `astype("string")` will actually convert the values if your string dtype doesn't match the globab setting:

```python
>>> s = pd.Series(['a', 'b'], dtype=pd.StringDtype(storage="pyarrow"))
>>> s.dtype
string[pyarrow]
>>> s.astype("string").dtype
string[python]
```

While I think it could make sense for `.astype("string")` to mean: "ensure I have *a* string dtype", and thus don't convert to another storage backend if I already had a string dtype to start with. 

We do something similar for CategoricalDtype (`"category"` means a categorical dtype with no categories, but `astype("category")` does not remove your categories, it preserves any existing categorical dtype as is). 

We could still have the `astype("string")` behave in the way I suggest by special casing this in the `astype` implementations (as suggested in https://github.com/pandas-dev/pandas/pull/39908#discussion_r643867763)), but I think that's something we would ideally avoid (any `astype` implementation accepting string dtype values as input would need to handle this case?)