Cách thêm một cột phụ vào mảng NumPy


292

Giả sử tôi có một mảng NumPy , a:

a = np.array([
    [1, 2, 3],
    [2, 3, 4]
    ])

Và tôi muốn thêm một cột số không để có được một mảng , b:

b = np.array([
    [1, 2, 3, 0],
    [2, 3, 4, 0]
    ])

Làm thế nào tôi có thể làm điều này dễ dàng trong NumPy?

Câu trả lời:


181

Tôi nghĩ rằng một giải pháp đơn giản hơn và khởi động nhanh hơn là làm như sau:

import numpy as np
N = 10
a = np.random.rand(N,N)
b = np.zeros((N,N+1))
b[:,:-1] = a

Và hẹn giờ:

In [23]: N = 10

In [24]: a = np.random.rand(N,N)

In [25]: %timeit b = np.hstack((a,np.zeros((a.shape[0],1))))
10000 loops, best of 3: 19.6 us per loop

In [27]: %timeit b = np.zeros((a.shape[0],a.shape[1]+1)); b[:,:-1] = a
100000 loops, best of 3: 5.62 us per loop

16
Tôi muốn nối (985,1) hình dạng np araay vào mảng (985,2) np để tạo mảng (985,3) np, nhưng nó không hoạt động. Tôi nhận được lỗi "không thể phát mảng đầu vào từ hình dạng (985) thành hình dạng (985,1)". Có gì sai với mã của tôi? Mã: np.hstack (dữ liệu, dữ liệu1)
Ngoại

5
@ Trước đó bạn nên đăng một câu hỏi mới thay vì hỏi một câu trong phần bình luận của câu hỏi này.
JoshAdel

4
@JoshAdel: Tôi đã thử mã của bạn trên ipython và tôi nghĩ có lỗi cú pháp. Bạn có thể muốn thử đổi a = np.random.rand((N,N))thànha = np.random.rand(N,N)
hlin117

Tôi đoán đây là một sự quá mức cho những gì OP yêu cầu. Câu trả lời của Op là thích hợp!
lft93ryt

Đây chỉ là một mẹo để thực hiện nối, hoặc chèn hoặc ngăn xếp. và không nên được chấp nhận là câu trả lời. Các kỹ sư nên xem xét sử dụng các câu trả lời dưới đây.
cinqS

326

np.r_[ ... ]np.c_[ ... ] là các lựa chọn thay thế hữu ích cho vstackhstack, với dấu ngoặc vuông [] thay vì round ().
Một vài ví dụ:

: import numpy as np
: N = 3
: A = np.eye(N)

: np.c_[ A, np.ones(N) ]              # add a column
array([[ 1.,  0.,  0.,  1.],
       [ 0.,  1.,  0.,  1.],
       [ 0.,  0.,  1.,  1.]])

: np.c_[ np.ones(N), A, np.ones(N) ]  # or two
array([[ 1.,  1.,  0.,  0.,  1.],
       [ 1.,  0.,  1.,  0.,  1.],
       [ 1.,  0.,  0.,  1.,  1.]])

: np.r_[ A, [A[1]] ]              # add a row
array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  1.],
       [ 0.,  1.,  0.]])
: # not np.r_[ A, A[1] ]

: np.r_[ A[0], 1, 2, 3, A[1] ]    # mix vecs and scalars
  array([ 1.,  0.,  0.,  1.,  2.,  3.,  0.,  1.,  0.])

: np.r_[ A[0], [1, 2, 3], A[1] ]  # lists
  array([ 1.,  0.,  0.,  1.,  2.,  3.,  0.,  1.,  0.])

: np.r_[ A[0], (1, 2, 3), A[1] ]  # tuples
  array([ 1.,  0.,  0.,  1.,  2.,  3.,  0.,  1.,  0.])

: np.r_[ A[0], 1:4, A[1] ]        # same, 1:4 == arange(1,4) == 1,2,3
  array([ 1.,  0.,  0.,  1.,  2.,  3.,  0.,  1.,  0.])

(Lý do cho dấu ngoặc vuông [] thay vì vòng () là Python mở rộng, ví dụ 1: 4 trong hình vuông - kỳ quan của quá tải.)


