b'...' means it's a byte-string and the default dtype for arrays of strings depends on the kind of strings. Unicodes (python 3 strings are unicode) are U and Python 2 str or Python 3 bytes have the dtype S. You can find the explanation of dtypes in the NumPy documentation here

Array-protocol type strings

The first character specifies the kind of data and the remaining characters specify the number of bytes per item, except for Unicode, where it is interpreted as the number of characters. The item size must correspond to an existing type, or an error will be raised. The supported kinds are:

  • '?' boolean
  • 'b' (signed) byte
  • 'B' unsigned byte
  • 'i' (signed) integer
  • 'u' unsigned integer
  • 'f' floating-point
  • 'c' complex-floating point
  • 'm' timedelta
  • 'M' datetime
  • 'O' (Python) objects
  • 'S', 'a' zero-terminated bytes (not recommended)
  • 'U' Unicode string
  • 'V' raw data (void)

However in your first case you actually forced NumPy to convert it to bytes because you specified dtype='S'.

Answer from MSeifert on Stack Overflow
Top answer
1 of 2
10

b'...' means it's a byte-string and the default dtype for arrays of strings depends on the kind of strings. Unicodes (python 3 strings are unicode) are U and Python 2 str or Python 3 bytes have the dtype S. You can find the explanation of dtypes in the NumPy documentation here

Array-protocol type strings

The first character specifies the kind of data and the remaining characters specify the number of bytes per item, except for Unicode, where it is interpreted as the number of characters. The item size must correspond to an existing type, or an error will be raised. The supported kinds are:

  • '?' boolean
  • 'b' (signed) byte
  • 'B' unsigned byte
  • 'i' (signed) integer
  • 'u' unsigned integer
  • 'f' floating-point
  • 'c' complex-floating point
  • 'm' timedelta
  • 'M' datetime
  • 'O' (Python) objects
  • 'S', 'a' zero-terminated bytes (not recommended)
  • 'U' Unicode string
  • 'V' raw data (void)

However in your first case you actually forced NumPy to convert it to bytes because you specified dtype='S'.

2 of 2
2

Since NumPy version 2.0 a new numpy.dtypes.StringDType is available.

Often, real-world string data does not have a predictable length. In these cases it is awkward to use fixed-width strings, since storing all the data without truncation requires knowing the length of the longest string one would like to store in the array before the array is created.

To support situations like this, NumPy provides numpy.dtypes.StringDType, which stores variable-width string data in a UTF-8 encoding in a NumPy array:

from numpy.dtypes import StringDType
data = ["this is a longer string", "short string"]
arr = np.array(data, dtype=StringDType())
arr
array(['this is a longer string', 'short string'], dtype=StringDType())

Note that unlike fixed-width strings, StringDType is not parameterized by the maximum length of an array element, arbitrarily long or short strings can live in the same array without needing to reserve storage for padding bytes in the short strings.

