ArtBot

De Wikipast
Aller à la navigation Aller à la recherche

Description

Le but de l'ArtBot est de créer des pages pour des œuvres qui regroupent l'historique de leurs ventes. Il utilise comme base de données les registres de ventes du Getty Provenance Index fourni par le Getty Research Institute [1]. À partir de cette base de donnée, le bot fait une séléction des informations complêtes de la base de donnée, telles que les dates de ventes. Les pages titre(artiste) sont renchéries avec la séléction faite auparavant. Finalement, le bot génère une page de liens pour retrouver et lier toutes les pages d'œuvres.

Il a été créé par Vincent Philippoz, Michael Richter et Agatha Duranceau.

Gestion des bases de données

L'ArtBot utilise la bibliothèque pandas pour les fonctions de gestion de bases de données.

import pandas as pd

Récupération des données

Les fichiers .csv ont directement été récupérés sur GitHub [2], mais ne contiennent pas tous les mêmes catégories, ou les mêmes noms pour les mêmes catégories. Les entrées pertinentes ont été sélectionnées et renommées, puis les fichiers concaténés afin de travailler sur une base unique plus facile à lire par l'ArtBot.

Code pour l'importation des registres de vente :

df_SC1 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_1.csv',
           low_memory=False)
df_SC2 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_2.csv',
           low_memory=False)
df_SC3 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_3.csv',
           low_memory=False)
df_SC4 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_4.csv',
           low_memory=False)
df_SC5 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_5.csv',
          low_memory=False)
df_SC6 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_6.csv',
          low_memory=False)
df_SC7 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_7.csv',
          low_memory=False)
df_SC8 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_8.csv',
          low_memory=False)
df_SC9 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_9.csv',
          low_memory=False)
df_SC10 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_10.csv',
          low_memory=False)
df_SC11 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_11.csv',
          low_memory=False)
df_SC12 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_12.csv',
          low_memory=False)
df_SC13 = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/sales_catalogs/sales_contents_13.csv',
          low_memory=False)
df_knoedler = pd.read_csv('https://raw.githubusercontent.com/thegetty/provenance-index-csv/master/knoedler/knoedler.csv',
          low_memory=False)          

Pour chaque base de donnée, les informations pertinentes sont récupérées : Titre de l'œuvre, artiste, nationalité de l'artiste, date de vente, maison de ventes, prix de vente, vendeur, acheteur, et éventuellement type d'œuvre et genre. Les dataframes SC1 à SC13 ont les mêmes colonnes et peuvent donc être traitées ensemble. On extrait d'abord les colonnes importantes de SC1 puis on fait de même avec les autres dataframes qu'on concatène les unes à la suite des autres. Ensuite, on renomme les colonnes avec des noms plus simples.

# Création de la bases de données des Sales Catalogs
SC_wanted_columns = ["title",
                    "artist_name_1",
                    "lot_sale_year", 
                    "lot_sale_month", 
                    "lot_sale_day",
                    "auction_house_1",
                    "nationality_1",
                    "price_amount_1",
                    "price_currency_1",
                    "buy_name_1",
                    "sell_name_1",
                    "object_type",
                    "genre"]

df_SC_all = df_SC1.loc[:, SC_wanted_columns]

SALES_CATALOGUE = [df_SC2, df_SC3, df_SC4, df_SC5, df_SC6, df_SC7, df_SC8, df_SC9, df_SC10, df_SC11, df_SC12, df_SC13]

for dataframe in SALES_CATALOGUE :
  df_temp = dataframe.loc[:, SC_wanted_columns]
  df_SC_all = pd.concat([df_SC_all, df_temp])

# Mettre la même convention de noms
df_SC_all = df_SC_all.rename(columns = {"artist_name_1" : "artist_name",
                                        "lot_sale_year" : "sale_year" , 
                                        "lot_sale_month" : "sale_month"  , 
                                        "lot_sale_day": "sale_day",
                                        "buyer_name_1" : "buyer_name", 
                                        "seller_name_1" : "seller_name",
                                        "auction_house_1" : "auction_house",
                                        "nationality_1" : "artist_nationality",
                                        "price_amount_1" : "purch_amount",
                                        "price_currency_1" : "purch_currency",
                                        "buy_name_1" : "buyer_name",
                                        "sell_name_1" : "seller_name"})