7
chỉ cần tìm kiếm thông tin về điều này, và chắc chắn đây là một câu trả lời tốt hơn câu trả lời được chấp nhận, bởi vì nó bao gồm việc thêm một cột phụ ở đầu và cuối, không chỉ ở cuối như những câu trả lời khác
Ay0

2
@ Ay0 Chính xác, tôi đang tìm cách thêm một đơn vị thiên vị vào mạng nơ ron nhân tạo của mình theo từng đợt trên tất cả các lớp cùng một lúc, và đây là câu trả lời hoàn hảo.
gabious

Và nếu bạn muốn thêm n cột trong một thời gian thì sao?
Riley

1
@Riley, bạn có thể cho một ví dụ xin vui lòng? Python 3 có "giải nén lặp lại", vd np.c_[ * iterable ]; xem danh sách biểu thức .
chối

@denis, đó chính xác là những gì tôi đang tìm kiếm!
Riley

148

Sử dụng numpy.append:

>>> a = np.array([[1,2,3],[2,3,4]])
>>> a
array([[1, 2, 3],
       [2, 3, 4]])

>>> z = np.zeros((2,1), dtype=int64)
>>> z
array([[0],
       [0]])

>>> np.append(a, z, axis=1)
array([[1, 2, 3, 0],
       [2, 3, 4, 0]])

3
Điều này là tốt khi chèn các cột phức tạp hơn.
Thomas Ahle

6
Điều này đơn giản hơn câu trả lời của @JoshAdel, nhưng khi xử lý các tập dữ liệu lớn, nó sẽ chậm hơn. Tôi sẽ chọn giữa hai tùy thuộc vào tầm quan trọng của khả năng đọc.
dvj

3
appendthực sự chỉ cần gọiconcatenate
rll

53

Một cách, sử dụng hstack , là:

b = np.hstack((a, np.zeros((a.shape[0], 1), dtype=a.dtype)))

2
Tôi nghĩ rằng đây là giải pháp thanh lịch nhất.
silvado

2
+1 - đây là cách tôi sẽ làm - bạn đánh bại tôi để đăng nó dưới dạng câu trả lời :).
Blair

3
Xóa dtypetham số, nó không cần thiết và thậm chí không được phép. Mặc dù giải pháp của bạn đủ thanh lịch, hãy chú ý không sử dụng nó nếu bạn cần "nối" thường xuyên vào một mảng. Nếu bạn không thể tạo toàn bộ mảng cùng một lúc và điền vào sau, hãy tạo một danh sách các mảng và hstacktất cả cùng một lúc.
eumiro

1
@eumiro Tôi không chắc làm thế nào tôi có thể lấy được dtype ở vị trí sai, nhưng np.zeros cần một dtype để tránh mọi thứ trở nên nổi (trong khi a là int)
Peter Smit 13/12/11

42

Tôi thấy thanh lịch nhất sau đây:

b = np.insert(a, 3, values=0, axis=1) # Insert values before column 3

Một lợi thế của insertnó là nó cũng cho phép bạn chèn các cột (hoặc hàng) tại các vị trí khác trong mảng. Ngoài ra, thay vì chèn một giá trị, bạn có thể dễ dàng chèn toàn bộ một vectơ, ví dụ, nhân đôi cột cuối cùng:

b = np.insert(a, insert_index, values=a[:,2], axis=1)

Điều này dẫn đến:

array([[1, 2, 3, 3],
       [2, 3, 4, 4]])

Về thời gian, insertcó thể chậm hơn giải pháp của JoshAdel:

In [1]: N = 10

In [2]: a = np.random.rand(N,N)

In [3]: %timeit b = np.hstack((a, np.zeros((a.shape[0], 1))))
100000 loops, best of 3: 7.5 µs per loop

In [4]: %timeit b = np.zeros((a.shape[0], a.shape[1]+1)); b[:,:-1] = a
100000 loops, best of 3: 2.17 µs per loop

In [5]: %timeit b = np.insert(a, 3, values=0, axis=1)
100000 loops, best of 3: 10.2 µs per loop

1
Điều này là khá gọn gàng. Quá tệ, tôi không thể làm gì insert(a, -1, ...)để nối thêm cột. Đoán tôi sẽ chỉ trả trước nó thay thế.
Thomas Ahle