๐ŸŒ
NumPy
numpy.org โ€บ devdocs โ€บ reference โ€บ routines.dtypes.html
Data type classes (numpy.dtypes) โ€” NumPy v2.5.dev0 Manual
numpy.dtypes.StrDType[source]# numpy.dtypes.BytesDType# numpy.dtypes.StringDType# numpy.dtypes.DateTime64DType[source]# numpy.dtypes.TimeDelta64DType# numpy.dtypes.ObjectDType[source]# numpy.dtypes.VoidDType# On this page
๐ŸŒ
NumPy
numpy.org โ€บ devdocs โ€บ user โ€บ basics.strings.html
Working with Arrays of Strings And Bytes โ€” NumPy v2.5.dev0 Manual
To support situations like this, NumPy provides numpy.dtypes.StringDType, which stores variable-width string data in a UTF-8 encoding in a NumPy array:
๐ŸŒ
NumPy
numpy.org โ€บ doc โ€บ stable โ€บ reference โ€บ arrays.dtypes.html
Data type objects (dtype) โ€” NumPy v2.4 Manual
A data type object (an instance of numpy.dtype class) describes how the bytes in the fixed-size block of memory corresponding to an array item should be interpreted.
๐ŸŒ
NumPy
numpy.org โ€บ doc โ€บ stable โ€บ reference โ€บ routines.dtypes.html
Data type classes (numpy.dtypes) โ€” NumPy v2.4 Manual
numpy.dtypes.StrDType[source]# numpy.dtypes.BytesDType# numpy.dtypes.StringDType# numpy.dtypes.DateTime64DType[source]# numpy.dtypes.TimeDelta64DType# numpy.dtypes.ObjectDType[source]# numpy.dtypes.VoidDType# On this page
๐ŸŒ
NumPy
numpy.org โ€บ doc โ€บ stable โ€บ user โ€บ basics.types.html
Data types โ€” NumPy v2.4 Manual
In addition to numerical types, NumPy also supports storing unicode strings, via the numpy.str_ dtype (U character code), null-terminated byte sequences via numpy.bytes_ (S character code), and arbitrary byte sequences, via numpy.void (V character code).
๐ŸŒ
NumPy
numpy.org โ€บ doc โ€บ 2.2 โ€บ reference โ€บ routines.dtypes.html
Data type classes (numpy.dtypes) โ€” NumPy v2.2 Manual
numpy.dtypes.StrDType[source]# numpy.dtypes.BytesDType# numpy.dtypes.StringDType# numpy.dtypes.DateTime64DType[source]# numpy.dtypes.TimeDelta64DType# numpy.dtypes.ObjectDType[source]# numpy.dtypes.VoidDType# On this page
๐ŸŒ
NumPy
numpy.org โ€บ doc โ€บ 2.1 โ€บ reference โ€บ routines.dtypes.html
Data type classes (numpy.dtypes) โ€” NumPy v2.1 Manual
numpy.dtypes.StrDType[source]# numpy.dtypes.BytesDType# numpy.dtypes.StringDType# numpy.dtypes.DateTime64DType[source]# numpy.dtypes.TimeDelta64DType# numpy.dtypes.ObjectDType[source]# numpy.dtypes.VoidDType# On this page
Find elsewhere
๐ŸŒ
W3Schools
w3schools.com โ€บ python โ€บ numpy โ€บ numpy_data_types.asp
NumPy Data Types
We use the array() function to create arrays, this function can take an optional argument: dtype that allows us to define the expected data type of the array elements: ... import numpy as np arr = np.array([1, 2, 3, 4], dtype='S') print(arr) print(arr.dtype) Try it Yourself ยป
๐ŸŒ
NumPy
numpy.org โ€บ doc โ€บ 2.3 โ€บ reference โ€บ routines.dtypes.html
Data type classes (numpy.dtypes) โ€” NumPy v2.3 Manual
numpy.dtypes.StrDType[source]# numpy.dtypes.BytesDType# numpy.dtypes.StringDType# numpy.dtypes.DateTime64DType[source]# numpy.dtypes.TimeDelta64DType# numpy.dtypes.ObjectDType[source]# numpy.dtypes.VoidDType# On this page
๐ŸŒ
NumPy
numpy.org โ€บ devdocs โ€บ user โ€บ basics.types.html
Data types โ€” NumPy v2.5.dev0 Manual
In addition to numerical types, NumPy also supports storing unicode strings, via the numpy.str_ dtype (U character code), null-terminated byte sequences via numpy.bytes_ (S character code), and arbitrary byte sequences, via numpy.void (V character code).
Top answer
1 of 2
64

NumPy arrays are stored as contiguous blocks of memory. They usually have a single datatype (e.g. integers, floats or fixed-length strings) and then the bits in memory are interpreted as values with that datatype.

Creating an array with dtype=object is different. The memory taken by the array now is filled with pointers to Python objects which are being stored elsewhere in memory (much like a Python list is really just a list of pointers to objects, not the objects themselves).

Arithmetic operators such as * don't work with arrays such as ar1 which have a string_ datatype (there are special functions instead - see below). NumPy is just treating the bits in memory as characters and the * operator doesn't make sense here. However, the line

np.array(['avinash','jay'], dtype=object) * 2

works because now the array is an array of (pointers to) Python strings. The * operator is well defined for these Python string objects. New Python strings are created in memory and a new object array with references to the new strings is returned.


If you have an array with string_ or unicode_ dtype and want to repeat each string, you can use np.char.multiply:

In [52]: np.char.multiply(ar1, 2)
Out[52]: array(['avinashavinash', 'jayjay'], 
      dtype='<U14')

NumPy has many other vectorised string methods too.

2 of 2
4

There are 3 main dtypes to store strings in numpy:

  • object: Stores pointers to Python objects
  • str: Stores fixed-width strings
  • numpy.types.StringDType(): New in numpy 2.0 and stores variable-width strings

str consumes more memory than object; StringDType is better

Depending on the length of the fixed-length string and the size of the array, the ratio differs but as long as the longest string in the array is longer than 2 characters, str consumes more memory (they are equal when the longest string in the array is 2 characters long). For example, in the following example, str consumes almost 8 times more memory.

On the other hand, the new (in numpy>=2.0) numpy.dtypes.StringDType stores variable width strings, so consumes much less memory.

from pympler.asizeof import asizeof

ar1 = np.array(['this is a string', 'string']*1000, dtype=object)
ar2 = np.array(['this is a string', 'string']*1000, dtype=str)
ar3 = np.array(['this is a string', 'string']*1000, dtype=np.dtypes.StringDType())