Ensuite, on fait de même avec la dernière base de donnée qui n'a pas la même structure que les autres :

# Création de la bases de données de Knoedler
K_wanted_columns = ["title",
                    "artist_name_1",
                    "nationality_1",
                    "genre",
                    "object_type",
                    "sale_date_year",
                    "sale_date_month",
                    "sale_date_day",
                    "purch_amount",
                    "purch_currency",
                    "seller_name_1",
                    "buyer_name_1"]

df_K = df_knoedler.loc[:, K_wanted_columns]

df_K = df_K.reindex(columns=list(df_K.columns) + ["auction_house"])
df_K.auction_house = df_K.auction_house.fillna(value='Knoedler')

# Mettre la même convention de noms et le même ordre
df_K = df_K.rename(columns = {"artist_name_1" : "artist_name",
                              "sale_date_year" : "sale_year" , 
                              "sale_date_month" : "sale_month"  , 
                              "sale_date_day": "sale_day",
                              "nationality_1" : "artist_nationality", 
                              "buyer_name_1" : "buyer_name", 
                              "seller_name_1" : "seller_name"})

columns_list = df_SC_all.columns
df_K = df_K.reindex(columns = columns_list)

Enfin, on concatène la dernière base de données aux autres, et on force les dates à s'écrire comme des nombres entiers pour que l'affichage soit le même que sur Wikipast

# Combinaison des dataframes
df_tot = pd.concat([df_SC_all, df_K])

# Mettre les nombre de la date comme des entiers
df_tot.sale_year = df_tot.sale_year.convert_dtypes(convert_integer = True) 
df_tot.sale_month = df_tot.sale_month.convert_dtypes(convert_integer = True) 
df_tot.sale_day = df_tot.sale_day.convert_dtypes(convert_integer = True)

Il est possible d'afficher les données de la manière suivante :


Exemple d'informations contenues dans la base de données


Le jeu de données complet contient 1288210 entrées, dont 77.84 % de titres uniques et 15.93 % d'artistes uniques.

Dictionnaire Wikidata

