Preface: Before anyone suggest using pandas, no I don't want to use pandas, only pure python please.
The concept of my code is quite simple, given some list of strings in a text file formatted as such (all the same size):
#sample_test.txt
------dooadb-----
asdoutdocnbmasdgd
--sdfhteoadb-----
-----asdfhwi-----
----ssdweibb-----
Find how frequently an element occurs within each position of each string (i.e. the first column would be [-,a,-,-,-]
) using the shortest string as the bounds (i.e. ignoring the '-'). Then plot each element, x being the position, y being the frequency of occurrence. With the size being proportional to the how frequently the element occurs.
As an example:
The shortest string is the very first line 'dooadb'
, thus the very first "column" to compare would be ['d','d','t','s','d']
. Within this column, d occurs 3x, t and s occur once. So for the plot, in position 0, d would be at the top, being the biggest letter. t and s would be lower (overlapping one another since they have the same occurrence)
What I've done in the code below is 3 functions. First I find the index of the shortest string to use as the bounds. Then I create a list of dictionaries, where the key is the letter and the value the frequency it occurs. Finally, I plot this using a scatter plot.
There are a couple of issues however:
- I think this code is redundant, bloated, and ugly. I think using my counts and loops in the find_bounds() function is...well there should be a cleaner better way to do this than that.
- I think using a dictionary is the best way to go about this, it contains all the info I need for my plot. However, I feel like I'm not quite utilizing it properly. I'm thinking there should be a way to simply plot the dictionary as is, without breaking it down within a loop to its keys and values. Furthermore, I have a 2nd loop because keys with the same values will result in overlap, to resolve this I create a new dict with the key now being the value, and the label being a combination of the duplicates. This way they are plotted side by side. However, I wonder if there is a cleaner way to do this that doesn't involve looping through my dicts and creating a new dict (i.e. I just feel like I'm utilizing my dicts wrong).
- Finally, while the plot works, it doesn't look good. The annotate means the labels will be to the side of the dot (i.e. not aligned to the x_axis), whereas I'd like it to actually replace the dot (but using annotate by itself doesn't work, you need to first plot the data than annotate it). This is also why I make the size of the dot super small, since I don't even want to see it, I just want the label. The label size also is arbitrary. If I set it to just the frequency of occurrence, its too small, but I also have to be careful not to set the small ones too big, because then the bigger ones will be too large. Ideally, I feel like normalizing all the values (in terms of size) to each other then expanding them is key. This way they are resized proportionally (i.e. if one letter occurs 2x and another 4x, the 4x letter should be twice as large as the 2x, but both as still legible). Additionally, the way the label works, it's not treated like a data point, so the bounds of the plot don't resize to fit it. Therefore, as it stands the bigger letters go outside of the bounds of the plot so it looks ugly.
In short, the code works, but the output plot is ugly, I think the code is poorly written, and I feel like I'm not utilizing the dictionary properly.
import matplotlib.pyplot as plt
def find_bounds():
"""find bounds of the shortest string"""
bounds=[]
temp_bounds=[]
with open('test_samples.txt') as file:
for lines in file:
count=0
counter=0
for elements in lines:
if elements == '-':
count+=1
if counter > 1:
counter-=1
temp_bounds.append(counter)
break
else:
counter+=1
if counter == 1:
temp_bounds.append(count)
if bounds == []:
bounds.append(temp_bounds[0])
bounds.append(temp_bounds[1])
continue
if temp_bounds[1] < bounds[1]:
bounds.clear()
bounds.append(temp_bounds[0])
bounds.append(temp_bounds[1])
temp_bounds.clear()
bounds[1]=bounds[0]+bounds[1]
return bounds
def find_frequency():
"""Using the bounds above, find the occurence frequency of each letter in it's respective column"""
bounds=find_bounds()
start=bounds[0]
end=bounds[1]
sequences=[]
list_of_frequencies=[]
with open('test_samples.txt') as file:
for lines in file:
sequences.append(list(lines[start:end+1]))
for x in range(len(sequences[0])):
frequence_counts={}
for entry in [item[x] for item in sequences]:
if entry in frequence_counts:
frequence_counts[entry] += 1
else:
frequence_counts.update({entry: 1})
list_of_frequencies.append([frequence_counts])
return list_of_frequencies
def plotting():
"""Plot the letters, with their size being proportional to their occurence frequency, and the y value being the occurence frequency. Letters that have the same occurence frequency should be plotted side by side so as to not overlap"""
plot_frequency_list=find_frequency()
for x,dicts in enumerate(plot_frequency_list):
duplicate_dict={}
for next_dict in dicts:
for dict_values,labels in zip(next_dict.values(),next_dict.keys()):
if dict_values in duplicate_dict:
duplicate_dict[dict_values] += labels
else:
duplicate_dict.update({dict_values: labels})
for dict_values,labels in zip(next_dict.values(),next_dict.keys()):
plt.scatter(x,dict_values,s=dict_values/1000000000000)
plt.annotate(duplicate_dict[dict_values],(x,dict_values),fontsize=dict_values**3+10)
plt.show()
plotting()