2
@ThomasAhle Bạn có thể nối thêm một hàng hoặc cột bằng cách lấy kích thước trong trục đó bằng cách sử dụng a.shape[axis]. I E. để nối một hàng, bạn làm np.insert(a, a.shape[0], 999, axis=0)và cho một cột, bạn làm np.insert(a, a.shape[1], 999, axis=1).
blubberdiblub

35

Tôi cũng quan tâm đến câu hỏi này và so sánh tốc độ của

numpy.c_[a, a]
numpy.stack([a, a]).T
numpy.vstack([a, a]).T
numpy.ascontiguousarray(numpy.stack([a, a]).T)               
numpy.ascontiguousarray(numpy.vstack([a, a]).T)
numpy.column_stack([a, a])
numpy.concatenate([a[:,None], a[:,None]], axis=1)
numpy.concatenate([a[None], a[None]], axis=0).T

mà tất cả đều làm điều tương tự cho bất kỳ vector đầu vào a. Thời điểm phát triển a:

nhập mô tả hình ảnh ở đây

Lưu ý rằng tất cả các biến thể không liền kề (đặc biệt stack/ vstack) cuối cùng đều nhanh hơn tất cả các biến thể liền kề. column_stack(vì sự rõ ràng và tốc độ của nó) dường như là một lựa chọn tốt nếu bạn yêu cầu sự liên tục.


Mã để tái tạo cốt truyện:

import numpy
import perfplot

perfplot.save(
    "out.png",
    setup=lambda n: numpy.random.rand(n),
    kernels=[
        lambda a: numpy.c_[a, a],
        lambda a: numpy.ascontiguousarray(numpy.stack([a, a]).T),
        lambda a: numpy.ascontiguousarray(numpy.vstack([a, a]).T),
        lambda a: numpy.column_stack([a, a]),
        lambda a: numpy.concatenate([a[:, None], a[:, None]], axis=1),
        lambda a: numpy.ascontiguousarray(
            numpy.concatenate([a[None], a[None]], axis=0).T
        ),
        lambda a: numpy.stack([a, a]).T,
        lambda a: numpy.vstack([a, a]).T,
        lambda a: numpy.concatenate([a[None], a[None]], axis=0).T,
    ],
    labels=[
        "c_",
        "ascont(stack)",
        "ascont(vstack)",
        "column_stack",
        "concat",
        "ascont(concat)",
        "stack (non-cont)",
        "vstack (non-cont)",
        "concat (non-cont)",
    ],
    n_range=[2 ** k for k in range(20)],
    xlabel="len(a)",
    logx=True,
    logy=True,
)

1
Đồ thị đẹp! Chỉ cần nghĩ rằng bạn muốn biết rằng dưới mui xe, stack, hstack, vstack, column_stack, dstacklà tất cả các chức năng helper được xây dựng trên np.concatenate. Bằng cách truy tìm định nghĩa của ngăn xếp, tôi thấy rằng np.stack([a,a])đang gọi np.concatenate([a[None], a[None]], axis=0). Có thể tốt hơn khi thêm np.concatenate([a[None], a[None]], axis=0).Tvào perfplot để hiển thị rằng np.concatenateluôn có thể nhanh nhất là nhanh như chức năng của trình trợ giúp của nó.
unutbu

@unutbu Thêm vào đó.
Nico Schlömer

Thư viện đẹp, chưa bao giờ nghe nói về nó! Thật thú vị khi tôi chỉ có cùng một lô ngoại trừ stack và concat đã thay đổi địa điểm (trong cả hai biến thể ascont và non-cont). Cộng với cột concat và cột_stack cũng được hoán đổi.
Antony Hatchkins

1
Wow, yêu những âm mưu này!
jhegedus

Dường như đối với một hoạt động đệ quy nối thêm một cột vào một mảng, ví dụ b = [b, a], một số lệnh không hoạt động (một lỗi về kích thước không bằng nhau được đưa ra). Hai duy nhất dường như hoạt động với các mảng có kích thước không bằng nhau (nghĩa là khi một là ma trận và một số khác là vectơ 1d) c_column_stack
Bị nhiễu


12

np.concatenate cũng hoạt động

>>> a = np.array([[1,2,3],[2,3,4]])
>>> a
array([[1, 2, 3],
       [2, 3, 4]])
