Как использовать numpy .char.join? - PullRequest
1 голос
/ 31 марта 2020

Критическая часть моего сценария зависит от объединения большого количества строк фиксированной длины. Поэтому я хотел бы использовать низкоуровневую функцию numpy.char.join вместо классической python build str.join.

Однако я не могу заставить ее работать правильно:

import numpy as np

# Example array.
array = np.array([
    ['a', 'b', 'c'],
    ['d', 'e', 'f'],
    ['g', 'h', 'i'],
    ], dtype='<U1')

# Now I wish to get:
# array(['abc', 'def', 'ghi'], dtype='<U3')

# But none of these is successful :(
np.char.join('', array)
np.char.join('', array.astype('<U3'))
np.char.join(np.array(''), array.astype('<U3'))
np.char.join(np.array('').astype('<U3'), array.astype('<U3'))
np.char.join(np.array(['', '', '']).astype('<U3'), array.astype('<U3'))
np.char.join(np.char.asarray(['', '', '']).astype('<U3'), np.char.asarray(array))
np.char.asarray(['', '', '']).join(array)
np.char.asarray(['', '', '']).astype('<U3').join(array.astype('<U3'))

.. и мой исходный массив всегда остается неизменным.

Что мне здесь не хватает?
Какой самый эффективный способ numpy объединить каждую строку большой строки? 2D <U1 массив?


[РЕДАКТИРОВАТЬ]: Поскольку производительность является проблемой, я провел сравнительный анализ предлагаемых решений. Но я все еще не знаю, как правильно вызвать np.char.join.

import numpy as np
import numpy.random as rd
from string import ascii_lowercase as letters
from time import time

# Build up an array with many random letters
n_lines = int(1e7)
n_columns = 4
array = np.array(list(letters))[rd.randint(0, len(letters), n_lines * n_columns)]
array = array.reshape((n_lines, n_columns))

# One quick-n-dirty way to benchmark.
class MeasureTime(object):
    def __enter__(self):
        self.tic = time()
    def __exit__(self, type, value, traceback):
        toc = time()
        print(f"{toc-self.tic:0.3f} seconds")


# And test three concatenations procedures.
with MeasureTime():
    # Involves str.join
    cat = np.apply_along_axis("".join, 1, array)

with MeasureTime():
    # Involves str.join
    cat = np.array(["".join(row) for row in array])

with MeasureTime():
    # Involve low-level np functions instead.
    # Here np.char.add for example.
    cat = np.char.add(
        np.char.add(np.char.add(array[:, 0], array[:, 1]), array[:, 2]), array[:, 3]
    )

выводит

41.722 seconds
19.921 seconds
15.206 seconds

на моем аппарате.

np.char.join будет лучше? Как заставить это работать?

Ответы [ 2 ]

1 голос
/ 31 марта 2020

На исходном (3,3) массиве (временные характеристики могут масштабироваться по-разному):

Цепочка np.char.add:

In [88]: timeit np.char.add(np.char.add(arr[:,0],arr[:,1]),arr[:,2])                           
29 µs ± 223 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Эквивалентный подход с использованием object dtype. Для python строк '+' является соединением строк.

In [89]: timeit arr.astype(object).sum(axis=1)                                                 
14.1 µs ± 18.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Для списка строк ''.join() должен быть быстрее, чем сумма строк. Кроме того, он позволяет указать «разделитель»:

In [90]: timeit np.array([''.join(row) for row in arr])                                        
13.8 µs ± 41.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Без преобразования обратно в массив:

In [91]: timeit [''.join(row) for row in arr]                                                      
10.2 µs ± 15.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Еще лучше, используйте tolist, чтобы преобразовать массив в список списки строк:

In [92]: timeit [''.join(row) for row in arr.tolist()]                                         
1.01 µs ± 1.81 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

список, эквивалентный вложенному np.char.add:

In [97]: timeit [row[0]+row[1]+row[2] for row in arr.tolist()]                                 
1.19 µs ± 2.68 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

numpy, не имеет строкового кода низкого уровня, по крайней мере, не в тот же смысл, что он имеет низкоуровневый скомпилированный код Numberri c. Он по-прежнему зависит от Python строкового кода, даже если он вызывает его из C -API.

====

Поскольку строки U1, мы можем просмотреть их как U3:

In [106]: arr.view('U3')                                                                       
Out[106]: 
array([['abc'],
       ['def'],
       ['ghi']], dtype='<U3')
In [107]: arr.view('U3').ravel()                                                               
Out[107]: array(['abc', 'def', 'ghi'], dtype='<U3')
In [108]: timeit arr.view('U3').ravel()                                                        
1.04 µs ± 9.81 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

===

Чтобы использовать np.char.join, мы должны собрать строки в некоторый кортеж, список и т. д. c. Один из способов сделать это - создать массив dtype объекта и заполнить его из массива:

In [110]: temp = np.empty(arr.shape[0], object)                                                
In [111]: temp                                                                                 
Out[111]: array([None, None, None], dtype=object)
In [112]: temp[:] = list(arr)                                                                  
In [113]: temp                                                                                 
Out[113]: 
array([array(['a', 'b', 'c'], dtype='<U1'),
       array(['d', 'e', 'f'], dtype='<U1'),
       array(['g', 'h', 'i'], dtype='<U1')], dtype=object)
In [114]: np.char.join('',temp)                                                                
Out[114]: array(['abc', 'def', 'ghi'], dtype='<U3')

или заполнить его списком списков:

In [115]: temp[:] = arr.tolist()                                                               
In [116]: temp                                                                                 
Out[116]: 
array([list(['a', 'b', 'c']), list(['d', 'e', 'f']),
       list(['g', 'h', 'i'])], dtype=object)
In [117]: np.char.join('',temp)                                                                
Out[117]: array(['abc', 'def', 'ghi'], dtype='<U3')

In [122]: %%timeit  
     ...: temp = np.empty(arr.shape[0], object) 
     ...: temp[:] = arr.tolist() 
     ...: np.char.join('', temp) 
     ...:  
     ...:                                                                                      
22.1 µs ± 69.1 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

====

Чтобы лучше понять, что может делать np.char.join, сравните его с split:

In [132]: temp                                                                                 
Out[132]: 
array([list(['a', 'b', 'c']), list(['d', 'e', 'f']),
       list(['g', 'h', 'i'])], dtype=object)
In [133]: b = np.char.join(',',temp)                                                           
In [134]: b                                                                                    
Out[134]: array(['a,b,c', 'd,e,f', 'g,h,i'], dtype='<U5')
In [135]: np.char.split(b,',')                                                                 
Out[135]: 
array([list(['a', 'b', 'c']), list(['d', 'e', 'f']),
       list(['g', 'h', 'i'])], dtype=object)

Еще один способ применить ''.join к элементам массива объектов:

In [136]: np.frompyfunc(lambda s: ','.join(s), 1,1)(temp)                                      
Out[136]: array(['a,b,c', 'd,e,f', 'g,h,i'], dtype=object)
0 голосов
/ 31 марта 2020
np.array([''.join(row) for row in array])

- это способ pythoni c, использующий понимание списка, затем обрабатывая его как массив numpy.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...