60 genres différents et 82 types d'œuvres ont été identifiés dans la base de données. Cependant, cette dernière contient des informations en anglais, en français, en allemand et en néerlandais. Il est donc nécessaire d'identifier les différents types et genres à l'aide d'une étiquette indépendante du mot utilisé dans la base de données. Pour cela, les identifiants Wikidata sont utilisés, et un dictionnaire pour associer les bons identifiants aux bonnes catégories a été créé.

 # Dictionnaires
    types_wiki = {"peinture" : "[https://www.wikidata.org/wiki/Q3305213 Q3305213]",
        "sculpture" : "[https://www.wikidata.org/wiki/Q860861 Q860861]",
        "photographie" : "[https://www.wikidata.org/wiki/Q125191 Q125191]",
        "émail" : "[https://www.wikidata.org/wiki/Q79496108 Q79496108]",
        "dessin" : "[https://www.wikidata.org/wiki/Q93184 Q93184]",
        "tapisserie" : "[https://www.wikidata.org/wiki/Q184296 Q184296]",
        "broderie" : "[https://www.wikidata.org/wiki/Q28966302 Q28966302]",
        "meuble" : "[https://www.wikidata.org/wiki/Q14745 Q14745]",
        "fresque" : "[https://www.wikidata.org/wiki/Q134194 Q134194]",
        "objet d'arts décoratifs" : "[https://www.wikidata.org/wiki/Q631931 Q631931]",
        "gravure sur pierres précieuses" : "[https://www.wikidata.org/wiki/Q1501187 Q1501187]",
        "médaille" : "[https://www.wikidata.org/wiki/Q131647 Q131647]",
        "mosaïque" : "[https://www.wikidata.org/wiki/Q133067 Q133067]",
        "miniature" : "",
        "dentelle" : "[https://www.wikidata.org/wiki/Q231250 Q231250]",
        "marqueterie" : "[https://www.wikidata.org/wiki/Q1049923 Q1049923]",
        "aquarelle" : "[https://www.wikidata.org/wiki/Q18761202 Q18761202]",
        "estampe" : "[https://www.wikidata.org/wiki/Q11060274 Q11060274]",
        "lithographie" : "[https://www.wikidata.org/wiki/Q11060274 Q11060274]",
        "pastel" : "[https://www.wikidata.org/wiki/Q12043905 Q12043905]",
        "livre" : "[https://www.wikidata.org/wiki/Q571 Q571]",
        "horloge" : "[https://www.wikidata.org/wiki/Q376 Q376]",
        "montre": "[https://www.wikidata.org/wiki/Q376 Q376]",
        "cartes à jouer" : "[https://www.wikidata.org/wiki/Q47883 Q47883]",
        "assiette de cuivre" : "",
        "vêtement" : "[https://www.wikidata.org/wiki/Q11460 Q11460]",
        "carte" :  "[https://www.wikidata.org/wiki/Q4006 Q4006]",
        "minéraux" : "[https://www.wikidata.org/wiki/Q7946 Q7946]"}

    genres_wiki = {"paysage" : "[https://www.wikidata.org/wiki/Q191163 Q191163]",
        "portrait" : "[https://www.wikidata.org/wiki/Q134307 Q134307]",
        "oeuvre abstraite" : "[https://www.wikidata.org/wiki/Q128115 Q128115]",
        "nature morte" : "[https://www.wikidata.org/wiki/Q170571 Q170571]",
        "représentations d'animaux" : "[https://www.wikidata.org/wiki/Q16875712 Q16875712]",
        "scènes historiques" : "[https://www.wikidata.org/wiki/Q742333 Q742333]"}

Les noms réels des catégories dans la base de données sont remplacés de la manière suivante :

types_trans = {"miniature" : "miniature", 
    "enamel" : "émail",  
    "embroidery" : "broderie",  
    "mosaic" : "mosaïque", 
    "painting" : "peinture", 
    "drawing" : "dessin", 
    "sculpture" : "sculpture", 
    "furniture" : "meuble", 
    "fresco" : "fresque", 
    "tapestry" : "tapisserie" ,
    "decorative arts" : "objet d'arts décoratifs",
    "engraved gemstone" : "gravure sur pierres précieuses", 
    "dessin" : "dessin", 
    "peinture" : "peinture", 
    "émail" : "émail", 
    "peinture [?]" : "peinture", 
    "médaille" : "médaille",
    "tapisserie" : "tapisserie", 
    "mosaique" : "mosaïque", 
    "email" : "émail",
    "mosaïque" : "mosaïque", 
    "meuble" : "meuble",
    "tapisserie" : "tapisserie", 
    "dentelle" : "dentelle", 
    "marqueterie" : "marqueterie", 
    "peinture ou dessin" : "", 
    "broderie" : "broderie", 
    "gemälde" : "peinture",  
    "gemalde" : "peinture", 
    "zeichnung" : "dessin", 
    "skulptur" : "sculpture", 
    "miniatur" : "miniature",  
    "gemãlde" : "peinture",
    "skulptur" : "sculpture", 
    "graphik" : "", 
    "painting [?]" : "peinture",
    "watercolor" : "aquarelle", 
    "print" : "", 
    "pastel" : "pastel", 
    "book" : "livre", 
    "clocks" : "horloge", 
    "photograph" : "photographie",
    "decorative art" : "objet d'arts décoratifs", 
    "playing cards" : "cartes à jouer", 
    "copper plate" : "assiette de cuivre",
    "clothing" : "vêtement", 
    "maps" : "carte", 
    "minerals" : "minéraux"}