>>> z = np.zeros((2,1))
>>> z
array([[ 0.],
       [ 0.]])
>>> np.concatenate((a, z), axis=1)
array([[ 1.,  2.,  3.,  0.],
       [ 2.,  3.,  4.,  0.]])

np.concatenatedường như nhanh hơn 3 lần so np.hstackvới ma trận 2x1, 2x2 và 2x3. np.concatenatecũng nhanh hơn một chút so với việc sao chép ma trận bằng tay vào một ma trận trống trong các thí nghiệm của tôi. Điều đó phù hợp với câu trả lời của Nico Schlömer bên dưới.
Lenar Hoyt

11

Giả sử Mlà một (100,3) ndarray và ylà một ndarray (100,) appendcó thể được sử dụng như sau:

M=numpy.append(M,y[:,None],1)

Bí quyết là sử dụng

y[:, None]

Điều này chuyển đổi ythành một mảng 2D (100, 1).

M.shape

bây giờ cho

(100, 4)

Bạn là một anh hùng mà bạn biết điều đó?! Đó chính xác là những gì tôi đang kéo tóc trong 1 giờ qua! Ty!
John Doe

8

Tôi thích câu trả lời của JoshAdel vì tập trung vào hiệu suất. Một cải tiến hiệu suất nhỏ là để tránh chi phí khởi tạo với số không, chỉ được ghi đè. Điều này có sự khác biệt có thể đo được khi N lớn, trống được sử dụng thay cho số không và cột số không được viết dưới dạng một bước riêng biệt:

In [1]: import numpy as np

In [2]: N = 10000

In [3]: a = np.ones((N,N))

In [4]: %timeit b = np.zeros((a.shape[0],a.shape[1]+1)); b[:,:-1] = a
1 loops, best of 3: 492 ms per loop

In [5]: %timeit b = np.empty((a.shape[0],a.shape[1]+1)); b[:,:-1] = a; b[:,-1] = np.zeros((a.shape[0],))
1 loops, best of 3: 407 ms per loop

Bạn có thể sử dụng phát sóng để điền vào cột cuối cùng bằng số không (hoặc bất kỳ giá trị nào khác), có thể dễ đọc hơn : b[:,-1] = 0. Ngoài ra, với các mảng rất lớn, sự khác biệt hiệu năng np.insert()trở nên không đáng kể, điều này có thể khiến np.insert()nhiều người mong muốn hơn do tính cô đọng của nó.
blubberdiblub

7

np.insert cũng phục vụ mục đích.

matA = np.array([[1,2,3], 
                 [2,3,4]])
idx = 3
new_col = np.array([0, 0])
np.insert(matA, idx, new_col, axis=1)

array([[1, 2, 3, 0],
       [2, 3, 4, 0]])

Nó chèn các giá trị, ở đây new_col, trước một chỉ mục nhất định, ở đây idxdọc theo một trục. Nói cách khác, các giá trị mới được chèn sẽ chiếm idxcột và di chuyển những gì ban đầu ở đó sau và sau khi idxlùi.


1
Lưu ý rằng insertkhông đúng chỗ vì người ta có thể giả sử đã đưa ra tên của hàm (xem tài liệu được liên kết trong câu trả lời).
jneuendorf

5

Thêm một cột bổ sung vào một mảng numpy:

np.appendPhương thức của Numpy có ba tham số, hai tham số đầu tiên là mảng numpy 2D và thứ ba là tham số trục hướng dẫn dọc theo trục nào sẽ nối thêm:

import numpy as np  
x = np.array([[1,2,3], [4,5,6]]) 
print("Original x:") 
print(x) 

y = np.array([[1], [1]]) 
print("Original y:") 
print(y) 

print("x appended to y on axis of 1:") 
print(np.append(x, y, axis=1)) 

Bản in:

Original x:
[[1 2 3]
 [4 5 6]]
Original y:
[[1]
 [1]]
x appended to y on axis of 1:
[[1 2 3 1]
 [4 5 6 1]]

Lưu ý rằng bạn đang nối thêm y vào x ở đây thay vì nối thêm x vào y - đó là lý do tại sao vectơ cột của y nằm bên phải các cột của x trong kết quả.
Brian Popeck

4

