Python cria gráfico de palavras entre duas listas no matplotlib para mostrar a semelhança da lista

Digamos que eu tenha duas listas de palavras

list1 = ['Cat', 'Dog', 'Elephant', 'Giraffe' 'Monkey'] list2 = ['Cat', 'Dog', 'Eagle', 'Elephant', 'Monkey'] 

E eu gostaria de criar um enredo que se pareça com isso:

 Cat -> Cat Dog -> Dog Elephant \ Eagle Giraffe > Elephant Monkey -> Monkey 

Basicamente, uma palavra ‘ladder’ é plotada com setas conectando cada palavra comum entre as duas listas. Se uma dada palavra na list1 não tiver uma contraparte na list2 (como a Águia e a Girafa no exemplo), então não é necessária uma flecha.

Eu não estou ciente de uma maneira de fazer isso no matplotlib. Alguém sabe como fazer isso no matplotlib (talvez em conjunto com o networkx?)? Pontos de bônus se o gráfico funciona para um número arbitrário de listas (digamos, com outro conjunto de setas conectando list2 e list3 também, etc).

Acho que colocar os dados em uma representação baseada em charts é uma boa abordagem para o problema, conforme descrito, mas talvez você tenha um caso de uso em que isso seja muito pesado. No primeiro, @ xg.pltpy já fez uma sugestão.

Aqui está uma maneira de fazer isso apenas no matplotlib, usando a poderosa funcionalidade de annotate .

 import matplotlib.pyplot as plt # define drawing of the words and links separately. def plot_words(wordlist, col, ax): bbox_props = dict(boxstyle="round4,pad=0.3", fc="none", ec="b", lw=2) for i, word in enumerate(wordlist): ax.text(col, i, word, ha="center", va="center", size=12, bbox=bbox_props) def plot_links(list1, list2, cols, ax): connectionstyle = "arc3,rad=0" for i, word in enumerate(list1): try: # do we need an edge? j = list2.index(word) except ValueError: continue # move on to the next word # define coordinates (relabelling here for clarity only) y1, y2 = i, j x1, x2 = cols # draw a line from word in 1st list to word in 2nd list ax.annotate("", xy=(x2, y2), xycoords='data', xytext=(x1, y1), textcoords='data', arrowprops=dict( arrowstyle="->", color="k", lw=2, shrinkA=25, shrinkB=25, patchA=None, patchB=None, connectionstyle=connectionstyle,)) # define several lists list1 = ['Cat', 'Dog', 'Elephant', 'Giraffe', 'Monkey'] list2 = ['Cat', 'Dog', 'Eagle', 'Elephant', 'Monkey'] list3 = ['Cat', 'Mouse', 'Horse', 'Elephant', 'Monkey'] # now plot them all -- words first then links between them plt.figure(1); plt.clf() fig, ax = plt.subplots(num=1) plot_words(list1, col=1, ax=ax) plot_words(list2, col=2, ax=ax) plot_words(list3, col=0, ax=ax) plot_links(list1, list2, ax=ax, cols=[1,2]) plot_links(list1, list3, ax=ax, cols=[1,0]) ax.set_xlim(-0.5, 2.5) ax.set_ylim(-0.5, len(list1)+0.5) 

gráfico barato

Existem LOTES de opções para o tipo de seta, veja demonstração .

Seria mais limpo fornecer os argumentos patchB e patchB no modo de seta, pois o annotate então corta automaticamente o comprimento da seta para evitar os patches (aqui, as palavras). Deixo isso como um exercício para o leitor;)

Confira o matplotlib.pyplot.text . Você pode dar uma coordenada x,y exata para um ponto em um gráfico e ele irá ‘plotar’ essa palavra.

Aqui está um exemplo desleixado, mas de trabalho:

 import matplotlib.pyplot as plt list1 = ['Cat', 'Dog', 'Elephant', 'Giraffe', 'Monkey'] list2 = ['Cat', 'Dog', 'Eagle', 'Elephant', 'Monkey'] fig, ax = plt.subplots() x = .5 y = 1 for i, word in enumerate(list1): ax.text(x,y,word) if word == list2[i]: ax.text(x+.25,y,'-> '+word) else: ax.text(x+.25,y,'/ '+list2[i]) y = y-1/len(list1) 

insira a descrição da imagem aqui

Aqui está um exemplo com o networkx .

Disclaimer: Muitos dos códigos dentro dos loops for podem ser simplificados e convertidos em one-liners (ou seja, os dictionarys de posição e label podem ser facilmente convertidos para one-liners no python 3.5 ou superior usando esta resposta ). Para maior clareza, acreditei que era melhor explicitar todos os passos.

O primeiro passo é criar um gráfico direcionado em networkx . Em seguida, para cada elemento na list2 , as seguintes ações são executadas:

  • A posição e o label na plotagem do nó são armazenados em um dictionary.
  • Um nó é adicionado ao gráfico. Como os elementos nas listas são repetidos, o nome do nó não é o animal em list2 mas o nome seguido por 'list2' , para ter nós diferentes. É por isso que precisamos de um label_dict .

Para list1 , as mesmas etapas são executadas, adicionando um passo a mais:

  • Se o animal atual estiver na lista 2, adicione uma borda no gráfico

Aqui está o código de exemplo, que funciona para qualquer tamanho das listas e também se elas têm comprimentos diferentes.

 import networkx as nx list1 = ['Cat', 'Dog', 'Elephant', 'Giraffe', 'Monkey'] list2 = ['Cat', 'Dog', 'Eagle', 'Elephant', 'Monkey'] DG = nx.DiGraph() pos_dict = {}; label_dict = {} # dictionary with the plot info for i,animal in enumerate(list2): pos_dict['{}list2'.format(animal)] = (1,i) label_dict['{}list2'.format(animal)] = animal DG.add_node('{}list2'.format(animal)) for i,animal in enumerate(list1): pos_dict['{}list1'.format(animal)] = (0,i) label_dict['{}list1'.format(animal)] = animal DG.add_node('{}list1'.format(animal)) if animal in list2: DG.add_edge('{}list1'.format(animal),'{}list2'.format(animal)) nx.draw_networkx(DG, arrows=True, with_labels=True, node_color='w', pos=pos_dict, labels=label_dict, node_size=2000) plt.axis('off') # removes the axis to leave only the graph 

A imagem de saída usando networkx2.1 (em 2.0 as setas parecem diferentes) é esta:

enredo