forked from MapComplete/MapComplete
		
	Add some fancy graphs
This commit is contained in:
		
							parent
							
								
									f464600ab8
								
							
						
					
					
						commit
						c49585a70a
					
				
					 11 changed files with 3120 additions and 0 deletions
				
			
		
							
								
								
									
										
											BIN
										
									
								
								Docs/Tools/Contributors in 2020.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Docs/Tools/Contributors in 2020.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 199 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Docs/Tools/Contributors in 2021.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Docs/Tools/Contributors in 2021.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 196 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Docs/Tools/Contributors.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Docs/Tools/Contributors.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 196 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Docs/Tools/CumulativeContributors in 2020.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Docs/Tools/CumulativeContributors in 2020.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 264 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Docs/Tools/CumulativeContributors in 2021.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Docs/Tools/CumulativeContributors in 2021.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 258 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Docs/Tools/CumulativeContributors.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Docs/Tools/CumulativeContributors.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 258 KiB | 
							
								
								
									
										6
									
								
								Docs/Tools/compileStats.sh
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										6
									
								
								Docs/Tools/compileStats.sh
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,6 @@ | ||||||
|  | #! /bin/bash | ||||||
|  | 
 | ||||||
|  | ./fetchStats.sh | ||||||
|  | ./csvPerChange.sh | ||||||
|  | python csvGrapher.py  | ||||||
|  | 
 | ||||||
							
								
								
									
										224
									
								
								Docs/Tools/csvGrapher.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								Docs/Tools/csvGrapher.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,224 @@ | ||||||
|  | import csv | ||||||
|  | from datetime import datetime | ||||||
|  | 
 | ||||||
|  | from matplotlib import pyplot | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def clean(s): | ||||||
|  |     return s.strip().strip("\"") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def counts(lst): | ||||||
|  |     counts = {} | ||||||
|  |     for v in lst: | ||||||
|  |         if not v in counts: | ||||||
|  |             counts[v] = 0 | ||||||
|  |         counts[v] += 1 | ||||||
|  |     return counts | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Hist: | ||||||
|  | 
 | ||||||
|  |     def __init__(self, firstcolumn): | ||||||
|  |         self.key = "\"" + firstcolumn + "\"" | ||||||
|  |         self.dictionary = {} | ||||||
|  |         self.key = "" | ||||||
|  | 
 | ||||||
|  |     def add(self, key, value): | ||||||
|  |         if not key in self.dictionary: | ||||||
|  |             self.dictionary[key] = [] | ||||||
|  |         self.dictionary[key].append(value) | ||||||
|  | 
 | ||||||
|  |     def values(self): | ||||||
|  |         allV = [] | ||||||
|  |         for v in self.dictionary.values(): | ||||||
|  |             allV += list(set(v)) | ||||||
|  |         return list(set(allV)) | ||||||
|  | 
 | ||||||
|  |     def keys(self): | ||||||
|  |         return self.dictionary.keys() | ||||||
|  | 
 | ||||||
|  |     def get(self, key): | ||||||
|  |         if key in self.dictionary: | ||||||
|  |             return self.dictionary[key] | ||||||
|  |         return None | ||||||
|  | 
 | ||||||
|  |     # Returns (keys, values.map(f)). To be used with e.g. pyplot.plot | ||||||
|  |     def map(self, f): | ||||||
|  |         vals = [] | ||||||
|  |         keys = self.keys() | ||||||
|  |         for key in keys: | ||||||
|  |             vals.append(f(self.get(key))) | ||||||
|  |         return vals | ||||||
|  | 
 | ||||||
|  |     def mapcumul(self, f, add, zero): | ||||||
|  |         vals = [] | ||||||
|  |         running_value = zero | ||||||
|  |         keys = self.keys() | ||||||
|  |         for key in keys: | ||||||
|  |             v = f(self.get(key)) | ||||||
|  |             running_value = add(running_value, v) | ||||||
|  |             vals.append(running_value) | ||||||
|  |         return vals | ||||||
|  | 
 | ||||||