genres_trans = {"history" : "scènes historiques", 
    "genre" : "", 
    "landscape" : "paysage",  
    "portraits" : "portrait", 
    "animals" : "représentations d'animaux", 
    "still life" : "nature morte", 
    "historie" : "scènes historiques", 
    "landschaft" : "paysage", 
    "stilleben" : "nature morte", 
    "tiere" : "représentations d'animaux", 
    "porträt" : "portrait", 
    "stillleben |t stilleben" : "nature morte",
    "[not identified]" : "", 
    "portrait" : "portrait", 
    "abstract" : "oeuvre abstraite"}

Fonctionnement

L'ArtBot utilise la bibliothèque pywikiapi pour lire et écrire dans les pages de wikipast.

Création/modification d'une page

L'ArtBot vérifie pour chaque œuvre si une page de type Titre de l'œuvre (Auteur) existe déjà. Si non, il crée la page en question et ajoute la description de l'œuvre et une entrée pour l'évènement de vente dans les sections correspondantes. Si oui, il copie le texte de la page, insère la nouvelle entrée au bon endroit et écrase l'ancienne version de la page.

La création du tritre de la page se fait grâce à l'appel de la fonction sale_title_create qui reçoit comme argument une ligne de la dataframe contenant les informations d'une œuvre et génère un titre de la forme "Nom de l'œuvre (Nom de l'artiste)".

def sale_title_create(line) :
  if (line['artist_name'] == '[Anonymous]' or line['artist_name'] == '[unbekannt]' or pd.isna(line['artist_name'])) :
    title = (line['title'] + "(artiste anonyme)")
  else :
    title = (line['title'] + "(" + line['artist_name'] + ")")
    
  return(title)

Structure des pages

Titre de la page : Titre de l'œuvre (Artiste)

Description : [[type oeuvre]], [[genre]]

Syntaxe pour un évènement de vente :

  • [[Année.Mois.Jour]] / [[lieu]]. [[Vente]] de [[titre œuvre]] de [[artiste]] (de nationalité : [[nationalité]]) par [[vendeur]] à [[acheteur]] au prix de [[prix]] [[monnaie]] par la maison [[maison de ventes]]. [Source]

Lorsque certaines données sont absentes, elles sont ignorées et la proposition correspondante n'est pas écrite.

Insertion d'une entrée à une page déjà éxistante

Afin d'insérer une nouvelle entrée au bon endroit sur la page d'une œuvre, l'ArtBot fait appel à la fonction sale_entry_create qui reçoit comme argument les informations sur la vente d'une œuvre et les transforme pour les afficher au format désiré décrit dans la définition de la structure de la page. La fonction retourne ensuite la chaîne de caractères complète contenant toutes les informations présentes pour l'œuvre analysée. Les informations manquantes seront bien sûr ignorées (par exemple si elles contiennent des NaN, "unknown" etc.), et si le nom de l'artiste n'est pas précisé, il sera remplacé par la mention "artiste anonyme".

def sale_entry_create(line) :
  '''
  Take a line from the cleaned dataframe and prints it according to the 
  Wikidata event syntax.
  '''
  #il y a des dates ou le mois ou le jour ne sont pas connus
  if (line['sale_month'] == 0): #ça ne sert à rien d'avoir le jour si on n'a pas le mois
    date_txt = (f"[[{line['sale_year']}.-.-]]/")
  elif (line['sale_day'] == 0):
    date_txt = (f"[[{line['sale_year']}.{line['sale_month']}.-]]/")
  else:
    date_txt = (f"[[{line['sale_year']}.{line['sale_month']}.{line['sale_day']}]]/")

  place_txt = (f"-. ")
  title_txt = (f"Vente de [[" + sale_title_create(line)+f"|{line['title']}]]")

  if (line['artist_name'] == '[Anonymous]' or line['artist_name'] == '[unbekannt]' or pd.isna(line['artist_name'])) :
    artist_txt = (" d'un.e artiste anonyme")
  else : 
    artist_txt = (f" de [[{line['artist_name']}]]")

  if (pd.isna(line['seller_name'])):
    seller_txt = ('')
  else :
    seller_txt = (f" par [[{line['seller_name']}]]")

  if (pd.isna(line['buyer_name'])):
    buyer_txt = ('')
  else :
    buyer_txt = (f" à [[{line['buyer_name']}]]")

  if (pd.isna(line['purch_amount']) or pd.isna(line['purch_currency'])): 
    price_txt = ('')
  else :
    price_txt = (f" au prix de {line['purch_amount']} {line['purch_currency']}")

  if (pd.isna(line['auction_house']) or line['auction_house'] == 'Anonymous'): 
    house_txt = ('')
  else :
    house_txt = (f" par la maison [[{line['auction_house']}]]")

  source_txt = (f". [https://github.com/thegetty/provenance-index-csv]")

  concat_txt = '\n* '+date_txt+place_txt+title_txt+artist_txt+seller_txt+buyer_txt+price_txt+house_txt+source_txt

  return concat_txt