Một chút muộn cho bữa tiệc, nhưng chưa ai đăng câu trả lời này, vì vậy để hoàn thiện: bạn có thể làm điều này với sự hiểu biết danh sách, trên một mảng Python đơn giản:

source = a.tolist()
result = [row + [0] for row in source]
b = np.array(result)

4

Đối với tôi, cách tiếp theo trông khá trực quan và đơn giản.

zeros = np.zeros((2,1)) #2 is a number of rows in your array.   
b = np.hstack((a, zeros))

3

Trong trường hợp của tôi, tôi đã phải thêm một cột của một cột vào mảng NumPy

X = array([ 6.1101, 5.5277, ... ])
X.shape => (97,)
X = np.concatenate((np.ones((m,1), dtype=np.int), X.reshape(m,1)), axis=1)

Sau X.shape => (97, 2)

array([[ 1. , 6.1101],
       [ 1. , 5.5277],
...

1

Có một chức năng đặc biệt cho việc này. Nó được gọi là numpy.pad

a = np.array([[1,2,3], [2,3,4]])
b = np.pad(a, ((0, 0), (0, 1)), mode='constant', constant_values=0)
print b
>>> array([[1, 2, 3, 0],
           [2, 3, 4, 0]])

Đây là những gì nó nói trong chuỗi:

Pads an array.

Parameters
----------
array : array_like of rank N
    Input array
pad_width : {sequence, array_like, int}
    Number of values padded to the edges of each axis.
    ((before_1, after_1), ... (before_N, after_N)) unique pad widths
    for each axis.
    ((before, after),) yields same before and after pad for each axis.
    (pad,) or int is a shortcut for before = after = pad width for all
    axes.
mode : str or function
    One of the following string values or a user supplied function.

    'constant'
        Pads with a constant value.
    'edge'
        Pads with the edge values of array.
    'linear_ramp'
        Pads with the linear ramp between end_value and the
        array edge value.
    'maximum'
        Pads with the maximum value of all or part of the
        vector along each axis.
    'mean'
        Pads with the mean value of all or part of the
        vector along each axis.
    'median'
        Pads with the median value of all or part of the
        vector along each axis.
    'minimum'
        Pads with the minimum value of all or part of the
        vector along each axis.
    'reflect'
        Pads with the reflection of the vector mirrored on
        the first and last values of the vector along each
        axis.
    'symmetric'
        Pads with the reflection of the vector mirrored
        along the edge of the array.
    'wrap'
        Pads with the wrap of the vector along the axis.
        The first values are used to pad the end and the
        end values are used to pad the beginning.
    <function>
        Padding function, see Notes.
stat_length : sequence or int, optional
    Used in 'maximum', 'mean', 'median', and 'minimum'.  Number of
    values at edge of each axis used to calculate the statistic value.

    ((before_1, after_1), ... (before_N, after_N)) unique statistic
    lengths for each axis.

    ((before, after),) yields same before and after statistic lengths
    for each axis.

    (stat_length,) or int is a shortcut for before = after = statistic
    length for all axes.

    Default is ``None``, to use the entire axis.
constant_values : sequence or int, optional
    Used in 'constant'.  The values to set the padded values for each
    axis.

    ((before_1, after_1), ... (before_N, after_N)) unique pad constants
    for each axis.

    ((before, after),) yields same before and after constants for each
    axis.

    (constant,) or int is a shortcut for before = after = constant for
    all axes.

    Default is 0.
end_values : sequence or int, optional
    Used in 'linear_ramp'.  The values used for the ending value of the
    linear_ramp and that will form the edge of the padded array.

    ((before_1, after_1), ... (before_N, after_N)) unique end values
    for each axis.

    ((before, after),) yields same before and after end values for each
    axis.

    (constant,) or int is a shortcut for before = after = end value for
    all axes.

    Default is 0.
reflect_type : {'even', 'odd'}, optional
    Used in 'reflect', and 'symmetric'.  The 'even' style is the
    default with an unaltered reflection around the edge value.  For
    the 'odd' style, the extented part of the array is created by
    subtracting the reflected values from two times the edge value.

Returns
-------
pad : ndarray
    Padded array of rank equal to `array` with shape increased
    according to `pad_width`.

Notes
-----
.. versionadded:: 1.7.0

For an array with rank greater than 1, some of the padding of later
axes is calculated from padding of previous axes.  This is easiest to
think about with a rank 2 array where the corners of the padded array
are calculated by using padded values from the first axis.

The padding function, if used, should return a rank 1 array equal in
length to the vector argument with padded values replaced. It has the
following signature::

    padding_func(vector, iaxis_pad_width, iaxis, kwargs)

where

    vector : ndarray
        A rank 1 array already padded with zeros.  Padded values are
        vector[:pad_tuple[0]] and vector[-pad_tuple[1]:].
    iaxis_pad_width : tuple
        A 2-tuple of ints, iaxis_pad_width[0] represents the number of
        values padded at the beginning of vector where
        iaxis_pad_width[1] represents the number of values padded at
        the end of vector.
    iaxis : int
        The axis currently being calculated.
    kwargs : dict
        Any keyword arguments the function requires.

Examples
--------
>>> a = [1, 2, 3, 4, 5]
>>> np.pad(a, (2,3), 'constant', constant_values=(4, 6))
array([4, 4, 1, 2, 3, 4, 5, 6, 6, 6])

>>> np.pad(a, (2, 3), 'edge')
array([1, 1, 1, 2, 3, 4, 5, 5, 5, 5])

>>> np.pad(a, (2, 3), 'linear_ramp', end_values=(5, -4))
array([ 5,  3,  1,  2,  3,  4,  5,  2, -1, -4])

>>> np.pad(a, (2,), 'maximum')
array([5, 5, 1, 2, 3, 4, 5, 5, 5])

>>> np.pad(a, (2,), 'mean')
array([3, 3, 1, 2, 3, 4, 5, 3, 3])

>>> np.pad(a, (2,), 'median')
array([3, 3, 1, 2, 3, 4, 5, 3, 3])

>>> a = [[1, 2], [3, 4]]
>>> np.pad(a, ((3, 2), (2, 3)), 'minimum')
array([[1, 1, 1, 2, 1, 1, 1],
       [1, 1, 1, 2, 1, 1, 1],
       [1, 1, 1, 2, 1, 1, 1],
       [1, 1, 1, 2, 1, 1, 1],
       [3, 3, 3, 4, 3, 3, 3],
       [1, 1, 1, 2, 1, 1, 1],
       [1, 1, 1, 2, 1, 1, 1]])

>>> a = [1, 2, 3, 4, 5]
>>> np.pad(a, (2, 3), 'reflect')
array([3, 2, 1, 2, 3, 4, 5, 4, 3, 2])

>>> np.pad(a, (2, 3), 'reflect', reflect_type='odd')
array([-1,  0,  1,  2,  3,  4,  5,  6,  7,  8])

>>> np.pad(a, (2, 3), 'symmetric')
array([2, 1, 1, 2, 3, 4, 5, 5, 4, 3])

>>> np.pad(a, (2, 3), 'symmetric', reflect_type='odd')
array([0, 1, 1, 2, 3, 4, 5, 5, 6, 7])

>>> np.pad(a, (2, 3), 'wrap')
array([4, 5, 1, 2, 3, 4, 5, 1, 2, 3])

>>> def pad_with(vector, pad_width, iaxis, kwargs):
...     pad_value = kwargs.get('padder', 10)
...     vector[:pad_width[0]] = pad_value
...     vector[-pad_width[1]:] = pad_value
...     return vector
>>> a = np.arange(6)
>>> a = a.reshape((2, 3))
>>> np.pad(a, 2, pad_with)
array([[10, 10, 10, 10, 10, 10, 10],
       [10, 10, 10, 10, 10, 10, 10],
       [10, 10,  0,  1,  2, 10, 10],
       [10, 10,  3,  4,  5, 10, 10],
       [10, 10, 10, 10, 10, 10, 10],
       [10, 10, 10, 10, 10, 10, 10]])
>>> np.pad(a, 2, pad_with, padder=100)
array([[100, 100, 100, 100, 100, 100, 100],
       [100, 100, 100, 100, 100, 100, 100],
       [100, 100,   0,   1,   2, 100, 100],
       [100, 100,   3,   4,   5, 100, 100],
       [100, 100, 100, 100, 100, 100, 100],
       [100, 100, 100, 100, 100, 100, 100]])
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.