|  |     def csv(self): | ||||||
|  |         csv = self.key + "," + ",".join(self.values()) | ||||||
|  |         header = self.values() | ||||||
|  |         for k in self.dictionary.keys(): | ||||||
|  |             csv += k | ||||||
|  |             values = counts(self.dictionary[k]) | ||||||
|  |             for head in header: | ||||||
|  |                 if head in values: | ||||||
|  |                     csv += "," + str(values[head]) | ||||||
|  |                 else: | ||||||
|  |                     csv += ",0" | ||||||
|  |             csv += "\n" | ||||||
|  |         return csv | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def build_hist(stats, keyIndex, valueIndex, condition=None): | ||||||
|  |     hist = Hist("date") | ||||||
|  |     c = 0 | ||||||
|  |     for row in stats: | ||||||
|  |         print(row[0] + " => " + str(condition(row))) | ||||||
|  |         if condition is not None and not condition(row): | ||||||
|  |             continue | ||||||
|  |         c += 1 | ||||||
|  |         row = list(map(clean, row)) | ||||||
|  |         hist.add(row[keyIndex], row[valueIndex]) | ||||||
|  |     return hist | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cumulative_users(stats, year=""): | ||||||
|  |     users_hist = build_hist(stats, 0, 1, lambda row: row[0].startswith(year)) | ||||||
|  |     all_users_per_day = users_hist.mapcumul( | ||||||
|  |         lambda users: set(users), | ||||||
|  |         lambda a, b: a.union(b), | ||||||
|  |         set([]) | ||||||
|  |     ) | ||||||
|  |     cumul_uniq = list(map(len, all_users_per_day)) | ||||||
|  |     unique_per_day = users_hist.map(lambda users: len(set(users))) | ||||||
|  |     new_users = [0] | ||||||
|  |     for i in range(len(cumul_uniq) - 1): | ||||||
|  |         new_users.append(cumul_uniq[i + 1] - cumul_uniq[i]) | ||||||
|  |     dates = map(lambda dt: datetime.strptime(dt, "%Y-%m-%d"), users_hist.keys()) | ||||||
|  |     return list(dates), cumul_uniq, list(unique_per_day), list(new_users) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def pyplot_init(): | ||||||
|  |     pyplot.figure(figsize=(14, 8), dpi=200) | ||||||
|  |     pyplot.xticks(rotation='vertical') | ||||||
|  |     pyplot.tight_layout() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def create_usercount_graphs(stats, year="", show=False): | ||||||
|  |     dates, cumul_uniq, unique_per_day, new_users = cumulative_users(stats, year) | ||||||
|  |     total = cumul_uniq[-1] | ||||||
|  | 
 | ||||||
|  |     if year != "": | ||||||
|  |         year = " in " + year | ||||||
|  |     pyplot_init() | ||||||
|  |     pyplot.bar(dates, unique_per_day, label='Unique contributors') | ||||||
|  |     pyplot.bar(dates, new_users, label='First time contributor via MapComplete') | ||||||
|  |     pyplot.legend() | ||||||
|  |     pyplot.title("Unique contributors" + year + ' with MapComplete (' + str(total) + ' contributors)') | ||||||
|  |     pyplot.ylabel("Number of unique contributors") | ||||||
|  |     pyplot.xlabel("Date") | ||||||
|  |     if show: | ||||||
|  |         pyplot.show() | ||||||
|  |     else: | ||||||
|  |         pyplot.savefig("Contributors" + year + ".png", dpi=400, facecolor='w', edgecolor='w', bbox_inches='tight') | ||||||
|  | 
 | ||||||