Ajout du type/genre de l'œuvre

Afin d'ajouter le type et le genre de l'œuvre lorsque ces informations sont disponibles l'ArtBot fait appel à la fonction sale_info_create qui reçoit comme argument une ligne de la dataframe, c'est à dire les infos relatives à une œuvre unique. Elle recherche ensuite les informations sur le type et le genre de l'œuvre et y associe un mot français et le numéro Wikidata correspondant. Enfin, la fonction retourne les informations de type et de genre au format correct.

def sale_info_create(line) :
    '''
    Take a line from the cleaned dataframe and prints its infos.
    '''
    # Trouver les correspondance
    type_entry = str(line['object_type']).lower().split("; ")
    genre_entry = str(line['genre']).lower().split("; ")

    type_txt = ""
    # Traduction en français
    for i in range(len(type_entry)):
        for word in types_trans:
            if word == type_entry[i]:
                type_entry[i] = types_trans[word]
                break
    # Associe un lien Wikidata
    for i in range(len(type_entry)):
        for word in types_wiki:
            if word == type_entry[i]:     
                type_txt = type_txt + "Type : " + word.capitalize() + ". (Wikidata : " + types_wiki[word] + ")\n"
                break

    genre_txt = ""
    # Traduction en français
    for i in range(len(genre_entry)):
        for word in genres_trans:
            if word == genre_entry[i]:
                genre_entry[i] = genres_trans[word]
                break
    # Associe un lien Wikidata
    for i in range(len(genre_entry)):
        for word in genres_wiki:
            if word == genre_entry[i]:     
                genre_txt = "Genre : " + word.capitalize() + ". (Wikidata : " + genres_wiki[word] + ")\n"
                break

    return type_txt + "\n" + genre_txt

Création de liens entre les pages

Les pages WikiPast peuvent être crées sans être reliées à aucune autre page du site. Ceci ne pose pas problème à petite échelle, mais lors de la génération de plus d'un million de page, les nouvelles pages peuvent être perdues. Pour remédier à cette complication, des pages de liens est définie. Ces dernières sont structurées de la manière suivante: La page Ventes d'œuvres par année est crée comme source de l'arborescence de liens: cette page regroupe toutes les années durant lesquelles une vente à été enregistrée. Ensuite, une page est crée pour chacune de ces années (Exemple). Celles-ci rassemblent toutes les dates (année/mois/jour) de cette année auxquelles une entrée est enregistrée dans la base de données. Le schéma se répète et chaque date possède une page réunissant toutes les ventes homologuées de la journée(Exemple). Quelques exceptions sont tout de mêmes notables. Heureusement, chaque entrée est associée à année de vente. Par contre le mois et le jour ne sont pas toujours compris dans la database. Ainsi, deux cas possibles sont traités: si le mois vaut une valeur de 0, le jour et le mois sont remplacés par un "-". Ceci est décidé car le jour sans le mois apporte peu d'informations. Le deuxième cas de figure est celui ou seul le jour est de valeur nulle. Le mois est alors pertinent et seulement le jour est changé pour un "-". De cette manière, même les entrées de la base de données avec une date incomplète seront ajoutés dans l'arborescence de liens.

Discussion des performances de l'ArtBot

nombre de page créées, problèmes rencontrés etc.