asizeof(ar2) / asizeof(ar1)  # 7.944444444444445
asizeof(ar3) / asizeof(ar1)  # 1.992063492063492

For numpy 1.x, str is slower than object

For numpy>=2.0.0, str is faster than object

Numpy 2.0 has introduced a new numpy.strings API that has much more performant ufuncs for string operations. A simple test (on numpy 2.2.0) below shows that vectorized string operations on an array of str or StringDType dtype is much faster than the same operations on an object dtype array.

import timeit

t1 = min(timeit.repeat(lambda: ar1*2, number=1000))
t2a = min(timeit.repeat(lambda: np.strings.multiply(ar2, 2), number=1000))
t2b = min(timeit.repeat(lambda: np.strings.multiply(ar3, 2), number=1000))
print(t2a / t1)   # 0.8786601958427778
print(t2b / t1)   # 0.7311586933668037

t3 = min(timeit.repeat(lambda: np.array([s.count('i') for s in ar1]), number=1000))
t4a = min(timeit.repeat(lambda: np.strings.count(ar2, 'i'), number=1000))
t4b = min(timeit.repeat(lambda: np.strings.count(ar3, 'i'), number=1000))

print(t4a / t3)   # 0.13328748153237377
print(t4b / t3)   # 0.3365874412749679
For numpy<2.0.0 (tested on numpy 1.26.0)

Numpy 1.x's vectorized string methods are not optimized, so operating on the object array is often faster. For example, in the example in the OP where each character is repeated, a simple * (aka multiply()) is not only more concise but also over 10 times faster than char.multiply().

import timeit
setup = "import numpy as np; from __main__ import ar1, ar2"
t1 = min(timeit.repeat("ar1*2", setup, number=1000))
t2 = min(timeit.repeat("np.char.multiply(ar2, 2)", setup, number=1000))
t2 / t1   # 10.650433758517027

Even for functions that cannot be readily be applied on the array, instead of the vectorized char method of str arrays, it is faster to loop over the object array and work on the Python strings.

For example, iterating over the object array and calling str.count() on each Python string is over 3 times faster than the vectorized char.count() on the str array.

f1 = lambda: np.array([s.count('i') for s in ar1])
f2 = lambda: np.char.count(ar2, 'i')

setup = "import numpy as np; from __main__ import ar1, ar2, f1, f2, f3"
t3 = min(timeit.repeat("f1()", setup, number=1000))
t4 = min(timeit.repeat("f2()", setup, number=1000))

t4 / t3   # 3.251369161574832

On a side note, if it comes to explicit loop, iterating over a list is faster than iterating over a numpy array. So in the previous example, a further performance gain can be made by iterating over the list

f3 = lambda: np.array([s.count('i') for s in ar1.tolist()])
#                                               ^^^^^^^^^  <--- convert to list here
t5 = min(timeit.repeat("f3()", setup, number=1000))
t3 / t5   # 1.2623498005294627
๐ŸŒ
NumPy
numpy.org โ€บ doc โ€บ 2.1 โ€บ user โ€บ basics.types.html
Data types โ€” NumPy v2.1 Manual
In addition to numerical types, NumPy also supports storing unicode strings, via the numpy.str_ dtype (U character code), null-terminated byte sequences via numpy.bytes_ (S character code), and arbitrary byte sequences, via numpy.void (V character code).
๐ŸŒ
NumPy
numpy.org โ€บ doc โ€บ 2.1 โ€บ reference โ€บ arrays.dtypes.html
Data type objects (dtype) โ€” NumPy v2.1 Manual
A data type object (an instance of numpy.dtype class) describes how the bytes in the fixed-size block of memory corresponding to an array item should be interpreted.
๐ŸŒ
GitHub
github.com โ€บ pandas-dev โ€บ pandas โ€บ issues โ€บ 47884
Pandas string dtype needs from NumPy - prototyping & plan of attack ยท Issue #47884 ยท pandas-dev/pandas
July 28, 2022 - The purpose of this issue is to discuss a plan of attack for improving string dtypes in NumPy to better suit Pandas. Context @seberg has spent a ton of effort improving the infrastructure NumPy offers for implement dtypes, in NumPy itsel...
Author ย  rgommers
๐ŸŒ
NumPy
numpy.org โ€บ doc โ€บ 2.2 โ€บ user โ€บ basics.types.html
Data types โ€” NumPy v2.2 Manual
In addition to numerical types, NumPy also supports storing unicode strings, via the numpy.str_ dtype (U character code), null-terminated byte sequences via numpy.bytes_ (S character code), and arbitrary byte sequences, via numpy.void (V character code).