|  |     pyplot_init() | ||||||
|  |     pyplot.plot(dates, cumul_uniq, label='Cumulative unique contributors') | ||||||
|  |     pyplot.legend() | ||||||
|  |     pyplot.title("Cumulative unique contributors" + year + " with MapComplete - " + str(total) + " contributors") | ||||||
|  |     pyplot.ylabel("Number of unique contributors") | ||||||
|  |     pyplot.xlabel("Date") | ||||||
|  |     if show: | ||||||
|  |         pyplot.show() | ||||||
|  |     else: | ||||||
|  |         pyplot.savefig("CumulativeContributors" + year + ".png", dpi=400, facecolor='w', edgecolor='w', | ||||||
|  |                        bbox_inches='tight') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def create_yearly_usercount_graphs(contents): | ||||||
|  |     create_usercount_graphs(contents) | ||||||
|  |     currentYear = datetime.now().year | ||||||
|  |     for year in range(2020, currentYear + 1): | ||||||
|  |         create_usercount_graphs(contents, str(year)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | theme_remappings = { | ||||||
|  |     "null": "buurtnatuur", | ||||||
|  |     "metamap": "maps", | ||||||
|  |     "wiki:mapcomplete/fritures": "fritures", | ||||||
|  |     "lits": "lit", | ||||||
|  |     "wiki:user:joost_schouppe/campersite": "campersite", | ||||||
|  |     "wiki-user-joost_schouppe-geveltuintjes": "geveltuintjes", | ||||||
|  |     "wiki-user-joost_schouppe-campersite":"campersites", | ||||||
|  |     "https://raw.githubusercontent.com/osmbe/play/master/mapcomplete/geveltuinen/geveltuinen.json": "geveltuintjes" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def create_theme_breakdown(stats, year="", user=None, columnIndex=3): | ||||||
|  |     themeCounts = {} | ||||||
|  |     for row in stats: | ||||||
|  |         if not row[0].startswith(year): | ||||||
|  |             continue | ||||||
|  |         if user is not None and clean(row[1]) != user: | ||||||
|  |             continue | ||||||
|  |         theme = clean(row[columnIndex]).lower() | ||||||
|  |         if theme in theme_remappings: | ||||||
|  |             theme = theme_remappings[theme] | ||||||
|  |         if theme in themeCounts: | ||||||
|  |             themeCounts[theme] += 1 | ||||||
|  |         else: | ||||||
|  |             themeCounts[theme] = 1 | ||||||
|  |     themes = list(themeCounts.items()) | ||||||
|  |     if len(themes) == 0: | ||||||
|  |         print("No entries found for user "+user+" in "+year) | ||||||
|  |         return | ||||||
|  |     themes.sort(key=lambda kv : kv[1], reverse=True) | ||||||
|  |      | ||||||
|  |     cutoff = 5 | ||||||
|  |     if user is not None: | ||||||
|  |         cutoff = 0 | ||||||
|  |     other_count = sum([theme[1] for theme in themes if theme[1] < cutoff]) | ||||||
|  |     themes_filtered = [theme for theme in themes if theme[1] >= cutoff] | ||||||
|  |     keys = list(map(lambda kv : kv[0] + " (" + str(kv[1])+")", themes_filtered)) | ||||||
|  |     values = list(map(lambda kv : kv[1], themes_filtered)) | ||||||
|  |     total =sum(map(lambda kv:kv[1], themes)) | ||||||
|  |     first_pct = themes[0][1] / total; | ||||||
|  |     if year != "": | ||||||
|  |         year = " in " + year | ||||||
|  |          | ||||||
|  |     if other_count > 0: | ||||||
|  |         keys.append("other") | ||||||
|  |         values.append(other_count) | ||||||
|  |     pyplot_init() | ||||||
|  |     pyplot.pie(values, labels=keys, startangle=(90 - 360 * first_pct/2)) | ||||||
|  |     if user is None: | ||||||
|  |         user = "" | ||||||
|  |     else: | ||||||
|  |         user = " by contributor "+user | ||||||
|  |     pyplot.title("MapComplete changes per theme"+year+user+ " - "+str(total)+" total changes") | ||||||
|  |     pyplot.savefig("Theme distribution" + user+year + ".png", dpi=400, facecolor='w', edgecolor='w', | ||||||
|  |                    bbox_inches='tight') | ||||||
|  |     return themes | ||||||
|  | 
 | ||||||
|  | def gen_theme_breakdown_graphs(contents, user=None): | ||||||
|  |     create_theme_breakdown(contents, "", user) | ||||||
|  |     currentYear = datetime.now().year | ||||||
|  |     for year in range(2020, currentYear + 1): | ||||||
|  |         create_theme_breakdown(contents, str(year), user) | ||||||
|  | 
 | ||||||
|  | def main(): | ||||||
|  |     with open('stats.csv', newline='') as csvfile: | ||||||
|  |         stats = list(csv.reader(csvfile, delimiter=',', quotechar='"')) | ||||||
|  |         # create_yearly_usercount_graphs(stats) | ||||||
|  |         gen_theme_breakdown_graphs(stats, "joost schouppe") | ||||||
|  |     print("All done!") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | main() | ||||||
							
								
								
									
										21
									
								
								Docs/Tools/csvPerChange.sh
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										21
									
								
								Docs/Tools/csvPerChange.sh
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | #! /bin/bash | ||||||
|  | 
 | ||||||
|  | if [[ ! -e stats.1.json ]] | ||||||
|  | then | ||||||
|  |     echo "No stats found - not compiling" | ||||||
|  |     exit | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | rm stats.csv | ||||||
|  | # echo "date, username, language, theme, editor, creations, changes" > stats.csv | ||||||
|  | echo "" > tmp.csv | ||||||
|  | 
 | ||||||
|  | for f in stats.*.json | ||||||
|  | do | ||||||
|  |     echo $f | ||||||
|  |     jq ".features[].properties | [.date, .user, .metadata.language, .metadata.theme, .editor, .create, .modify]" "$f" | tr -d "\n" | sed "s/]\[/\n/g" | tr -d "][" >> tmp.csv | ||||||
|  |     echo "" >> tmp.csv | ||||||
|  | done | ||||||
|  | 
 | ||||||
|  | sed "/^$/d" tmp.csv | sed "s/^  //" | sed "s/  / /g" | sed "s/\"\(....-..-..\)T........./\"\1/" | sort >> stats.csv | ||||||
|  | rm tmp.csv | ||||||
							
								
								
									
										18
									
								
								Docs/Tools/fetchStats.sh
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										18
									
								
								Docs/Tools/fetchStats.sh
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | DATE=$(date +"%Y-%m-%d%%20%H%%3A%M") | ||||||
|  | COUNTER=1 | ||||||
|  | if [[ $1 != "" ]] | ||||||
|  | then | ||||||
|  |     echo "Starting at $1" | ||||||
|  |     COUNTER="$1" | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | NEXT_URL=$(echo "https://osmcha.org/api/v1/changesets/?date__gte=2020-07-05&date__lte=$DATE&editor=mapcomplete&page=$COUNTER&page_size=1000") | ||||||
|  | rm stats.*.json | ||||||
|  | while [[ "$NEXT_URL" != "" ]] | ||||||
|  | do | ||||||
|  |     echo "$COUNTER '$NEXT_URL'" | ||||||
|  |     curl "$NEXT_URL" -H 'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:86.0) Gecko/20100101 Firefox/86.0' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'Referer: https://osmcha.org/?filters=%7B%22date__gte%22%3A%5B%7B%22label%22%3A%222020-07-05%22%2C%22value%22%3A%222020-07-05%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22mapcomplete%22%2C%22value%22%3A%22mapcomplete%22%7D%5D%7D' -H 'Content-Type: application/json' -H 'Authorization: Token 6e422e2afedb79ef66573982012000281f03dc91' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'TE: Trailers' -H 'Pragma: no-cache' -H 'Cache-Control: no-cache' -o stats.$COUNTER.json | ||||||
|  |      | ||||||
|  |     NEXT_URL=$(jq ".next" stats.$COUNTER.json | sed "s/\"//g") | ||||||
|  |     let COUNTER++ | ||||||
|  | done; | ||||||
							
								
								
									
										2851
									
								
								Docs/Tools/stats.csv
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2851
									
								
								Docs/Tools/stats.csv
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue