Panda vs Numpy
Ce qu’il faut retenir
Numpy et Pandas n’ont pas exactement les mêmes objectifs.
Dans la plupart des cas, NumPy peut être légèrement plus rapide que pandas, car NumPy est plus bas niveau et a moins de surcharge. Cependant, pandas offre des structures de données et des fonctionnalités plus avancées, ce qui peut faciliter le travail avec des ensembles de données complexes. Les performances relatives de NumPy et pandas dépendent également des opérations spécifiques effectuées sur les données, de sorte que les différences de performances peuvent varier en fonction des tâches spécifiques. Certaines fonctions n’existent qu’avec pandas, et qui n’ont pas d’équivalents NumPy sont : read_csv, read_excel, groupby, pivot_table, merge, concat, melt, crosstab, cut, qcut, get_dummies et applymap.
Résultats
Résultat : image générée : notez bien que j’ai appelé des fonctions « bas niveau » pour qu’on voie ce que NumPy a dans le ventre et des fonctions qui n’existent que dans pandas, que ré-implémentées en Python pur + NumPy.
Code source
Voici le code source que j’ai fait, qui appelle quelques fonctions connues de NumPy et de pandas.
import numpy as np
import pandas as pd
import time
import matplotlib.pyplot as plt
# Générer un grand ensemble de données
data_np = np.random.rand(30_000_000)
data_pd = pd.DataFrame({"values": data_np})
operations = (
"sum",
"mean",
"filter",
"cum_sum",
"sort",
"complex",
"pivot",
"group_by",
"rolling",
)
time_np = []
time_pd = []
# Définir une fonction pour chronométrer et stocker les temps d'exécution
def measure_time(start_time, end_time, time_list):
time_list.append(end_time - start_time)
# Effectuer les différentes opérations et mesurer les temps d'exécution
for operation in operations:
# print(f"operation: {operation}")
print(f"{operation}")
if operation == "sum":
start_time_np = time.time()
result_np = np.sum(data_np)
end_time_np = time.time()
measure_time(start_time_np, end_time_np, time_np)
start_time_pd = time.time()
result_pd = data_pd["values"].sum()
end_time_pd = time.time()
measure_time(start_time_pd, end_time_pd, time_pd)
elif operation == "mean":
start_time_np = time.time()
mean_np = np.mean(data_np)
end_time_np = time.time()
measure_time(start_time_np, end_time_np, time_np)
start_time_pd = time.time()
mean_pd = data_pd["values"].mean()
end_time_pd = time.time()
measure_time(start_time_pd, end_time_pd, time_pd)
elif operation == "filter":
start_time_np = time.time()
filtered_np = data_np[data_np > 0.5]
end_time_np = time.time()
measure_time(start_time_np, end_time_np, time_np)
start_time_pd = time.time()
filtered_pd = data_pd[data_pd["values"] > 0.5]
end_time_pd = time.time()
measure_time(start_time_pd, end_time_pd, time_pd)
elif operation == "cum_sum":
start_time_np = time.time()
cum_sum_np = np.cumsum(data_np)
end_time_np = time.time()
measure_time(start_time_np, end_time_np, time_np)
start_time_pd = time.time()
cum_sum_pd = data_pd["values"].cumsum()
end_time_pd = time.time()
measure_time(start_time_pd, end_time_pd, time_pd)
elif operation == "sort":
start_time_np = time.time()
sorted_np = np.sort(data_np)
end_time_np = time.time()
measure_time(start_time_np, end_time_np, time_np)
start_time_pd = time.time()
sorted_pd = data_pd["values"].sort_values()
end_time_pd = time.time()
measure_time(start_time_pd, end_time_pd, time_pd)
elif operation == "complex":
# Générer des données structurées
data_1 = np.random.randint(0, 1_000_000, (2_000, 2))
data_2 = np.random.randint(0, 1_000_000, (2_000, 2))
# Créer des DataFrames pandas
df_1 = pd.DataFrame(data_1, columns=["id", "value_1"])
df_2 = pd.DataFrame(data_2, columns=["id", "value_2"])
# Créer des arrays structurés NumPy
d_type = np.dtype([("id", int), ("value", int)])
numpy_data_1 = np.array(
list(map(tuple, data_1)), dtype=d_type
)
numpy_data_2 = np.array(
list(map(tuple, data_2)), dtype=d_type
)
# Jointure avec NumPy
def numpy_join(data1, data2):
result = []
for row1 in data1:
for row2 in data2:
if row1["id"] == row2["id"]:
result.append(
(row1["id"], row1["value"], row2["value"])
)
return np.array(
result,
dtype=[
("id", int),
("value_1", int),
("value_2", int),
],
)
start_time_np = time.time()
numpy_result = numpy_join(numpy_data_1, numpy_data_2)
end_time_np = time.time()
measure_time(
start_time_np, end_time_np, time_np
) # Ajoutez cette ligne
# Jointure avec pandas
start_time_pd = time.time()
pandas_result = df_1.merge(df_2, on="id")
end_time_pd = time.time()
measure_time(start_time_pd, end_time_pd, time_pd)
elif operation == "pivot":
# Générer des données structurées
unique_ids = np.arange(0, 60_000)
unique_groups = np.arange(0, 3)
id_col = np.repeat(unique_ids, len(unique_groups))
group_col = np.tile(unique_groups, len(unique_ids))
value_col = np.random.randint(0, 100, len(id_col))
data = np.column_stack((id_col, group_col, value_col))
# Créer des DataFrames pandas
df = pd.DataFrame(data, columns=["id", "group", "value"])
# Créer des arrays structurés NumPy
d_type = np.dtype(
[("id", int), ("group", int), ("value", int)]
)
numpy_data = np.array(list(map(tuple, data)), dtype=d_type)
# Pivot avec NumPy
def numpy_pivot(_data, _id_col, _group_col, _value_col):
_unique_ids = np.unique(_data[_id_col])
_unique_groups = np.unique(_data[_group_col])
pivot_table = np.zeros(
(len(_unique_ids), len(_unique_groups))
)
for row in _data:
id_index = np.where(_unique_ids == row[_id_col])[0][0]
group_index = np.where(
_unique_groups == row[_group_col]
)[0][0]
pivot_table[id_index, group_index] = row[_value_col]
return pivot_table
start_time_np = time.time()
numpy_pivot_table = numpy_pivot(
numpy_data, "id", "group", "value"
)
end_time_np = time.time()
measure_time(start_time_np, end_time_np, time_np)
# Pivot avec pandas
start_time_pd = time.time()
pandas_pivot_table = df.pivot(
index="id", columns="group", values="value"
)
end_time_pd = time.time()
measure_time(start_time_pd, end_time_pd, time_pd)
elif operation == "group_by":
# Générer des données structurées
data = np.random.randint(0, 10_000_000, (100_000, 2))
# Créer des DataFrames pandas
df = pd.DataFrame(data, columns=["id", "value"])
# Créer des arrays structurés NumPy
d_type = np.dtype([("id", int), ("value", int)])
numpy_data = np.array(list(map(tuple, data)), dtype=d_type)
# Group_by avec NumPy
def numpy_group_by_mean(_data):
_unique_ids, counts = np.unique(
_data["id"], return_counts=True
)
sums = np.zeros_like(_unique_ids, dtype=float)
for row in _data:
sums[np.where(_unique_ids == row["id"])[0][0]] += row[
"value"
]
return _unique_ids, sums / counts
start_time_np = time.time()
numpy_result = numpy_group_by_mean(numpy_data)
end_time_np = time.time()
measure_time(start_time_np, end_time_np, time_np)
# Group_by avec pandas
start_time_pd = time.time()
pandas_result = df.groupby("id")["value"].mean()
end_time_pd = time.time()
measure_time(start_time_pd, end_time_pd, time_pd)
elif operation == "rolling":
# Générer un grand ensemble de données
data_np = np.random.rand(100_000_000)
data_pd = pd.DataFrame({"values": data_np})
window = 100
def numpy_rolling_mean(arr, _window):
_cum_sum = np.cumsum(np.insert(arr, 0, 0))
return (
_cum_sum[_window:] - _cum_sum[:-_window]
) / _window
start_time_np = time.time()
numpy_result = numpy_rolling_mean(data_np, window)
end_time_np = time.time()
measure_time(start_time_np, end_time_np, time_np)
# Rolling avec pandas
start_time_pd = time.time()
pandas_result = (
data_pd["values"].rolling(window=window).mean()
)
end_time_pd = time.time()
measure_time(start_time_pd, end_time_pd, time_pd)
# Créer un graphique de comparaison
x = np.arange(len(operations))
width = 0.35
fig, ax = plt.subplots()
rects1 = ax.bar(
x - width / 2,
time_np,
width,
label="NumPy",
color="#c9daf8",
edgecolor="black",
hatch="//",
linewidth=1,
)
rects2 = ax.bar(
x + width / 2,
time_pd,
width,
label="pandas",
color="#c2e8b8",
edgecolor="black",
hatch=".",
linewidth=1,
alpha=0.5,
)
# Modification de la taille des marqueurs dans rects2
for rect in rects2:
rect.set_linewidth(2)
ax.set_yscale("log")
ax.set_ylabel("Temps d'exécution (s) - Échelle logarithmique")
ax.set_title(
"Comparaison des temps d'exécution entre NumPy et pandas"
)
ax.set_xticks(x)
ax.set_xticklabels(operations)
ax.legend()
def autolabel(rects):
for _rect in rects:
height = _rect.get_height()
ax.annotate(
"{:.2f}".format(height),
xy=(_rect.get_x() + _rect.get_width() / 2, height),
xytext=(0, 3), # 3 points vertical offset
textcoords="offset points",
ha="center",
va="bottom",
)
autolabel(rects1)
autolabel(rects2)
fig.tight_layout()
plt.savefig("pandas_vs_numpy.png")